From f12728de615acd5be0bbb48fba983d7bcd665485 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 16 Dec 2021 03:29:22 +0000 Subject: [PATCH 0001/1699] [firebase-release] Removed change log and reset repo after 10.0.0 release --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae9cc9751d8..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +0,0 @@ -- **BREAKING** Drops support for running the CLI on Node 10. -- **BREAKING** Replaces all usages of `-y`, `--yes`, or `--confirm` with `-f` and `--force`. -- **BREAKING** Function deploys upload source to the deployed region instead of us-central1. -- Requires firebase-functions >= 3.13.1 in Functions emulator to include bug fixes (#3851). -- Updates default functions runtime to Node.js 16. From 318087b74c8908afe9093a200776cf322ca7b4a4 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Thu, 16 Dec 2021 11:37:48 -0800 Subject: [PATCH 0002/1699] Release Database Emulator v4.7.3. (#3955) * Release Database Emulator v4.7.3. * Update CHANGELOG.md. --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..92ca9b39ecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Upgrades Database Emulator to v4.7.3, removing log4j dependency. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 08fc674b3ff..90d2ea79edd 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -27,14 +27,14 @@ const CACHE_DIR = export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDetails } = { database: { - downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.7.2.jar"), - version: "4.7.2", + downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.7.3.jar"), + version: "4.7.3", opts: { cacheDir: CACHE_DIR, remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.7.2.jar", - expectedSize: 28910604, - expectedChecksum: "264e5df0c0661c064ef7dc9ce8179aba", + "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.7.3.jar", + expectedSize: 28862098, + expectedChecksum: "8f696f24ee89c937a789498a0c0e4899", namePrefix: "firebase-database-emulator", }, }, From 09e9dbc6e2d5975545d969379574839d26611293 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 16 Dec 2021 12:22:27 -0800 Subject: [PATCH 0003/1699] package-lock.json and npm audit package updates (#3954) * update package-lock, update check for package-lock to use npm@8 * don't run the prepare script when checking package-lock * this commit should fail testing * this commit should fix testing * update npm in all steps * update error message --- .github/workflows/node-test.yml | 9 +- package-lock.json | 14231 +++++++++++++++++++++++++++++- 2 files changed, 14209 insertions(+), 31 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index a41713ebcdc..3856b201cfd 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -29,6 +29,7 @@ jobs: path: ~/.npm key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + - run: npm i -g npm@8 - run: npm ci - run: npm run lint:changed-files @@ -52,6 +53,7 @@ jobs: path: ~/.npm key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + - run: npm i -g npm@8 - run: npm ci - run: npm test @@ -99,6 +101,7 @@ jobs: key: ${{ runner.os }}-firebase-emulators-${{ hashFiles('emulator-cache/**') }} continue-on-error: true + - run: npm i -g npm@8 - run: npm ci - run: echo ${{ secrets.service_account_json_base64 }} | base64 -d > ./scripts/service-account.json - run: ${{ matrix.script }} @@ -121,8 +124,10 @@ jobs: uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - - run: npm install --package-lock-only - - run: "git diff --exit-code -- package-lock.json || (echo 'Error: package-lock.json is changed during npm install! Please make sure to use npm >= 6.9.0 and commit package-lock.json.' && false)" + - run: npm i -g npm@8 + # --ignore-scripts prevents the `prepare` script from being run. + - run: npm install --package-lock-only --ignore-scripts + - run: "git diff --exit-code -- package-lock.json || (echo 'Error: package-lock.json is changed during npm install! Please make sure to use npm >= 8 and commit package-lock.json.' && false)" check-json-schema: runs-on: ubuntu-latest diff --git a/package-lock.json b/package-lock.json index 4224b76f366..82256e08937 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,14170 @@ { "name": "firebase-tools", "version": "10.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "firebase-tools", + "version": "10.0.0", + "license": "MIT", + "dependencies": { + "@google-cloud/pubsub": "^2.7.0", + "@types/archiver": "^5.1.0", + "abort-controller": "^3.0.0", + "ajv": "^6.12.6", + "archiver": "^5.0.0", + "body-parser": "^1.19.0", + "chokidar": "^3.0.2", + "cjson": "^0.3.1", + "cli-color": "^1.2.0", + "cli-table": "0.3.11", + "commander": "^4.0.1", + "configstore": "^5.0.1", + "cors": "^2.8.5", + "cross-env": "^5.1.3", + "cross-spawn": "^7.0.1", + "csv-streamify": "^3.0.4", + "dotenv": "^6.1.0", + "exegesis": "^2.5.7", + "exegesis-express": "^2.0.0", + "exit-code": "^1.0.2", + "express": "^4.16.4", + "filesize": "^6.1.0", + "fs-extra": "^5.0.0", + "glob": "^7.1.2", + "google-auth-library": "^6.1.3", + "inquirer": "~6.3.1", + "js-yaml": "^3.13.1", + "JSONStream": "^1.2.1", + "jsonwebtoken": "^8.5.1", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "marked": "^0.7.0", + "marked-terminal": "^3.3.0", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "morgan": "^1.10.0", + "node-fetch": "^2.6.1", + "open": "^6.3.0", + "ora": "^3.4.0", + "portfinder": "^1.0.23", + "progress": "^2.0.3", + "proxy-agent": "^5.0.0", + "request": "^2.87.0", + "rimraf": "^3.0.0", + "semver": "^5.7.1", + "superstatic": "^7.1.0", + "tar": "^4.3.0", + "tcp-port-used": "^1.0.1", + "tmp": "0.0.33", + "triple-beam": "^1.3.0", + "tweetsodium": "0.0.5", + "universal-analytics": "^0.4.16", + "unzipper": "^0.10.10", + "update-notifier": "^5.1.0", + "uuid": "^8.3.2", + "winston": "^3.0.0", + "winston-transport": "^4.4.0", + "ws": "^7.2.3" + }, + "bin": { + "firebase": "lib/bin/firebase.js" + }, + "devDependencies": { + "@google/events": "^5.1.1", + "@manifoldco/swagger-to-ts": "^2.0.0", + "@types/body-parser": "^1.17.0", + "@types/chai": "^4.2.12", + "@types/chai-as-promised": "^7.1.3", + "@types/cjson": "^0.5.0", + "@types/cli-color": "^0.3.29", + "@types/cli-table": "^0.3.0", + "@types/configstore": "^4.0.0", + "@types/cors": "^2.8.10", + "@types/cross-spawn": "^6.0.1", + "@types/dotenv": "^6.1.0", + "@types/express": "^4.17.0", + "@types/express-serve-static-core": "^4.17.8", + "@types/fs-extra": "^5.0.5", + "@types/glob": "^7.1.1", + "@types/inquirer": "^6.0.3", + "@types/js-yaml": "^3.12.2", + "@types/jsonwebtoken": "^8.3.8", + "@types/lodash": "^4.14.149", + "@types/marked": "^0.6.5", + "@types/mocha": "^8.2.0", + "@types/multer": "^1.4.3", + "@types/node": "^10.17.50", + "@types/node-fetch": "^2.5.7", + "@types/progress": "^2.0.3", + "@types/puppeteer": "^5.4.2", + "@types/request": "^2.48.1", + "@types/rimraf": "^2.0.3", + "@types/semver": "^6.0.0", + "@types/sinon": "^9.0.10", + "@types/sinon-chai": "^3.2.2", + "@types/supertest": "^2.0.6", + "@types/tar": "^4.0.0", + "@types/tcp-port-used": "^1.0.0", + "@types/tmp": "^0.1.0", + "@types/triple-beam": "^1.3.0", + "@types/unzipper": "^0.10.0", + "@types/uuid": "^8.3.1", + "@types/winston": "^2.4.4", + "@types/ws": "^7.2.3", + "@typescript-eslint/eslint-plugin": "^4.12.0", + "@typescript-eslint/parser": "^4.12.0", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", + "eslint": "^7.17.0", + "eslint-config-google": "^0.14.0", + "eslint-config-prettier": "^7.1.0", + "eslint-plugin-jsdoc": "^30.7.13", + "eslint-plugin-prettier": "^3.3.1", + "firebase": "^7.24.0", + "firebase-admin": "^9.4.2", + "firebase-functions": "^3.15.0", + "google-discovery-to-swagger": "^2.1.0", + "mocha": "^8.2.1", + "nock": "^13.0.5", + "nyc": "^15.1.0", + "openapi-merge": "^1.0.23", + "prettier": "^2.2.1", + "proxy": "^1.0.2", + "puppeteer": "^9.0.0", + "sinon": "^9.2.3", + "sinon-chai": "^3.6.0", + "source-map-support": "^0.5.9", + "supertest": "^3.3.0", + "swagger2openapi": "^6.0.3", + "ts-node": "^9.1.1", + "typescript": "^3.9.5", + "typescript-json-schema": "^0.50.1" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.7.tgz", + "integrity": "sha512-QdwOGF1+eeyFh+17v2Tz626WX0nucd1iKOm6JUTUvCZdbolblCOOQCxGrQPY0f7jEhn36PiAWqZnsC2r5vmUWg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.13.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.0.0" + } + }, + "node_modules/@babel/core": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.6", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/core/node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.11.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "node_modules/@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "dependencies": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/template/node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/template/node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", + "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/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==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@eslint/eslintrc/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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@exodus/schemasafe": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.2.tgz", + "integrity": "sha512-W98NvvOe/Med3o66xTO03pd7a2omZebH79PV64gSE+ceDdU8uxQhFTa7ISiD1kseyqyOrMyW5/MNdsGEU02i3Q==", + "dev": true + }, + "node_modules/@firebase/analytics": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.6.0.tgz", + "integrity": "sha512-6qYEOPUVYrMhqvJ46Z5Uf1S4uULd6d7vGpMP5Qz+u8kIWuOQGcPdJKQap+Hla6Rq164or9gC2HRXuYXKlgWfpw==", + "dev": true, + "dependencies": { + "@firebase/analytics-types": "0.4.0", + "@firebase/component": "0.1.19", + "@firebase/installations": "0.4.17", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.4.0.tgz", + "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==", + "dev": true + }, + "node_modules/@firebase/analytics/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/analytics/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/app": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.11.tgz", + "integrity": "sha512-FH++PaoyTzfTAVuJ0gITNYEIcjT5G+D0671La27MU8Vvr6MTko+5YUZ4xS9QItyotSeRF4rMJ1KR7G8LSyySiA==", + "dev": true, + "dependencies": { + "@firebase/app-types": "0.6.1", + "@firebase/component": "0.1.19", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "dom-storage": "2.1.0", + "tslib": "^1.11.1", + "xmlhttprequest": "1.8.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "dev": true + }, + "node_modules/@firebase/app/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/app/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/auth": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.15.0.tgz", + "integrity": "sha512-IFuzhxS+HtOQl7+SZ/Mhaghy/zTU7CENsJFWbC16tv2wfLZbayKF5jYGdAU3VFLehgC8KjlcIWd10akc3XivfQ==", + "dev": true, + "dependencies": { + "@firebase/auth-types": "0.10.1" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", + "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "0.x" + } + }, + "node_modules/@firebase/auth-types": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", + "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "0.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", + "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.4", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/database": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.1.tgz", + "integrity": "sha512-/1HhR4ejpqUaM9Cn3KSeNdQvdlehWIhdfTVWFxS73ZlLYf7ayk9jITwH10H3ZOIm5yNzxF67p/U7Z/0IPhgWaQ==", + "dev": true, + "dependencies": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.21", + "@firebase/database-types": "0.6.1", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.4", + "faye-websocket": "0.11.3", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz", + "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==", + "dev": true, + "dependencies": { + "@firebase/app-types": "0.6.1" + } + }, + "node_modules/@firebase/firestore": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.18.0.tgz", + "integrity": "sha512-maMq4ltkrwjDRusR2nt0qS4wldHQMp+0IDSfXIjC+SNmjnWY/t/+Skn9U3Po+dB38xpz3i7nsKbs+8utpDnPSw==", + "dev": true, + "dependencies": { + "@firebase/component": "0.1.19", + "@firebase/firestore-types": "1.14.0", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "@firebase/webchannel-wrapper": "0.4.0", + "@grpc/grpc-js": "^1.0.0", + "@grpc/proto-loader": "^0.5.0", + "node-fetch": "2.6.1", + "tslib": "^1.11.1" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.14.0.tgz", + "integrity": "sha512-WF8IBwHzZDhwyOgQnmB0pheVrLNP78A8PGxk1nxb/Nrgh1amo4/zYvFMGgSsTeaQK37xMYS/g7eS948te/dJxw==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/firestore/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/firestore/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/functions": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.5.1.tgz", + "integrity": "sha512-yyjPZXXvzFPjkGRSqFVS5Hc2Y7Y48GyyMH+M3i7hLGe69r/59w6wzgXKqTiSYmyE1pxfjxU4a1YqBDHNkQkrYQ==", + "dev": true, + "dependencies": { + "@firebase/component": "0.1.19", + "@firebase/functions-types": "0.3.17", + "@firebase/messaging-types": "0.5.0", + "node-fetch": "2.6.1", + "tslib": "^1.11.1" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.17.tgz", + "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==", + "dev": true + }, + "node_modules/@firebase/functions/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/functions/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/installations": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.17.tgz", + "integrity": "sha512-AE/TyzIpwkC4UayRJD419xTqZkKzxwk0FLht3Dci8WI2OEKHSwoZG9xv4hOBZebe+fDzoV2EzfatQY8c/6Avig==", + "dev": true, + "dependencies": { + "@firebase/component": "0.1.19", + "@firebase/installations-types": "0.3.4", + "@firebase/util": "0.3.2", + "idb": "3.0.2", + "tslib": "^1.11.1" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", + "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/installations/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/installations/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, + "node_modules/@firebase/messaging": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.7.1.tgz", + "integrity": "sha512-iev/ST9v0xd/8YpGYrZtDcqdD9J6ZWzSuceRn8EKy5vIgQvW/rk2eTQc8axzvDpQ36ZfphMYuhW6XuNrR3Pd2Q==", + "dev": true, + "dependencies": { + "@firebase/component": "0.1.19", + "@firebase/installations": "0.4.17", + "@firebase/messaging-types": "0.5.0", + "@firebase/util": "0.3.2", + "idb": "3.0.2", + "tslib": "^1.11.1" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/messaging-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.5.0.tgz", + "integrity": "sha512-QaaBswrU6umJYb/ZYvjR5JDSslCGOH6D9P136PhabFAHLTR4TWjsaACvbBXuvwrfCXu10DtcjMxqfhdNIB1Xfg==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/messaging/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/messaging/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/performance": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.4.2.tgz", + "integrity": "sha512-irHTCVWJ/sxJo0QHg+yQifBeVu8ZJPihiTqYzBUz/0AGc51YSt49FZwqSfknvCN2+OfHaazz/ARVBn87g7Ex8g==", + "dev": true, + "dependencies": { + "@firebase/component": "0.1.19", + "@firebase/installations": "0.4.17", + "@firebase/logger": "0.2.6", + "@firebase/performance-types": "0.0.13", + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", + "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==", + "dev": true + }, + "node_modules/@firebase/performance/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/performance/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/polyfill": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", + "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", + "dev": true, + "dependencies": { + "core-js": "3.6.5", + "promise-polyfill": "8.1.3", + "whatwg-fetch": "2.0.4" + } + }, + "node_modules/@firebase/remote-config": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.28.tgz", + "integrity": "sha512-4zSdyxpt94jAnFhO8toNjG8oMKBD+xTuBIcK+Nw8BdQWeJhEamgXlupdBARUk1uf3AvYICngHH32+Si/dMVTbw==", + "dev": true, + "dependencies": { + "@firebase/component": "0.1.19", + "@firebase/installations": "0.4.17", + "@firebase/logger": "0.2.6", + "@firebase/remote-config-types": "0.1.9", + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", + "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==", + "dev": true + }, + "node_modules/@firebase/remote-config/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/remote-config/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/storage": { + "version": "0.3.43", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.43.tgz", + "integrity": "sha512-Jp54jcuyimLxPhZHFVAhNbQmgTu3Sda7vXjXrNpPEhlvvMSq4yuZBR6RrZxe/OrNVprLHh/6lTCjwjOVSo3bWA==", + "dev": true, + "dependencies": { + "@firebase/component": "0.1.19", + "@firebase/storage-types": "0.3.13", + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz", + "integrity": "sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "0.x" + } + }, + "node_modules/@firebase/storage/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/storage/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/util": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", + "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", + "integrity": "sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==", + "dev": true + }, + "node_modules/@google-cloud/common": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", + "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", + "dev": true, + "optional": true, + "dependencies": { + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^6.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.8.0.tgz", + "integrity": "sha512-cBPo7QQG+aUhS7AIr6fDlA9KIX0/U26rKZyL2K/L68LArDQzgBk1/xOiMoflHRNDQARwCQ0PAZmw8V8CXg7vTg==", + "dev": true, + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^2.9.2" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@google-cloud/firestore/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==", + "dev": true, + "optional": true + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz", + "integrity": "sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/precise-date": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-2.0.3.tgz", + "integrity": "sha512-+SDJ3ZvGkF7hzo6BGa8ZqeK3F6Z4+S+KviC9oOK+XCs3tfMyJCh/4j93XIWINgMMDIh9BgEvlw4306VxlXIlYA==", + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/pubsub": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.7.0.tgz", + "integrity": "sha512-wc/XOo5Ibo3GWmuaLu80EBIhXSdu2vf99HUqBbdsSSkmRNIka2HqoIhLlOFnnncQn0lZnGL7wtKGIDLoH9LiBg==", + "dependencies": { + "@google-cloud/paginator": "^3.0.0", + "@google-cloud/precise-date": "^2.0.0", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/tracing": "^0.11.0", + "@types/duplexify": "^3.6.0", + "@types/long": "^4.0.0", + "arrify": "^2.0.0", + "extend": "^3.0.2", + "google-auth-library": "^6.1.2", + "google-gax": "^2.9.2", + "is-stream-ended": "^0.1.4", + "lodash.snakecase": "^4.1.1", + "p-defer": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/storage": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.7.0.tgz", + "integrity": "sha512-6nPTylNaYWsVo5yHDdjQfUSh9qP/DFwahhyvOAf9CSDKfeoOys8+PAyHsoKyL29uyYoC6ymws7uJDO48y/SzBA==", + "dev": true, + "optional": true, + "dependencies": { + "@google-cloud/common": "^3.5.0", + "@google-cloud/paginator": "^3.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.0", + "compressible": "^2.0.12", + "date-and-time": "^0.14.0", + "duplexify": "^4.0.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "gcs-resumable-upload": "^3.1.0", + "get-stream": "^6.0.0", + "hash-stream-validation": "^0.2.2", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/storage/node_modules/get-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@google-cloud/storage/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, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@google-cloud/storage/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, + "optional": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google-cloud/storage/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==", + "dev": true, + "optional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google/events": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@google/events/-/events-5.1.1.tgz", + "integrity": "sha512-97u6AUfEXo6TxoBAdbziuhSL56+l69WzFahR6eTQE/bSjGPqT1+W4vS7eKaR7r60pGFrZZfqdFZ99uMbns3qgA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.8.tgz", + "integrity": "sha512-64hg5rmEm6F/NvlWERhHmmgxbWU8nD2TMWE+9TvG7/WcOrFT3fzg/Uu631pXRFwmJ4aWO/kp9vVSlr8FUjBDLA==", + "dependencies": { + "@grpc/proto-loader": "^0.6.0-pre14", + "@types/node": "^12.12.47", + "google-auth-library": "^6.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.6.0-pre9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.0-pre9.tgz", + "integrity": "sha512-oM+LjpEjNzW5pNJjt4/hq1HYayNeQT+eGrOPABJnYHv7TyNPDNzkQ76rDYZF86X5swJOa4EujEMzQ9iiTdPgww==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.9.0", + "yargs": "^15.3.1" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "node_modules/@grpc/grpc-js/node_modules/@types/node": { + "version": "12.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.9.tgz", + "integrity": "sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q==" + }, + "node_modules/@grpc/grpc-js/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@grpc/grpc-js/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/@grpc/grpc-js/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@grpc/grpc-js/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==" + }, + "node_modules/@grpc/grpc-js/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==" + }, + "node_modules/@grpc/grpc-js/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==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/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==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/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==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", + "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + "hasInstallScript": true, + "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.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/@types/node": { + "version": "13.13.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", + "integrity": "sha512-ctzZJ+XsmHQwe3xp07gFUq4JxBaRSYzKHPgblR76//UanGST7vfFNF0+ty5eEbgTqsENopzoDK090xlha9dccQ==" + }, + "node_modules/@grpc/grpc-js/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@grpc/grpc-js/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "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.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/grpc-js/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.1.tgz", + "integrity": "sha512-3y0FhacYAwWvyXshH18eDkUI40wT/uGio7MAegzY8lO5+wVsc19+1A7T0pPptae4kl7bdITL+0cHpnAPmryBjQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + }, + "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, + "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/load-nyc-config/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "node_modules/@manifoldco/swagger-to-ts": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@manifoldco/swagger-to-ts/-/swagger-to-ts-2.1.0.tgz", + "integrity": "sha512-IH0FAHhwWHR3Gs3rnVHNEscZujGn+K6/2Zu5cWfZre3Vz2tx1SvvJKEbSM89MztfDDRjOpb+6pQD/vqdEoTBVg==", + "deprecated": "This package has changed to openapi-typescript", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "js-yaml": "^3.13.1", + "meow": "^7.0.0", + "prettier": "^2.0.5" + }, + "bin": { + "swagger-to-ts": "bin/cli.js" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@manifoldco/swagger-to-ts/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@manifoldco/swagger-to-ts/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": 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/@manifoldco/swagger-to-ts/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@manifoldco/swagger-to-ts/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==", + "dev": true + }, + "node_modules/@manifoldco/swagger-to-ts/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manifoldco/swagger-to-ts/node_modules/prettier": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", + "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@manifoldco/swagger-to-ts/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.11.0.tgz", + "integrity": "sha512-K+1ADLMxduhsXoZ0GRfi9Pw162FvzBQLDQlHru1lg86rpIU+4XqdJkSGo6y3Kg+GmOWq1HNHOA/ydw/rzHQkRg==", + "dependencies": { + "@opentelemetry/context-base": "^0.11.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-base": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.11.0.tgz", + "integrity": "sha512-ESRk+572bftles7CVlugAj5Azrz61VO0MO0TS2pE9MLVL/zGmWuUBQryART6/nsrFqo+v9HPt37GPNcECTZR1w==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-0.11.0.tgz", + "integrity": "sha512-ZEKjBXeDGBqzouz0uJmrbEKNExEsQOhsZ3tJDCLcz5dUNoVw642oIn2LYWdQK2YdIfZbEmltiF65/csGsaBtFA==", + "dependencies": { + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "semver": "^7.1.3" + }, + "engines": { + "node": ">=8.5.0" + } + }, + "node_modules/@opentelemetry/core/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-0.11.0.tgz", + "integrity": "sha512-o7DwV1TcezqBtS5YW2AWBcn01nVpPptIbTr966PLlVBcS//w8LkjeOShiSZxQ0lmV4b2en0FiSouSDoXk/5qIQ==", + "dependencies": { + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/core": "^0.11.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.11.0.tgz", + "integrity": "sha512-xsthnI/J+Cx0YVDGgUzvrH0ZTtfNtl866M454NarYwDrc0JvC24sYw+XS5PJyk2KDzAHtb0vlrumUc1OAut/Fw==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/tracing": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/tracing/-/tracing-0.11.0.tgz", + "integrity": "sha512-QweFmxzl32BcyzwdWCNjVXZT1WeENNS/RWETq/ohqu+fAsTcMyGcr6cOq/yDdFmtBy+bm5WVVdeByEjNS+c4/w==", + "deprecated": "Package renamed to @opentelemetry/sdk-trace-base", + "dependencies": { + "@opentelemetry/api": "^0.11.0", + "@opentelemetry/context-base": "^0.11.0", + "@opentelemetry/core": "^0.11.0", + "@opentelemetry/resources": "^0.11.0", + "@opentelemetry/semantic-conventions": "^0.11.0" + }, + "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": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "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==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "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": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/archiver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.0.tgz", + "integrity": "sha512-baFOhanb/hxmcOd1Uey2TfFg43kTSmM6py1Eo7Rjbv/ivcl7PXLhY0QgXGf50Hx/eskGCFqPfhs/7IZLb15C5g==", + "dependencies": { + "@types/glob": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", + "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", + "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==", + "dev": true + }, + "node_modules/@types/chai-as-promised": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz", + "integrity": "sha512-FQnh1ohPXJELpKhzjuDkPLR2BZCAqed+a6xV4MI/T3XzHfd2FlarfUGUdZYgqYe8oxkYn0fchHEeHfHqdZ96sg==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/cjson": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@types/cjson/-/cjson-0.5.0.tgz", + "integrity": "sha512-fZdrvfhUxvBDQ5+mksCUvUE+nLXwG416gz+iRdYGDEsQQD5mH0PeLzH0ACuRPbobpVvzKjDHo9VYpCKb1EwLIw==", + "dev": true + }, + "node_modules/@types/cli-color": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@types/cli-color/-/cli-color-0.3.29.tgz", + "integrity": "sha1-yDpx/gLIx+HM7ASN1qJFjR9sluo=", + "dev": true + }, + "node_modules/@types/cli-table": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.0.tgz", + "integrity": "sha512-QnZUISJJXyhyD6L1e5QwXDV/A5i2W1/gl6D6YMc8u0ncPepbv/B4w3S+izVvtAg60m6h+JP09+Y/0zF2mojlFQ==", + "dev": true + }, + "node_modules/@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "node_modules/@types/configstore": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/configstore/-/configstore-4.0.0.tgz", + "integrity": "sha512-SvCBBPzOIe/3Tu7jTl2Q8NjITjLmq9m7obzjSyb8PXWWZ31xVK6w4T6v8fOx+lrgQnqk3Yxc00LDolFsSakKCA==", + "dev": true + }, + "node_modules/@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "dev": true + }, + "node_modules/@types/cross-spawn": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.1.tgz", + "integrity": "sha512-MtN1pDYdI6D6QFDzy39Q+6c9rl2o/xN7aWGe6oZuzqq5N6+YuwFsWiEAv3dNzvzN9YzU+itpN8lBzFpphQKLAw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/dotenv": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", + "integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/duplexify": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", + "integrity": "sha512-5zOA53RUlzN74bvrSGwjudssD9F3a797sDZQkiYpUOxW+WHaXTCPz4/d5Dgi6FKnOqZ2CpaTo0DhgIfsXAOE/A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "node_modules/@types/express": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.0.tgz", + "integrity": "sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", + "integrity": "sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", + "integrity": "sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dependencies": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/inquirer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.0.3.tgz", + "integrity": "sha512-lBsdZScFMaFYYIE3Y6CWX22B9VeY2NerT1kyU2heTc3u/W6a+Om6Au2q0rMzBrzynN0l4QoABhI0cbNdyz6fDg==", + "dev": true, + "dependencies": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, + "node_modules/@types/js-yaml": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.2.tgz", + "integrity": "sha512-0CFu/g4mDSNkodVwWijdlr8jH7RoplRWNgovjFLEZeT+QEbbZXjBmCe3HwaWheAlCbHwomTwzZoSedeOycABug==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.3.8.tgz", + "integrity": "sha512-g2ke5+AR/RKYpQxd+HJ2yisLHGuOV0uourOcPtKlcT5Zqv4wFg9vKhFpXEztN4H/6Y6RSUKioz/2PTFPP30CTA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" + }, + "node_modules/@types/marked": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.6.5.tgz", + "integrity": "sha512-6kBKf64aVfx93UJrcyEZ+OBM5nGv4RLsI6sR1Ar34bpgvGVRoyTgpxn4ZmtxOM5aDTAaaznYuYUH8bUX3Nk3YA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, + "node_modules/@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, + "node_modules/@types/minipass": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-2.2.0.tgz", + "integrity": "sha512-wuzZksN4w4kyfoOv/dlpov4NOunwutLA/q7uc00xU02ZyUY+aoM5PWIXEKBMnm0NHd4a+N71BMjq+x7+2Af1fg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mocha": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", + "integrity": "sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==", + "dev": true + }, + "node_modules/@types/multer": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.4.tgz", + "integrity": "sha512-wdfkiKBBEMTODNbuF3J+qDDSqJxt50yB9pgDiTcFew7f97Gcc7/sM4HR66ofGgpJPOALWOqKAch4gPyqEXSkeQ==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "10.17.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", + "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==" + }, + "node_modules/@types/node-fetch": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", + "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "node_modules/@types/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-bPOsfCZ4tsTlKiBjBhKnM8jpY5nmIll166IPD58D92hR7G7kZDfx5iB9wGF4NfZrdKolebjeAr3GouYkSGoJ/A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/puppeteer": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.2.tgz", + "integrity": "sha512-yjbHoKjZFOGqA6bIEI2dfBE5UPqU0YGWzP+ipDVP1iGzmlhksVKTBVZfT3Aj3wnvmcJ2PQ9zcncwOwyavmafBw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "node_modules/@types/request": { + "version": "2.48.2", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.2.tgz", + "integrity": "sha512-gP+PSFXAXMrd5PcD7SqHeUjdGshAI8vKQ3+AvpQr3ht9iQea+59LOKvKITcQI+Lg+1EIkDP6AFSBUJPWG8GDyA==", + "dev": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.0.tgz", + "integrity": "sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@types/rimraf": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.3.tgz", + "integrity": "sha512-dZfyfL/u9l/oi984hEXdmAjX3JHry7TLWw43u1HQ8HhPv6KtfxnrZ3T/bleJ0GEvnk9t5sM7eePkgMqz3yBcGg==", + "dev": true, + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz", + "integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==", + "dev": true + }, + "node_modules/@types/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "node_modules/@types/sinon": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.10.tgz", + "integrity": "sha512-/faDC0erR06wMdybwI/uR8wEKV/E83T0k4sepIpB7gXuy2gzx2xiOjmztq6a2Y6rIGJ04D+6UU0VBmWy+4HEMA==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinon-chai": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.2.tgz", + "integrity": "sha512-5zSs2AslzyPZdOsbm2NRtuSNAI2aTWzNKOHa/GRecKo7a5efYD7qGcPxMZXQDayVXT2Vnd5waXxBvV31eCZqiA==", + "dev": true, + "dependencies": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", + "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", + "dev": true + }, + "node_modules/@types/superagent": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.3.tgz", + "integrity": "sha512-vy2licJQwOXrTAe+yz9SCyUVXAkMgCeDq9VHzS5CWJyDU1g6CI4xKb4d5sCEmyucjw5sG0y4k2/afS0iv/1D0Q==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/supertest": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.8.tgz", + "integrity": "sha512-wcax7/ip4XSSJRLbNzEIUVy2xjcBIZZAuSd2vtltQfRK7kxhx5WMHbLHkYdxN3wuQCrwpYrg86/9byDjPXoGMA==", + "dev": true, + "dependencies": { + "@types/superagent": "*" + } + }, + "node_modules/@types/tar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-4.0.3.tgz", + "integrity": "sha512-Z7AVMMlkI8NTWF0qGhC4QIX0zkV/+y0J8x7b/RsHrN0310+YNjoJd8UrApCiGBCWtKjxS9QhNqLi2UJNToh5hA==", + "dev": true, + "dependencies": { + "@types/minipass": "*", + "@types/node": "*" + } + }, + "node_modules/@types/tcp-port-used": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/tcp-port-used/-/tcp-port-used-1.0.0.tgz", + "integrity": "sha512-UbspV5WZNhfM55HyvLEFyVc5n6K6OKuKep0mzvsgoUXQU1FS42GbePjreBnTCoKXfNzK/3/RJVCRlUDTuszFPg==", + "dev": true + }, + "node_modules/@types/through": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.29.tgz", + "integrity": "sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==", + "dev": true + }, + "node_modules/@types/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-tl34wMtk3q+fSdRSJ+N83f47IyXLXPPuLjHm7cmAx0fE2Wml2TZCQV3FmQdSR5J6UEGV3qafG054e0cVVFCqPA==", + "dev": true + }, + "node_modules/@types/unzipper": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.0.tgz", + "integrity": "sha512-GZL5vt0o9ZAST+7ge1Sirzc14EEJFbq6kib24nS0UglY6BHX8ERhA8cBq4XsYWcGK212FtMBZyJz6AwPvrhGLQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "dev": true + }, + "node_modules/@types/winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", + "deprecated": "This is a stub types definition. winston provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "winston": "*" + } + }, + "node_modules/@types/ws": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.3.tgz", + "integrity": "sha512-VT/GK7nvDA7lfHy40G3LKM+ICqmdIsBLBHGXcWD97MtqQEjNMX+7Gudo8YGpaSlYdTX7IFThhCE8Jx09HegymQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.12.0.tgz", + "integrity": "sha512-wHKj6q8s70sO5i39H2g1gtpCXCvjVszzj6FFygneNFyIAxRvNSVz9GML7XpqrB9t7hNutXw+MHnLN/Ih6uyB8Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "4.12.0", + "@typescript-eslint/scope-manager": "4.12.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.12.0.tgz", + "integrity": "sha512-MpXZXUAvHt99c9ScXijx7i061o5HEjXltO+sbYfZAAHxv3XankQkPaNi5myy0Yh0Tyea3Hdq1pi7Vsh0GJb0fA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.12.0", + "@typescript-eslint/types": "4.12.0", + "@typescript-eslint/typescript-estree": "4.12.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.12.0.tgz", + "integrity": "sha512-9XxVADAo9vlfjfoxnjboBTxYOiNY93/QuvcPgsiKvHxW6tOZx1W4TvkIQ2jB3k5M0pbFP5FlXihLK49TjZXhuQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.12.0", + "@typescript-eslint/types": "4.12.0", + "@typescript-eslint/typescript-estree": "4.12.0", + "debug": "^4.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.12.0.tgz", + "integrity": "sha512-QVf9oCSVLte/8jvOsxmgBdOaoe2J0wtEmBr13Yz0rkBNkl5D8bfnf6G4Vhox9qqMIoG7QQoVwd2eG9DM/ge4Qg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.12.0", + "@typescript-eslint/visitor-keys": "4.12.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.12.0.tgz", + "integrity": "sha512-N2RhGeheVLGtyy+CxRmxdsniB7sMSCfsnbh8K/+RUIXYYq3Ub5+sukRCjVE80QerrUBvuEvs4fDhz5AW/pcL6g==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.12.0.tgz", + "integrity": "sha512-gZkFcmmp/CnzqD2RKMich2/FjBTsYopjiwJCroxqHZIY11IIoN0l5lKqcgoAPKHt33H2mAkSfvzj8i44Jm7F4w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.12.0", + "@typescript-eslint/visitor-keys": "4.12.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.12.0.tgz", + "integrity": "sha512-hVpsLARbDh4B9TKYz5cLbcdMIOAoBYgFPCSP9FFS/liSF+b33gVNq8JHY3QGhHNVz85hObvL7BEYLlgx553WCw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.12.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/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==" + }, + "node_modules/ansi-align/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" + }, + "node_modules/anymatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", + "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "optional": true + }, + "node_modules/archiver": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.2.0.tgz", + "integrity": "sha512-QEAKlgQuAtUxKeZB9w5/ggKXh21bZS+dzzuQ0RPBC20qtDCbTyzqmisoeJP46MP39fg4B4IcyvR+yeyEBdblsQ==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.1.4", + "zip-stream": "^4.0.4" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "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" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/args": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", + "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "dev": true, + "dependencies": { + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/args/node_modules/camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/args/node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/as-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/as-array/-/as-array-2.0.0.tgz", + "integrity": "sha1-TwSAXYf4/OjlEbwhCPjl46KH1Uc=" + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/atlassian-openapi": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.8.tgz", + "integrity": "sha512-aecHFJuhu5mUNdVOKbOd17+ZrCnuTw7ZFZBGaMb/fHyqUX3FEVz5e4RRgbvn1EE1+w2vmAUA+vkB9fiOzTjhQA==", + "dev": true, + "dependencies": { + "jsonpointer": "^4.0.1", + "urijs": "^1.18.10" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth-connect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz", + "integrity": "sha1-/bC0OWLKe0BFanwrtI/hc9otISI=" + }, + "node_modules/basic-auth-parser": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2.tgz", + "integrity": "sha1-zp5xp38jwSee7NJlmypGJEwVbkE=", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/better-ajv-errors": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-0.6.7.tgz", + "integrity": "sha512-PYgt/sCzR4aGpyNy5+ViSQ77ognMnWq7745zM+/flYO4/Yisdtp9wDQW2IKCyVYPUxQt3E/b5GBSwfhd1LPdlg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/runtime": "^7.0.0", + "chalk": "^2.4.1", + "core-js": "^3.2.1", + "json-to-ast": "^2.0.3", + "jsonpointer": "^4.0.1", + "leven": "^3.1.0" + }, + "peerDependencies": { + "ajv": "4.11.8 - 6" + } + }, + "node_modules/big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/blakejs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz", + "integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U=" + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" + }, + "node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/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==" + }, + "node_modules/boxen/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==" + }, + "node_modules/boxen/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "engines": { + "node": "*" + } + }, + "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": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz", + "integrity": "sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8=", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffer/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" + } + ] + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, + "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, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 5" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "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" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "dependencies": { + "anymatch": "^3.0.1", + "braces": "^3.0.2", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", + "normalize-path": "^3.0.0", + "readdirp": "^3.1.1" + }, + "engines": { + "node": ">= 8" + }, + "optionalDependencies": { + "fsevents": "^2.0.6" + } + }, + "node_modules/chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", + "dev": true + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "node_modules/cjson": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.3.3.tgz", + "integrity": "sha1-qS2ceG5b+bkwgGMp7gXV0yYbSvo=", + "dependencies": { + "json-parse-helpfulerror": "^1.0.3" + }, + "engines": { + "node": ">= 0.3.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "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" + } + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-spinners": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", + "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-error-fragment": { + "version": "0.0.230", + "resolved": "https://registry.npmjs.org/code-error-fragment/-/code-error-fragment-0.0.230.tgz", + "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "dependencies": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/color-string": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", + "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "dependencies": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.0.1.tgz", + "integrity": "sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-parser": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.6.tgz", + "integrity": "sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/compare-semver": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/compare-semver/-/compare-semver-1.1.0.tgz", + "integrity": "sha1-fAp5onu4C2xplERfgpWCWdPQIVM=", + "dependencies": { + "semver": "^5.0.1" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/compress-commons": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.0.2.tgz", + "integrity": "sha512-qhd32a9xgzmpfoga1VQEiLEwdKZ6Plnpx5UCgIsf89FSolyJ7WnifY4Gtjgv5WR6hWAyRaHxC5MiEhU/38U70A==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "dependencies": { + "mime-db": ">= 1.40.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "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": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/configstore/node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/configstore/node_modules/dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/configstore/node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/configstore/node_modules/make-dir": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/configstore/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/configstore/node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "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": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "node_modules/core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "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==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-env": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", + "integrity": "sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg==", + "dependencies": { + "cross-spawn": "^6.0.5", + "is-windows": "^1.0.0" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/cross-env/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "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" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn/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==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csv-streamify": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/csv-streamify/-/csv-streamify-3.0.4.tgz", + "integrity": "sha1-TLYUxX4/KZzKF7Y/3LStFnd39Ho=", + "dependencies": { + "through2": "2.0.1" + }, + "bin": { + "csv-streamify": "cli.js" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/date-and-time": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.2.tgz", + "integrity": "sha512-EFTCh9zRSEpGPmJaexg7HTuzZHh6cnJj1ui7IGCFNXzd2QdpsNh05Db5TF3xzJm30YN+A8/6xHSuRcQqoc3kFA==", + "dev": true, + "optional": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-freeze": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz", + "integrity": "sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ=" + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, + "node_modules/degenerator": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz", + "integrity": "sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ==", + "dependencies": { + "ast-types": "^0.13.2", + "escodegen": "^1.8.1", + "esprima": "^4.0.0", + "vm2": "^3.9.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "optional": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + }, + "node_modules/diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, + "node_modules/dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "dev": true, + "dependencies": { + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=4.5.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-storage": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", + "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/dotenv": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", + "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "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" + } + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "node_modules/duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "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" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "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==", + "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": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "dependencies": { + "env-variable": "0.0.x" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true, + "optional": true + }, + "node_modules/env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/env-variable": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", + "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" + }, + "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, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es5-ext": { + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + }, + "node_modules/es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.17.0.tgz", + "integrity": "sha512-zJk08MiBgwuGoxes5sSQhOtibZ75pz0J35XTRlZOk9xMffhpA9BTbQZxoXZzOl5zMbleShbGwtw+1kGferfFwQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.2.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^6.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.4", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-google": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", + "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", + "integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "30.7.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.13.tgz", + "integrity": "sha512-YM4WIsmurrp0rHX6XiXQppqKB8Ne5ATiZLJe2+/fkp9l9ExXFr43BbAbjZaVrpCT+tuPYOZ8k1MICARHnURUNQ==", + "dev": true, + "dependencies": { + "comment-parser": "^0.7.6", + "debug": "^4.3.1", + "jsdoctypeparser": "^9.0.0", + "lodash": "^4.17.20", + "regextras": "^0.7.1", + "semver": "^7.3.4", + "spdx-expression-parse": "^3.0.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/eslint-plugin-jsdoc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz", + "integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": 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/eslint/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/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==", + "dev": true + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/events-listener/-/events-listener-1.1.0.tgz", + "integrity": "sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g==" + }, + "node_modules/exegesis": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/exegesis/-/exegesis-2.5.7.tgz", + "integrity": "sha512-Y0gEY3hgoLa80aMUm8rhhlIW3/KWo4uqN5hKJqok2GLh3maZjRLRC+p0gj33Jw3upAOKOXeRgScT5rtRoMyxwQ==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.3", + "ajv": "^6.12.2", + "body-parser": "^1.18.3", + "content-type": "^1.0.4", + "deep-freeze": "0.0.1", + "events-listener": "^1.1.0", + "glob": "^7.1.3", + "json-ptr": "^2.2.0", + "json-schema-traverse": "^1.0.0", + "lodash": "^4.17.11", + "openapi3-ts": "^2.0.1", + "promise-breaker": "^5.0.0", + "pump": "^3.0.0", + "qs": "^6.6.0", + "raw-body": "^2.3.3", + "semver": "^7.0.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">5.0.0" + } + }, + "node_modules/exegesis-express": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/exegesis-express/-/exegesis-express-2.0.0.tgz", + "integrity": "sha512-NKvKBsBa2OvU+1BFpWbz3PzoRMhA9q7/wU2oMmQ9X8lPy/FRatADvhlkGO1zYOMgeo35k1ZLO9ZV0uIs9pPnXg==", + "dependencies": { + "exegesis": "^2.0.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">5.0.0" + } + }, + "node_modules/exegesis/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/exegesis/node_modules/ajv/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/exegesis/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==" + }, + "node_modules/exegesis/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/exegesis/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/exit-code": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/exit-code/-/exit-code-1.0.2.tgz", + "integrity": "sha1-zhZYEcnxF69qX4gpQLlq5/muzDQ=" + }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "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" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.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==" + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob/node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "node_modules/fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fast-url-parser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "node_modules/fastq": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", + "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", + "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", + "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/filesize": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", + "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/firebase": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.24.0.tgz", + "integrity": "sha512-j6jIyGFFBlwWAmrlUg9HyQ/x+YpsPkc/TTkbTyeLwwAJrpAmmEHNPT6O9xtAnMV4g7d3RqLL/u9//aZlbY4rQA==", + "dev": true, + "dependencies": { + "@firebase/analytics": "0.6.0", + "@firebase/app": "0.6.11", + "@firebase/app-types": "0.6.1", + "@firebase/auth": "0.15.0", + "@firebase/database": "0.6.13", + "@firebase/firestore": "1.18.0", + "@firebase/functions": "0.5.1", + "@firebase/installations": "0.4.17", + "@firebase/messaging": "0.7.1", + "@firebase/performance": "0.4.2", + "@firebase/polyfill": "0.3.36", + "@firebase/remote-config": "0.1.28", + "@firebase/storage": "0.3.43", + "@firebase/util": "0.3.2" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/firebase-admin": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.4.2.tgz", + "integrity": "sha512-mRnBJbW6BAz6DJkZ0GOUTkmnmCrwVzMreMc6O+RXWukFydOzi5Xr6TKSiPKxoOQw41r9IluP2AZ3Qzvlx2SR+g==", + "dev": true, + "dependencies": { + "@firebase/database": "^0.8.1", + "@firebase/database-types": "^0.6.1", + "@types/node": "^10.10.0", + "dicer": "^0.3.0", + "jsonwebtoken": "^8.5.1", + "node-forge": "^0.10.0" + }, + "engines": { + "node": ">=10.10.0" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^4.5.0", + "@google-cloud/storage": "^5.3.0" + } + }, + "node_modules/firebase-admin/node_modules/@types/node": { + "version": "10.17.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", + "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==", + "dev": true + }, + "node_modules/firebase-functions": { + "version": "3.15.7", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.15.7.tgz", + "integrity": "sha512-ZD7r8eoWWebgs+mTqfH8NLUT2C0f7/cyAvIA1RSUdBVQZN7MBBt3oSlN/rL3e+m6tdlJz6YbQ3hrOKOGjOVYvQ==", + "dev": true, + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "lodash": "^4.17.14" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + }, + "peerDependencies": { + "firebase-admin": "^8.0.0 || ^9.0.0" + } + }, + "node_modules/firebase-functions/node_modules/@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/firebase/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/firebase/node_modules/@firebase/database": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "dev": true, + "dependencies": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "faye-websocket": "0.11.3", + "tslib": "^1.11.1" + } + }, + "node_modules/firebase/node_modules/@firebase/database-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "dev": true, + "dependencies": { + "@firebase/app-types": "0.6.1" + } + }, + "node_modules/firebase/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-arguments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flat-arguments/-/flat-arguments-1.0.2.tgz", + "integrity": "sha1-m6p4Ct8FAfKC1ybJxqA426ROp28=", + "dependencies": { + "array-flatten": "^1.0.0", + "as-array": "^1.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isobject": "^3.0.0" + } + }, + "node_modules/flat-arguments/node_modules/as-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/as-array/-/as-array-1.0.0.tgz", + "integrity": "sha1-KKbu6qVynx9OyiBH316d4avaDtE=", + "dependencies": { + "lodash.isarguments": "2.4.x", + "lodash.isobject": "^2.4.1", + "lodash.values": "^2.4.1" + } + }, + "node_modules/flat-arguments/node_modules/as-array/node_modules/lodash.isarguments": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-2.4.1.tgz", + "integrity": "sha1-STGpwIJTrfCRrnyhkiWKlzh27Mo=" + }, + "node_modules/flat-arguments/node_modules/as-array/node_modules/lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/flat-arguments/node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", + "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "dev": true + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fromentries": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.1.tgz", + "integrity": "sha512-Xu2Qh8yqYuDhQGOhD5iJGninErSfI9A3FrriD3tjUgV5VbJFeH8vfgZ9HnC6jWN80QDVNQK5vmxRAmEAp7Mevw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dependencies": { + "minipass": "^2.6.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/fsevents": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", + "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", + "dependencies": { + "readable-stream": "1.1.x", + "xregexp": "2.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ftp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/ftp/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/ftp/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "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": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "optional": true, + "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" + } + }, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gaxios": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.1.0.tgz", + "integrity": "sha512-vb0to8xzGnA2qcgywAjtshOKKVDf2eQhJoiL6fHhgW5tVN7wNk7egnYIO9zotfn3lQ3De1VPdf7V5/BWfCtCmg==", + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gaxios/node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gcp-metadata": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.0.tgz", + "integrity": "sha512-vQZD57cQkqIA6YPGXM/zc+PIZfNRFdukWGsGZ5+LcJzesi5xp6Gn7a02wRJi4eXPyArNMIYpPET4QMxGqtlk6Q==", + "dependencies": { + "gaxios": "^3.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcp-metadata/node_modules/bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "engines": { + "node": "*" + } + }, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", + "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcp-metadata/node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gcp-metadata/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==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/gcs-resumable-upload": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz", + "integrity": "sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A==", + "dev": true, + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "configstore": "^5.0.0", + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "google-auth-library": "^6.0.0", + "pumpify": "^2.0.0", + "stream-events": "^1.0.4" + }, + "bin": { + "gcs-upload": "build/src/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcs-resumable-upload/node_modules/gaxios": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", + "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", + "dev": true, + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcs-resumable-upload/node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": 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==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "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, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", + "integrity": "sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==", + "dependencies": { + "@tootallnate/once": "1", + "data-uri-to-buffer": "3", + "debug": "4", + "file-uri-to-path": "2", + "fs-extra": "^8.1.0", + "ftp": "^0.3.10" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/get-uri/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "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" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glob-slash/-/glob-slash-1.0.0.tgz", + "integrity": "sha1-/lLvpDMjP3Si/mTHq7m8hIICq5U=" + }, + "node_modules/glob-slasher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glob-slasher/-/glob-slasher-1.0.1.tgz", + "integrity": "sha1-dHoOW7IiZC7hDT4FRD4QlJPLD44=", + "dependencies": { + "glob-slash": "^1.0.0", + "lodash.isobject": "^2.4.1", + "toxic": "^1.0.0" + } + }, + "node_modules/global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "dependencies": { + "ini": "^1.3.5" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "dev": true, + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.3.tgz", + "integrity": "sha512-m9mwvY3GWbr7ZYEbl61isWmk+fvTmOt0YNUfPOUY2VH8K5pZlAIWJjxEi0PqR3OjMretyiQLI6GURMrPSwHQ2g==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/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==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-discovery-to-swagger": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/google-discovery-to-swagger/-/google-discovery-to-swagger-2.1.0.tgz", + "integrity": "sha512-MI1gfmWPkuXCp6yH+9rfd8ZG8R1R5OIyY4WlKDTqr2+ere1gt2Ne4DSEu8HM7NkwKpuVCE5TrTRAPfm3ownMUQ==", + "dev": true, + "dependencies": { + "json-schema-compatibility": "^1.1.0", + "jsonpath": "^1.0.2", + "lodash": "^4.17.15", + "mime-db": "^1.21.0", + "mime-lookup": "^0.0.2", + "traverse": "~0.6.6", + "urijs": "^1.17.0" + } + }, + "node_modules/google-discovery-to-swagger/node_modules/traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "node_modules/google-gax": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.9.2.tgz", + "integrity": "sha512-Pve4osEzNKpBZqFXMfGKBbKCtgnHpUe5IQMh5Ou+Xtg8nLcba94L3gF0xgM5phMdGRRqJn0SMjcuEVmOYu7EBg==", + "dependencies": { + "@grpc/grpc-js": "~1.1.1", + "@grpc/proto-loader": "^0.5.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^6.1.3", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "protobufjs": "^6.9.0", + "retry-request": "^4.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-gax/node_modules/@types/node": { + "version": "13.13.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", + "integrity": "sha512-ctzZJ+XsmHQwe3xp07gFUq4JxBaRSYzKHPgblR76//UanGST7vfFNF0+ty5eEbgTqsENopzoDK090xlha9dccQ==" + }, + "node_modules/google-gax/node_modules/protobufjs": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", + "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + "hasInstallScript": true, + "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.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/google-gax/node_modules/protobufjs/node_modules/@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "node_modules/google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "dependencies": { + "node-forge": "^0.10.0" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "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" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/gtoken": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.1.0.tgz", + "integrity": "sha512-4d8N6Lk8TEAHl9vVoRVMh9BNOKWVgl2DdNtr3428O75r3QFrF/a5MMu851VmK0AA8+iSvbwRv69k5XnMLURGhg==", + "dependencies": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0", + "mime": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/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==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/har-validator/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "optional": true + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "dev": true, + "optional": true + }, + "node_modules/hasha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", + "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha/node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/home-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/home-dir/-/home-dir-1.0.0.tgz", + "integrity": "sha1-KRfrRL3JByztqUJXlUOEfjAX/k4=" + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": 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 + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "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" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/http2-client": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.3.tgz", + "integrity": "sha512-nUxLymWQ9pzkzTmir24p2RtsgruLmhje7lH3hLX1IpwvyTg77fW+1brenPPP3USAR+rQ36p5sTA/x7sjCJVkAA==", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/agent-base": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "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==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/idb": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", + "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==", + "dev": true + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inquirer": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", + "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" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/install-artifact-from-github": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz", + "integrity": "sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA==", + "optional": true, + "bin": { + "install-from-cache": "bin/install-from-cache.js", + "save-to-github-cache": "bin/save-to-github-cache.js" + } + }, + "node_modules/ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, + "node_modules/ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "engines": { + "node": ">=4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "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": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dependencies": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, + "node_modules/is2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz", + "integrity": "sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==", + "dependencies": { + "deep-is": "^0.1.3", + "ip-regex": "^2.1.0", + "is-url": "^1.2.2" + }, + "engines": { + "node": ">=v0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/istanbul-lib-source-maps/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=" + }, + "node_modules/join-path": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/join-path/-/join-path-1.1.1.tgz", + "integrity": "sha1-EFNaEm0ky9Zff/zfFe8uYxB2tQU=", + "dependencies": { + "as-array": "^2.0.0", + "url-join": "0.0.1", + "valid-url": "^1" + } + }, + "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 + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/jsdoctypeparser": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", + "integrity": "sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==", + "dev": true, + "bin": { + "jsdoctypeparser": "bin/jsdoctypeparser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, + "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 + }, + "node_modules/json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", + "dependencies": { + "jju": "^1.1.0" + } + }, + "node_modules/json-ptr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json-ptr/-/json-ptr-2.2.0.tgz", + "integrity": "sha512-w9f6/zhz4kykltXMG7MLJWMajxiPj0q+uzQPR1cggNAE/sXoq/C5vjUb/7QNcC3rJsVIIKy37ALTXy1O+3c8QQ==", + "dependencies": { + "tslib": "^2.2.0" + } + }, + "node_modules/json-ptr/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "node_modules/json-schema-compatibility": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/json-schema-compatibility/-/json-schema-compatibility-1.1.0.tgz", + "integrity": "sha1-GomBd4zaDDgYcpjZmdCJ5Rrygt8=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/json-to-ast": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json-to-ast/-/json-to-ast-2.1.0.tgz", + "integrity": "sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==", + "dev": true, + "dependencies": { + "code-error-fragment": "0.0.230", + "grapheme-splitter": "^1.0.4" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "dev": true, + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsonpath/node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "dev": true + }, + "node_modules/jsonpointer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", + "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "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" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "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==" + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/just-extend": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "dev": true + }, + "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==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "dependencies": { + "colornames": "^1.1.1" + } + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "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" + } + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=" + }, + "node_modules/lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=" + }, + "node_modules/lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "node_modules/lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dependencies": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=" + }, + "node_modules/lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "node_modules/lodash.values": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", + "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "dependencies": { + "lodash.keys": "~2.4.1" + } + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "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" + } + }, + "node_modules/logform/node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/marked": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/marked-terminal": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.3.0.tgz", + "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" + }, + "peerDependencies": { + "marked": "^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "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" + } + }, + "node_modules/meow": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", + "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-lookup": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/mime-lookup/-/mime-lookup-0.0.2.tgz", + "integrity": "sha1-o1JdJixC5MraWFmR+FADil1dJB0=", + "dev": true + }, + "node_modules/mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dependencies": { + "mime-db": "1.40.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dependencies": { + "minipass": "^2.9.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mocha": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", + "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.3", + "debug": "4.2.0", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "nanoid": "3.1.12", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "7.2.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.2", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/mocha/node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mocha/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": 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/mocha/node_modules/chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "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.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" + } + }, + "node_modules/mocha/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/mocha/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==", + "dev": true + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/mocha/node_modules/readdirp/node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mocha/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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mocha/node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mri": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", + "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "node_modules/nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || >=13.7" + } + }, + "node_modules/nash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/nash/-/nash-3.0.0.tgz", + "integrity": "sha512-M5SahEycXUmko3zOvsBkF6p94CWLhnyy9hfpQ9Qzp+rQkQ8D1OaTlfTl1OBWktq9Fak3oDXKU+ev7tiMaMu+1w==", + "dependencies": { + "async": "^1.3.0", + "flat-arguments": "^1.0.0", + "lodash": "^4.17.5", + "minimist": "^1.1.0" + } + }, + "node_modules/nash/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node_modules/nise": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/nock": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.5.tgz", + "integrity": "sha512-1ILZl0zfFm2G4TIeJFW0iHknxr2NyA+aGCMTjDVUsBY4CkMRispF1pfIYkTRdAR/3Bg+UzdEuK0B6HczMQZcCg==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash.set": "^4.3.2", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/nock/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nock/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/node-emoji": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "dependencies": { + "lodash.toarray": "^4.4.0" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "dev": true, + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/node-gyp": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", + "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.3", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "request": "^2.88.2", + "rimraf": "^3.0.2", + "semver": "^7.3.2", + "tar": "^6.0.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-gyp/node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "optional": true + }, + "node_modules/node-gyp/node_modules/minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-gyp/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/node-gyp/node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "optional": true, + "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.3", + "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.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/node-gyp/node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/node-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/node-gyp/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "optional": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha1-271K8SE04uY1wkXvk//Pb2BnOl0=", + "dev": true, + "dependencies": { + "es6-promise": "^3.2.1" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/nyc/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==", + "dev": true + }, + "node_modules/nyc/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==", + "dev": true + }, + "node_modules/nyc/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "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.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "dev": true, + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/oas-linter": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.0.tgz", + "integrity": "sha512-LP5F1dhjULEJV5oGRg6ROztH2FddzttrrUEwq5J2GB2Zy938mg0vwt1+Rthn/qqDHtj4Qgq21duNGHh+Ew1wUg==", + "dev": true, + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.3.tgz", + "integrity": "sha512-y4gP5tabqP3YcNVHNAEJAlcqZ40Y9lxemzmXvt54evbrvuGiK5dEhuE33Rf+191TOwzlxMoIgbwMYeuOM7BwjA==", + "dev": true, + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.7", + "yaml": "^1.10.0", + "yargs": "^16.1.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/oas-resolver/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/oas-resolver/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/oas-resolver/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/oas-resolver/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==", + "dev": true + }, + "node_modules/oas-resolver/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==", + "dev": true + }, + "node_modules/oas-resolver/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/oas-resolver/node_modules/reftools": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.7.tgz", + "integrity": "sha512-I+KZFkQvZjMZqVWxRezTC/kQ2kLhGRZ7C+4ARbgmb5WJbvFUlbrZ/6qlz6mb+cGcPNYib+xqL8kZlxCsSZ7Hew==", + "dev": true, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/oas-resolver/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/oas-resolver/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==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/oas-resolver/node_modules/y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/oas-resolver/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/oas-resolver/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "dev": true, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-4.0.8.tgz", + "integrity": "sha512-bIt8erTyclF7bkaySTtQ9sppqyVc+mAlPi7vPzCLVHJsL9nrivQjc/jHLX/o+eGbxHd6a6YBwuY/Vxa6wGsiuw==", + "dev": true, + "dependencies": { + "ajv": "^5.5.2", + "better-ajv-errors": "^0.6.7", + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.1.3", + "oas-resolver": "^2.4.3", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.5", + "should": "^13.2.1", + "yaml": "^1.8.3" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator/node_modules/ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "node_modules/oas-validator/node_modules/fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "node_modules/oas-validator/node_modules/json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/openapi-merge": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/openapi-merge/-/openapi-merge-1.0.23.tgz", + "integrity": "sha512-5taciN3KUYFXGF3TrlO4LuPIxZW2oWMrzGrgTrO6OIW9RxCQe+Jj1xc6B3iwXdqwGeqfc4EvLFzde5++B36wQg==", + "dev": true, + "dependencies": { + "atlassian-openapi": "^1.0.8", + "lodash": "^4.17.15", + "ts-is-present": "^1.1.1" + } + }, + "node_modules/openapi3-ts": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.1.tgz", + "integrity": "sha512-v6X3iwddhi276siej96jHGIqTx3wzVfMTmpGJEQDt7GPI7pI6sywItURLzpEci21SBRpPN/aOWSF5mVfFVNmcg==", + "dependencies": { + "yaml": "^1.10.0" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "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" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-defer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", + "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/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==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz", + "integrity": "sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4", + "get-uri": "3", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "5", + "pac-resolver": "^5.0.0", + "raw-body": "^2.2.0", + "socks-proxy-agent": "5" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/pac-resolver": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.0.tgz", + "integrity": "sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA==", + "dependencies": { + "degenerator": "^3.0.1", + "ip": "^1.1.5", + "netmask": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": 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==", + "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==", + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "engines": { + "node": ">=4" + } + }, + "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 + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/portfinder": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.23.tgz", + "integrity": "sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ==", + "dependencies": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-breaker": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/promise-breaker/-/promise-breaker-5.0.0.tgz", + "integrity": "sha512-mgsWQuG4kJ1dtO6e/QlNDLFtMkMzzecsC69aI5hlLEjGHFNpHrvGhFi4LiK5jg2SMQj74/diH+wZliL9LpGsyA==" + }, + "node_modules/promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==", + "dev": true + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/protobufjs": { + "version": "6.8.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "hasInstallScript": true, + "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" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/protobufjs/node_modules/@types/node": { + "version": "10.17.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", + "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==" + }, + "node_modules/proxy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.2.tgz", + "integrity": "sha512-KNac2ueWRpjbUh77OAFPZuNdfEqNynm9DD4xHT14CccGpW8wKZwEkN0yjlb7X9G9Z9F55N0Q+1z+WfgAhwYdzQ==", + "dev": true, + "dependencies": { + "args": "5.0.1", + "basic-auth-parser": "0.0.2", + "debug": "^4.1.1" + }, + "bin": { + "proxy": "bin/proxy.js" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-5.0.0.tgz", + "integrity": "sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==", + "dependencies": { + "agent-base": "^6.0.0", + "debug": "4", + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "lru-cache": "^5.1.1", + "pac-proxy-agent": "^5.0.0", + "proxy-from-env": "^1.0.0", + "socks-proxy-agent": "^5.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy-agent/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==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/proxy-agent/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/proxy/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/psl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", + "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "dev": true, + "optional": true, + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/puppeteer": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.0.0.tgz", + "integrity": "sha512-Avu8SKWQRC1JKNMgfpH7d4KzzHOL/A65jRYrjNU46hxnOYGwqe4zZp/JW8qulaH0Pnbm5qyO3EbSKvqBUlfvkg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/puppeteer/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/re2": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/re2/-/re2-1.15.9.tgz", + "integrity": "sha512-AXWEhpMTBdC+3oqbjdU07dk0pBCvxh5vbOMLERL6Y8FYBSGn4vXlLe8cYszn64Yy7H8keVMrgPzoSvOd4mePpg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "install-artifact-from-github": "^1.2.0", + "nan": "^2.14.2", + "node-gyp": "^7.1.2" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/readdirp": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz", + "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==", + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", + "dependencies": { + "esprima": "~4.0.0" + } + }, + "node_modules/reftools": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.6.tgz", + "integrity": "sha512-rQfJ025lvPjw9qyQuNPqE+cRs5qVs7BMrZwgRJnmuMcX/8r/eJE8f5/RCunJWViXKHmN5K2DFafYzglLOHE/tw==", + "dev": true, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "node_modules/regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regextras": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz", + "integrity": "sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==", + "dev": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", + "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "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" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/retry-request": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", + "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", + "dependencies": { + "debug": "^4.1.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/retry-request/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/retry-request/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/router": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/router/-/router-1.3.5.tgz", + "integrity": "sha512-kozCJZUhuSJ5VcLhSb3F8fsmGXy+8HaDbKCAerR1G6tq3mnMZFMuSohbFvGv1c5oMFipijDjRZuuN/Sq5nMf3g==", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/router/node_modules/array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" + }, + "node_modules/router/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "engines": { + "node": "6.* || >= 7.*" + } + }, + "node_modules/run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dependencies": { + "is-promise": "^2.1.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rxjs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/sinon": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.3.tgz", + "integrity": "sha512-m+DyAWvqVHZtjnjX/nuShasykFeiZ+nPuEfD4G3gpvKGkXRhkF/6NSt2qN2FjZhfrcHXFzUzI+NLnk+42fnLEw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.0", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon-chai": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.6.0.tgz", + "integrity": "sha512-bk2h+0xyKnmvazAnc7HE5esttqmCerSMcBtuB2PS2T4tG6x8woXAxZeJaOJWD+8reXHngnXn0RtIbfEW9OTHFg==", + "dev": true, + "peerDependencies": { + "chai": "^4.0.0", + "sinon": ">=4.0.0 <11.0.0" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/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==", + "dev": true + }, + "node_modules/slice-ansi/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", + "dev": true, + "optional": true + }, + "node_modules/socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz", + "integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "4", + "socks": "^2.3.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "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" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "engines": { + "node": "*" + } + }, + "node_modules/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "dev": true, + "dependencies": { + "escodegen": "^1.8.1" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "dev": true, + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", + "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=", + "dependencies": { + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "dev": true, + "optional": true + }, + "node_modules/superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "dependencies": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/superagent/node_modules/readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/superstatic": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-7.1.0.tgz", + "integrity": "sha512-yBU8iw07nM3Bu4jFc8lnKwLey0cj61OaGmFJZcYC2X+kEpXVmXzERJ3OTAHZAESe1OTeNIuWadt81U5IULGGAA==", + "dependencies": { + "basic-auth-connect": "^1.0.0", + "chalk": "^1.1.3", + "compare-semver": "^1.0.0", + "compression": "^1.7.0", + "connect": "^3.6.2", + "destroy": "^1.0.4", + "fast-url-parser": "^1.1.3", + "fs-extra": "^8.1.0", + "glob-slasher": "^1.0.1", + "home-dir": "^1.0.0", + "is-url": "^1.2.2", + "join-path": "^1.1.1", + "lodash": "^4.17.19", + "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.8.0", + "router": "^1.3.1", + "rsvp": "^4.8.5", + "string-length": "^1.0.0", + "update-notifier": "^4.1.1" + }, + "bin": { + "superstatic": "bin/server" + }, + "engines": { + "node": ">= 8.6.0" + }, + "optionalDependencies": { + "re2": "^1.15.8" + } + }, + "node_modules/superstatic/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superstatic/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superstatic/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/superstatic/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==" + }, + "node_modules/superstatic/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/superstatic/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/superstatic/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/superstatic/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/superstatic/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/superstatic/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superstatic/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/superstatic/node_modules/update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "dependencies": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "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", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/superstatic/node_modules/update-notifier/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/superstatic/node_modules/update-notifier/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/superstatic/node_modules/update-notifier/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supertest": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", + "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz", + "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", + "dependencies": { + "has-flag": "^2.0.0", + "supports-color": "^5.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/swagger2openapi": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-6.2.3.tgz", + "integrity": "sha512-cUUktzLpK69UwpMbcTzjMw2ns9RZChfxh56AHv6+hTx3StPOX2foZjPgds3HlJcINbxosYYBn/D3cG8nwcCWwQ==", + "dev": true, + "dependencies": { + "better-ajv-errors": "^0.6.1", + "call-me-maybe": "^1.0.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.4.3", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^4.0.8", + "reftools": "^1.1.5", + "yaml": "^1.8.3", + "yargs": "^15.3.1" + }, + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/swagger2openapi/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/swagger2openapi/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/swagger2openapi/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/swagger2openapi/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==", + "dev": true + }, + "node_modules/swagger2openapi/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==", + "dev": true + }, + "node_modules/swagger2openapi/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "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.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/table": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", + "dev": true, + "dependencies": { + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", + "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/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==", + "dev": true + }, + "node_modules/table/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==", + "dev": true + }, + "node_modules/table/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "4.4.18", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.18.tgz", + "integrity": "sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==", + "dependencies": { + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar/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" + } + ] + }, + "node_modules/tar/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/tcp-port-used": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", + "integrity": "sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q==", + "dependencies": { + "debug": "4.1.0", + "is2": "2.0.1" + } + }, + "node_modules/tcp-port-used/node_modules/debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/tcp-port-used/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "dev": true, + "optional": true, + "dependencies": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/teeny-request/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==", + "dev": true, + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/term-size": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", + "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "node_modules/through2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz", + "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", + "dependencies": { + "readable-stream": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/through2/node_modules/process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "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" + } + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dependencies": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "engines": { + "node": ">=6" + } + }, + "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==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dependencies": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "node_modules/toxic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toxic/-/toxic-1.0.1.tgz", + "integrity": "sha512-WI3rIGdcaKULYg7KVoB0zcjikqvcYYvcuT6D89bFPz2rVR0Rl0PK6x8/X62rtdLtBKIE985NzVf/auTtGegIIg==", + "dependencies": { + "lodash": "^4.17.10" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "engines": { + "node": "*" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "node_modules/ts-is-present": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ts-is-present/-/ts-is-present-1.1.5.tgz", + "integrity": "sha512-7cTV1I0C58HusRxMXTgbAIFu54tB+ZqGX/nf4YuePFiz40NHQbQVBgZSws1No/DJYnGf5Mx26PcyLPol01t5DQ==", + "dev": true + }, + "node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-node/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ts-node/node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tsutils": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.0.tgz", + "integrity": "sha512-A7BaLUPvcQ1cxVu72YfD+UMI3SQPTDv/w4ol6TOwLyI0hwfG9EC+cYlhdflJTmtYTgZ3KqdPSe/otxU4K3kArg==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/tweetsodium": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/tweetsodium/-/tweetsodium-0.0.5.tgz", + "integrity": "sha512-T3aXZtx7KqQbutTtBfn+P5By3HdBuB1eCoGviIrRJV2sXeToxv2X2cv5RvYqgG26PSnN5m3fYixds22Gkfd11w==", + "dependencies": { + "blakejs": "^1.1.0", + "tweetnacl": "^1.0.1" + } + }, + "node_modules/tweetsodium/node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "node_modules/type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", + "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", + "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/typescript-json-schema": { + "version": "0.50.1", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.50.1.tgz", + "integrity": "sha512-GCof/SDoiTDl0qzPonNEV4CHyCsZEIIf+mZtlrjoD8vURCcEzEfa2deRuxYid8Znp/e27eDR7Cjg8jgGrimBCA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.7", + "@types/node": "^14.14.33", + "glob": "^7.1.6", + "json-stable-stringify": "^1.0.1", + "ts-node": "^9.1.1", + "typescript": "~4.2.3", + "yargs": "^16.2.0" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/typescript-json-schema/node_modules/@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "node_modules/typescript-json-schema/node_modules/@types/node": { + "version": "14.17.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz", + "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==", + "dev": true + }, + "node_modules/typescript-json-schema/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript-json-schema/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/typescript-json-schema/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/typescript-json-schema/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/typescript-json-schema/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==", + "dev": true + }, + "node_modules/typescript-json-schema/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==", + "dev": true + }, + "node_modules/typescript-json-schema/node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript-json-schema/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript-json-schema/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript-json-schema/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript-json-schema/node_modules/typescript": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/typescript-json-schema/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==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/typescript-json-schema/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript-json-schema/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript-json-schema/node_modules/yargs-parser": { + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/universal-analytics": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", + "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==", + "dependencies": { + "debug": "^3.0.0", + "request": "^2.88.0", + "uuid": "^3.0.0" + } + }, + "node_modules/universal-analytics/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/universal-analytics/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/universal-analytics/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unzipper": { + "version": "0.10.10", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.10.tgz", + "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" + } + }, + "node_modules/unzipper/node_modules/graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "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" + } + }, + "node_modules/update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dependencies": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/boxen/node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/update-notifier/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "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/update-notifier/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/update-notifier/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==" + }, + "node_modules/update-notifier/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==" + }, + "node_modules/update-notifier/node_modules/global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/update-notifier/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/update-notifier/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/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==", + "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/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urijs": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", + "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==", + "dev": true + }, + "node_modules/url-join": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", + "integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g=" + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "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==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "dev": true + }, + "node_modules/valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vm2": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz", + "integrity": "sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng==", + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", + "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.4.0 <0.4.11", + "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==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", + "dev": true + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/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==" + }, + "node_modules/widest-line/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "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" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "dependencies": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "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" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", + "engines": { + "node": "*" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "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" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.4.tgz", + "integrity": "sha512-a65wQ3h5gcQ/nQGWV1mSZCEzCML6EK/vyVPcrPNynySP1j3VBbQKh3nhC8CbORb+jfl2vXvh56Ul5odP1bAHqw==", + "dependencies": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.0.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + } + }, "dependencies": { "@apidevtools/json-schema-ref-parser": { "version": "9.0.7", @@ -505,13 +14667,15 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", - "dev": true + "dev": true, + "requires": {} }, "@firebase/auth-types": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", - "dev": true + "dev": true, + "requires": {} }, "@firebase/component": { "version": "0.1.21", @@ -589,7 +14753,8 @@ "version": "1.14.0", "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.14.0.tgz", "integrity": "sha512-WF8IBwHzZDhwyOgQnmB0pheVrLNP78A8PGxk1nxb/Nrgh1amo4/zYvFMGgSsTeaQK37xMYS/g7eS948te/dJxw==", - "dev": true + "dev": true, + "requires": {} }, "@firebase/functions": { "version": "0.5.1", @@ -669,7 +14834,8 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==", - "dev": true + "dev": true, + "requires": {} }, "@firebase/logger": { "version": "0.2.6", @@ -716,7 +14882,8 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.5.0.tgz", "integrity": "sha512-QaaBswrU6umJYb/ZYvjR5JDSslCGOH6D9P136PhabFAHLTR4TWjsaACvbBXuvwrfCXu10DtcjMxqfhdNIB1Xfg==", - "dev": true + "dev": true, + "requires": {} }, "@firebase/performance": { "version": "0.4.2", @@ -848,7 +15015,8 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz", "integrity": "sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==", - "dev": true + "dev": true, + "requires": {} }, "@firebase/util": { "version": "0.3.4", @@ -2205,15 +16373,6 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -2247,7 +16406,8 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "dev": true, + "requires": {} }, "agent-base": { "version": "6.0.2", @@ -4162,13 +18322,15 @@ "version": "0.14.0", "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", - "dev": true + "dev": true, + "requires": {} }, "eslint-config-prettier": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", "integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-jsdoc": { "version": "30.7.13", @@ -6286,6 +20448,15 @@ "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", "dev": true }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -9140,7 +23311,8 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.6.0.tgz", "integrity": "sha512-bk2h+0xyKnmvazAnc7HE5esttqmCerSMcBtuB2PS2T4tG6x8woXAxZeJaOJWD+8reXHngnXn0RtIbfEW9OTHFg==", - "dev": true + "dev": true, + "requires": {} }, "slash": { "version": "3.0.0", @@ -9379,6 +23551,14 @@ "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", "dev": true }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", @@ -9421,14 +23601,6 @@ } } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -11056,7 +25228,8 @@ "ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "requires": {} }, "xdg-basedir": { "version": "4.0.0", From b97dc4abe8f58af0cbb93f8be3aafb49d9a60f46 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 16 Dec 2021 21:34:00 +0000 Subject: [PATCH 0004/1699] 10.0.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82256e08937..e4ee035487e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.0.0", + "version": "10.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.0.0", + "version": "10.0.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.7.0", diff --git a/package.json b/package.json index 7973ef254e8..2ca5fc42bde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.0.0", + "version": "10.0.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 428ba3c422304409c37703e41bdac829d6788c8d Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 16 Dec 2021 21:34:46 +0000 Subject: [PATCH 0005/1699] [firebase-release] Removed change log and reset repo after 10.0.1 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92ca9b39ecd..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Upgrades Database Emulator to v4.7.3, removing log4j dependency. From c06838228684f5dd88351298671a7be4e1081b1c Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 16 Dec 2021 13:59:08 -0800 Subject: [PATCH 0006/1699] upgrade npm in node12 container for building (#3958) --- scripts/build/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/build/Dockerfile b/scripts/build/Dockerfile index 34ca05ae257..dd24e6621cc 100644 --- a/scripts/build/Dockerfile +++ b/scripts/build/Dockerfile @@ -7,3 +7,6 @@ RUN apt-get update && \ # Install hub RUN curl -fsSL --output hub.tgz https://github.com/github/hub/releases/download/v2.14.2/hub-linux-amd64-2.14.2.tgz RUN tar --strip-components=2 -C /usr/bin -xf hub.tgz hub-linux-amd64-2.14.2/bin/hub + +# Upgrade npm to 8. +RUN npm install --global npm@8 From 6ecb7916f220558e4839772f667451e6551185d8 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Thu, 23 Dec 2021 10:36:52 -0500 Subject: [PATCH 0007/1699] New provider metrics command for extension providers (#3938) * Bootstrap producer stats command and APIs (#724) * MVP for reading from cloud monitoring * Parse result into into meaningful buckets * Format * Reorganize to be more testable * Add some tests * format * api test * Add metricsUtil tests * Cleanup console logs * Refactor cloud monitoring to a general api under gcp/ * cleanup console logs * Fetch project number from getPublisherProfile in ext:dev:usage (#725) * Rename to metricsTypeDef * Add help to get publisher profile * Add tests * Provide more info in error message * format * Render metrics output in table format * Render output in a table * align right * adjust table output * Format * Add tests * Handle all versions when parsing cloud monitoring response (#727) * Add interactive option for ext:dev:usage command (#728) * Handle all versions when parsing cloud monitoring response * Add interactive option for ext:dev:usage command * Update ext-dev-usage.ts * Add a link to patheon at the end of ext:dev:usage command (#729) * Add a link to pantheon in extension:dev:usage * Update ext-dev-usage.ts * Format * Update helper text at end of ext:dev:usage command (#731) * Update text at end of ext:dev:usage command * format * Add back log that's accidentally deleted * format * Update text copy * Improve deep link by specifying more params * Address bugbash UX feedback for provider metrics (#733) * Address bugbash UX feedback * Fix tests * Fix typo * Fix typo 2 * Address PR feedback --- src/api.js | 4 + src/commands/ext-dev-usage.ts | 178 +++++++++ src/commands/index.js | 1 + src/extensions/extensionsApi.ts | 21 + src/extensions/extensionsHelper.ts | 13 + src/extensions/metricsTypeDef.ts | 32 ++ src/extensions/metricsUtils.ts | 134 +++++++ src/gcp/cloudmonitoring.ts | 152 ++++++++ src/test/extensions/extensionsApi.spec.ts | 32 ++ src/test/extensions/metricsUtils.spec.ts | 446 ++++++++++++++++++++++ src/test/gcp/cloudmonitoring.spec.ts | 48 +++ 11 files changed, 1061 insertions(+) create mode 100644 src/commands/ext-dev-usage.ts create mode 100644 src/extensions/metricsTypeDef.ts create mode 100644 src/extensions/metricsUtils.ts create mode 100644 src/gcp/cloudmonitoring.ts create mode 100644 src/test/extensions/metricsUtils.spec.ts create mode 100644 src/test/gcp/cloudmonitoring.spec.ts diff --git a/src/api.js b/src/api.js index ef40a7f190b..0419a19391c 100644 --- a/src/api.js +++ b/src/api.js @@ -99,6 +99,10 @@ var api = { "FIREBASE_CLOUDLOGGING_URL", "https://logging.googleapis.com" ), + cloudMonitoringOrigin: utils.envOverride( + "CLOUD_MONITORING_URL", + "https://monitoring.googleapis.com" + ), containerRegistryDomain: utils.envOverride("CONTAINER_REGISTRY_DOMAIN", "gcr.io"), artifactRegistryDomain: utils.envOverride( "ARTIFACT_REGISTRY_DOMAIN", diff --git a/src/commands/ext-dev-usage.ts b/src/commands/ext-dev-usage.ts new file mode 100644 index 00000000000..70989df61ad --- /dev/null +++ b/src/commands/ext-dev-usage.ts @@ -0,0 +1,178 @@ +import Table = require("cli-table"); +import * as clc from "cli-color"; +import * as utils from "../utils"; +import { Command } from "../command"; +import { Aligner, CmQuery, queryTimeSeries, TimeSeriesView } from "../gcp/cloudmonitoring"; +import { requireAuth } from "../requireAuth"; +import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; +import { buildMetricsTableRow, parseTimeseriesResponse } from "../extensions/metricsUtils"; +import { getPublisherProfile, listExtensions } from "../extensions/extensionsApi"; +import { getPublisherProjectFromName, logPrefix } from "../extensions/extensionsHelper"; +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import { promptOnce } from "../prompt"; +import { shortenUrl } from "../shortenUrl"; + +module.exports = new Command("ext:dev:usage ") + .description("get usage for an extension") + .help( + "use this command to get the usage of extensions you published. " + + "Specify the publisher ID you used to publish your extensions, " + + "or the extension ref of your published extension." + ) + .before(requireAuth) + .before(checkMinRequiredVersion, "extDevMinVersion") + .action(async (input: string) => { + const extensionRefRegex = /^[\w\d-]+\/[\w\d-]+$/; + + let extensionName; + let publisherId; + if (extensionRefRegex.test(input)) { + [publisherId, extensionName] = input.split("/"); + } else { + // If input doesn't match extensionRef regex then treat it as a publisher ID. + // We use the interactive flow to let users choose which extension to show stats for. + publisherId = input; + + let extensions; + try { + extensions = await listExtensions(publisherId); + } catch (err) { + throw new FirebaseError(err); + } + + if (extensions.length < 1) { + throw new FirebaseError( + `There are no published extensions associated with publisher ID ${clc.bold( + publisherId + )}. This could happen for two reasons:\n` + + " - The publisher ID doesn't exist or could be misspelled\n" + + " - This publisher has not published any extensions\n\n" + + "If you are expecting some extensions to appear, please make sure you have the correct publisher ID and try again." + ); + } + + extensionName = await promptOnce({ + type: "list", + name: "extension", + message: "Which published extension do you want to view the stats for?", + choices: extensions.map((e) => { + const [_, name] = e.ref.split("/"); + return { + name, + value: name, + }; + }), + }); + } + + const profile = await getPublisherProfile("-", publisherId); + + const projectNumber = getPublisherProjectFromName(profile.name); + + const past30d = new Date(); + past30d.setDate(past30d.getDate() - 30); + + const query: CmQuery = { + filter: + `metric.type="firebaseextensions.googleapis.com/extension/version/active_instances" ` + + `resource.type="firebaseextensions.googleapis.com/ExtensionVersion" ` + + `resource.labels.extension="${extensionName}"`, + "interval.endTime": new Date().toJSON(), + "interval.startTime": past30d.toJSON(), + view: TimeSeriesView.FULL, + "aggregation.alignmentPeriod": (60 * 60 * 24).toString() + "s", + "aggregation.perSeriesAligner": Aligner.ALIGN_MAX, + }; + + let response; + try { + response = await queryTimeSeries(query, projectNumber); + } catch (err) { + throw new FirebaseError( + `Error occurred when fetching usage data for extension ${extensionName}`, + { + original: err, + } + ); + } + if (!response) { + throw new FirebaseError(`Couldn't find any usage data for extension ${extensionName}`); + } + + const metrics = parseTimeseriesResponse(response); + + const table = new Table({ + head: ["Version", "Active Instances", "Changes last 7 Days", "Changes last 28 Days"], + style: { + head: ["yellow"], + }, + colAligns: ["left", "right", "right", "right"], + }); + metrics.forEach((m) => { + table.push(buildMetricsTableRow(m)); + }); + + utils.logLabeledBullet(logPrefix, `showing usage stats for ${clc.bold(extensionName)}:`); + + logger.info(table.toString()); + + const link = await buildCloudMonitoringLink({ + projectNumber: projectNumber, + extensionName, + }); + + utils.logLabeledBullet(logPrefix, `How to read this table:`); + logger.info(`* Due to privacy considerations, numbers are reported as ranges.`); + logger.info(`* In the absence of significant changes, we will render a '-' symbol.`); + logger.info( + `* You will need more than 10 installs over a period of more than 28 days to render sufficient data.` + ); + logger.info(`For more detail, visit: ${link}`); + }); + +async function buildCloudMonitoringLink(args: { + projectNumber: number; + extensionName: string; +}): Promise { + // This JSON can be exported from the cloud monitoring page. + const pageState = { + xyChart: { + dataSets: [ + { + timeSeriesFilter: { + filter: + `metric.type="firebaseextensions.googleapis.com/extension/version/active_instances"` + + ` resource.type="firebaseextensions.googleapis.com/ExtensionVersion"` + + ` resource.label.extension="${args.extensionName}"`, + minAlignmentPeriod: "86400s", + aggregations: [ + { + perSeriesAligner: "ALIGN_MEAN", + crossSeriesReducer: "REDUCE_MAX", + alignmentPeriod: "86400s", + groupByFields: ['resource.label."extension"', 'resource.label."version"'], + }, + { + crossSeriesReducer: "REDUCE_NONE", + alignmentPeriod: "60s", + groupByFields: [], + }, + ], + }, + }, + ], + }, + isAutoRefresh: true, + timeSelection: { + timeRange: "4w", + }, + }; + + let uri = + `https://console.cloud.google.com/monitoring/metrics-explorer?project=${args.projectNumber}` + + `&pageState=${JSON.stringify(pageState)}`; + uri = encodeURI(uri); + uri = await shortenUrl(uri); + return uri; +} diff --git a/src/commands/index.js b/src/commands/index.js index a740c73af9c..2c9feb79662 100644 --- a/src/commands/index.js +++ b/src/commands/index.js @@ -87,6 +87,7 @@ module.exports = function (client) { client.ext.dev.unpublish = loadCommand("ext-dev-unpublish"); client.ext.dev.publish = loadCommand("ext-dev-publish"); client.ext.dev.delete = loadCommand("ext-dev-extension-delete"); + client.ext.dev.usage = loadCommand("ext-dev-usage"); } client.firestore = {}; client.firestore.delete = loadCommand("firestore-delete"); diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index 5ec59b7a24a..df1ce1eba33 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -3,6 +3,7 @@ import * as _ from "lodash"; import * as clc from "cli-color"; import * as marked from "marked"; import * as api from "../api"; +import * as apiv2 from "../apiv2"; import * as refs from "./refs"; import { logger } from "../logger"; import * as operationPoller from "../operation-poller"; @@ -601,6 +602,26 @@ export async function listExtensionVersions( return extensionVersions; } +/** + * @param projectId the project for which we are registering a PublisherProfile + * @param publisherId the desired publisher ID + */ +export async function getPublisherProfile( + projectId: string, + publisherId?: string +): Promise { + const client = new apiv2.Client({ urlPrefix: api.extensionsOrigin }); + const res = await client.get(`/${VERSION}/projects/${projectId}/publisherProfile`, { + queryParams: + publisherId == undefined + ? undefined + : { + publisherId, + }, + }); + return res.body as PublisherProfile; +} + /** * @param projectId the project for which we are registering a PublisherProfile * @param publisherId the desired publisher ID diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 47c24b0e602..85630e09c88 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -585,6 +585,19 @@ export async function getExtensionSourceFromName(extensionName: string): Promise throw new FirebaseError(`Could not find an extension named '${extensionName}'. `); } +/** + * Parses the publisher project number from publisher profile name. + */ +export function getPublisherProjectFromName(publisherName: string): number { + const publisherNameRegex = /projects\/.+\/publisherProfile/; + + if (publisherNameRegex.test(publisherName)) { + const [_, projectNumber, __] = publisherName.split("/"); + return Number.parseInt(projectNumber); + } + throw new FirebaseError(`Could not find publisher with name '${publisherName}'.`); +} + /** * Confirm the version number in extension.yaml with the user . * diff --git a/src/extensions/metricsTypeDef.ts b/src/extensions/metricsTypeDef.ts new file mode 100644 index 00000000000..4e0a5c6f100 --- /dev/null +++ b/src/extensions/metricsTypeDef.ts @@ -0,0 +1,32 @@ +import * as refs from "./refs"; + +/** + * Interface for representing a metric to be rendered by the extension's CLI. + */ +export interface BucketedMetric { + ref: refs.Ref; + valueToday: Bucket | undefined; + value7dAgo: Bucket | undefined; + value28dAgo: Bucket | undefined; +} + +/** + * Bucket is the range that a raw number falls under. + * + * Valid bucket sizes are: + * 0 + * 0 - 10 + * 10 - 20 + * 20 - 30 + * ... + * 90 - 100 + * 100 - 200 + * 200 - 300 + * every 100... + * + * Note the buckets overlaps intentionally as a UX-optimization. + */ +export interface Bucket { + low: number; + high: number; +} diff --git a/src/extensions/metricsUtils.ts b/src/extensions/metricsUtils.ts new file mode 100644 index 00000000000..1667b89d141 --- /dev/null +++ b/src/extensions/metricsUtils.ts @@ -0,0 +1,134 @@ +import * as semver from "semver"; +import { TimeSeries, TimeSeriesResponse } from "../gcp/cloudmonitoring"; +import { Bucket, BucketedMetric } from "./metricsTypeDef"; +import * as refs from "./refs"; +import * as clc from "cli-color"; + +/** + * Parse TimeSeriesResponse into structured metric data. + */ +export function parseTimeseriesResponse(series: TimeSeriesResponse): Array { + const ret: BucketedMetric[] = []; + for (const s of series) { + const ref = buildRef(s); + + if (ref == undefined) { + // Skip if data point has no valid ref. + continue; + } + + let valueToday: Bucket | undefined; + let value7dAgo: Bucket | undefined; + let value28dAgo: Bucket | undefined; + + // Extract significant data points and convert them to buckets. + if (s.points.length >= 28 && s.points[27].value.int64Value != undefined) { + value28dAgo = parseBucket(s.points[27].value.int64Value); + } + if (s.points.length >= 7 && s.points[6].value.int64Value != undefined) { + value7dAgo = parseBucket(s.points[6].value.int64Value); + } + if (s.points.length >= 1 && s.points[0].value.int64Value != undefined) { + valueToday = parseBucket(s.points[0].value.int64Value); + } + + ret.push({ + ref, + valueToday, + value7dAgo, + value28dAgo, + }); + } + + ret.sort((a, b) => { + if (a.ref.version === "all") { + return 1; + } + if (b.ref.version === "all") { + return -1; + } + return semver.lt(a.ref.version!, b.ref.version!) ? 1 : -1; + }); + return ret; +} + +/** + * Converts a single number back into a range bucket that the raw number falls under. + * + * The reverse side of the logic lives here: + * https://source.corp.google.com/piper///depot/google3/firebase/mods/jobs/metrics/buckets.go + * + * @param v Value got from Cloud Monitoring, which is the upper-bound of the bucket. + */ +export function parseBucket(value: number): Bucket { + // int64Value has type "number" but can still be interupted as "string" sometimes. + // Force cast into number just in case. + const v = Number(value); + + if (v >= 200) { + return { low: v - 100, high: v }; + } + if (v >= 10) { + return { low: v - 10, high: v }; + } + return { low: 0, high: 0 }; +} + +/** + * Build a row in the metrics table given a bucketed metric. + */ +export function buildMetricsTableRow(metric: BucketedMetric): Array { + const ret: string[] = [metric.ref.version!]; + + if (metric.valueToday) { + ret.push(`${metric.valueToday.low} - ${metric.valueToday.high}`); + } else { + ret.push("Insufficient data"); + } + + ret.push(renderChangeCell(metric.value7dAgo, metric.valueToday)); + + ret.push(renderChangeCell(metric.value28dAgo, metric.valueToday)); + + return ret; +} + +function renderChangeCell(before: Bucket | undefined, after: Bucket | undefined) { + if (!(before && after)) { + return "Insufficient data"; + } + if (before.high === after.high) { + return "-"; + } + + if (before.high > after.high) { + const diff = before.high - after.high; + const tolerance = diff < 100 ? 10 : 100; + return clc.red("▼ ") + `-${diff} (±${tolerance})`; + } else { + const diff = after.high - before.high; + const tolerance = diff < 100 ? 10 : 100; + return clc.green("▲ ") + `${diff} (±${tolerance})`; + } +} + +/** + * Build an extension ref from a Cloud Monitoring's TimeSeries. + * + * Return null if resource labels are malformed. + */ +function buildRef(ts: TimeSeries): refs.Ref | undefined { + const publisherId = ts.resource.labels["publisher"]; + const extensionId = ts.resource.labels["extension"]; + const version = ts.resource.labels["version"]; + + if (!(publisherId && extensionId && version)) { + return undefined; + } + + return { + publisherId, + extensionId, + version, + }; +} diff --git a/src/gcp/cloudmonitoring.ts b/src/gcp/cloudmonitoring.ts new file mode 100644 index 00000000000..93f6d92cacf --- /dev/null +++ b/src/gcp/cloudmonitoring.ts @@ -0,0 +1,152 @@ +import * as api from "../api"; +import { FirebaseError } from "../error"; + +export const CLOUD_MONITORING_VERSION = "v3"; + +/** + * Content of this file is borrowed from Cloud monitoring console's source code. + * https://source.corp.google.com/piper///depot/google3/java/com/google/firebase/console/web/components/cloud_monitoring/typedefs.ts + */ + +/** Query from v3 Cloud Monitoring API */ +export interface CmQuery { + filter: string; + "interval.startTime"?: string; + "interval.endTime"?: string; + "aggregation.alignmentPeriod"?: string; + "aggregation.perSeriesAligner"?: Aligner; + "aggregation.crossSeriesReducer"?: Reducer; + "aggregation.groupByFields"?: string; + orderBy?: string; + pageSize?: number; + pageToken?: string; + view?: TimeSeriesView; +} + +/** + * Controls which fields are returned by ListTimeSeries. + */ +export enum TimeSeriesView { + FULL = "FULL", + HEADERS = "HEADERS", +} + +/** + * The Aligner describes how to bring the data points in a single time series + * into temporal alignment. + */ +export enum Aligner { + ALIGN_NONE = "ALIGN_NONE", + ALIGN_DELTA = "ALIGN_DELTA", + ALIGN_RATE = "ALIGN_RATE", + ALIGN_INTERPOLATE = "ALIGN_INTERPOLATE", + ALIGN_NEXT_OLDER = "ALIGN_NEXT_OLDER", + ALIGN_MIN = "ALIGN_MIN", + ALIGN_MAX = "ALIGN_MAX", + ALIGN_MEAN = "ALIGN_MEAN", + ALIGN_COUNT = "ALIGN_COUNT", + ALIGN_SUM = "ALIGN_SUM", + ALIGN_STDDEV = "ALIGN_STDDEV", + ALIGN_COUNT_TRUE = "ALIGN_COUNT_TRUE", + ALIGN_FRACTION_TRUE = "ALIGN_FRACTION_TRUE", +} + +export enum MetricKind { + METRIC_KIND_UNSPECIFIED = "METRIC_KIND_UNSPECIFIED", + GAUGE = "GAUGE", + DELTA = "DELTA", + CUMULATIVE = "CUMULATIVE", +} + +/** + * A Reducer describes how to aggregate data points from multiple time series + * into a single time series. + */ +export enum Reducer { + REDUCE_NONE = "REDUCE_NONE", + REDUCE_MEAN = "REDUCE_MEAN", + REDUCE_MIN = "REDUCE_MIN", + REDUCE_MAX = "REDUCE_MAX", + REDUCE_SUM = "REDUCE_SUM", + REDUCE_STDDEV = "REDUCE_STDDEV", + REDUCE_COUNT = "REDUCE_COUNT", + REDUCE_COUNT_TRUE = "REDUCE_COUNT_TRUE", + REDUCE_FRACTION_TRUE = "REDUCE_FRACTION_TRUE", + REDUCE_PERCENTILE_99 = "REDUCE_PERCENTILE_99", + REDUCE_PERCENTILE_95 = "REDUCE_PERCENTILE_95", + REDUCE_PERCENTILE_50 = "REDUCE_PERCENTILE_50", + REDUCE_PERCENTILE_05 = "REDUCE_PERCENTILE_05", +} + +/** TimeSeries from v3 Cloud Monitoring API */ +export interface TimeSeries { + metric: Metric; + metricKind: MetricKind; + points: Point[]; + resource: Resource; + valueType: ValueType; +} +export type TimeSeriesResponse = TimeSeries[]; + +/** Resource from v3 Cloud Monitoring API */ +export interface Resource { + labels: { [key: string]: string }; + type: string; +} +export type Metric = Resource; + +/** Point from v3 Cloud Monitoring API */ +export interface Point { + interval: Interval; + value: TypedValue; +} + +/** Interval from v3 Cloud Monitoring API */ +export interface Interval { + endTime: string; + startTime: string; +} + +/** TypedValue from v3 Cloud Monitoring API */ +export interface TypedValue { + boolValue?: boolean; + int64Value?: number; + doubleValue?: number; + stringValue?: string; +} + +/** + * The value type of a metric. + */ +export enum ValueType { + VALUE_TYPE_UNSPECIFIED = "VALUE_TYPE_UNSPECIFIED", + BOOL = "BOOL", + INT64 = "INT64", + DOUBLE = "DOUBLE", + STRING = "STRING", +} + +/** + * Get usage metrics for all extensions from Cloud Monitoring API. + */ +export async function queryTimeSeries( + query: CmQuery, + projectNumber: number +): Promise { + try { + const res = await api.request( + "GET", + `/${CLOUD_MONITORING_VERSION}/projects/${projectNumber}/timeSeries/`, + { + auth: true, + origin: api.cloudMonitoringOrigin, + data: query, + } + ); + return res.body.timeSeries; + } catch (err) { + throw new FirebaseError(`Failed to get extension usage: ${err}`, { + status: err.status, + }); + } +} diff --git a/src/test/extensions/extensionsApi.spec.ts b/src/test/extensions/extensionsApi.spec.ts index cbfedd60a24..dc0c2a99e58 100644 --- a/src/test/extensions/extensionsApi.spec.ts +++ b/src/test/extensions/extensionsApi.spec.ts @@ -954,6 +954,38 @@ describe("listExtensionVersions", () => { }); }); +describe("getPublisherProfile", () => { + afterEach(() => { + nock.cleanAll(); + }); + + const PUBLISHER_PROFILE = { + name: "projects/test-publisher/publisherProfile", + publisherId: "test-publisher", + registerTime: "2020-06-30T00:21:06.722782Z", + }; + it("should make a GET call to the correct endpoint", async () => { + nock(api.extensionsOrigin) + .get(`/${VERSION}/projects/${PROJECT_ID}/publisherProfile`) + .query(true) + .reply(200, PUBLISHER_PROFILE); + + const res = await extensionsApi.getPublisherProfile(PROJECT_ID); + expect(res).to.deep.equal(PUBLISHER_PROFILE); + expect(nock.isDone()).to.be.true; + }); + + it("should throw a FirebaseError if the endpoint returns an error response", async () => { + nock(api.extensionsOrigin) + .get(`/${VERSION}/projects/${PROJECT_ID}/publisherProfile`) + .query(true) + .reply(404); + + await expect(extensionsApi.getPublisherProfile(PROJECT_ID)).to.be.rejectedWith(FirebaseError); + expect(nock.isDone()).to.be.true; + }); +}); + describe("registerPublisherProfile", () => { afterEach(() => { nock.cleanAll(); diff --git a/src/test/extensions/metricsUtils.spec.ts b/src/test/extensions/metricsUtils.spec.ts new file mode 100644 index 00000000000..d00b729bf56 --- /dev/null +++ b/src/test/extensions/metricsUtils.spec.ts @@ -0,0 +1,446 @@ +import * as _ from "lodash"; +import { expect } from "chai"; +import * as clc from "cli-color"; + +import { + buildMetricsTableRow, + parseBucket, + parseTimeseriesResponse, +} from "../../extensions/metricsUtils"; +import { TimeSeriesResponse, MetricKind, ValueType } from "../../gcp/cloudmonitoring"; +import { BucketedMetric } from "../../extensions/metricsTypeDef"; + +describe("metricsUtil", () => { + describe(`${parseBucket.name}`, () => { + it("should parse a bucket based on the higher bound value", () => { + expect(parseBucket(10)).to.deep.equals({ low: 0, high: 10 }); + expect(parseBucket(50)).to.deep.equals({ low: 40, high: 50 }); + expect(parseBucket(200)).to.deep.equals({ low: 100, high: 200 }); + expect(parseBucket(2200)).to.deep.equals({ low: 2100, high: 2200 }); + expect(parseBucket(0)).to.deep.equals({ low: 0, high: 0 }); + }); + }); + + describe("buildMetricsTableRow", () => { + it("shows decreasing instance count properly", () => { + const metric: BucketedMetric = { + ref: { + publisherId: "firebase", + extensionId: "bq-export", + version: "0.0.1", + }, + valueToday: { + high: 500, + low: 400, + }, + value7dAgo: { + high: 400, + low: 300, + }, + value28dAgo: { + high: 200, + low: 100, + }, + }; + expect(buildMetricsTableRow(metric)).to.deep.equals([ + "0.0.1", + "400 - 500", + clc.green("▲ ") + "100 (±100)", + clc.green("▲ ") + "300 (±100)", + ]); + }); + it("shows decreasing instance count properly", () => { + const metric: BucketedMetric = { + ref: { + publisherId: "firebase", + extensionId: "bq-export", + version: "0.0.1", + }, + valueToday: { + high: 200, + low: 100, + }, + value7dAgo: { + high: 200, + low: 100, + }, + value28dAgo: { + high: 300, + low: 200, + }, + }; + expect(buildMetricsTableRow(metric)).to.deep.equals([ + "0.0.1", + "100 - 200", + "-", + clc.red("▼ ") + "-100 (±100)", + ]); + }); + }); + + describe(`${parseTimeseriesResponse.name}`, () => { + it("should parse TimeSeriesResponse into list of BucketedMetrics", () => { + const series: TimeSeriesResponse = [ + { + metric: { + type: "firebaseextensions.googleapis.com/extension/version/active_instances", + labels: { + extension: "export-bigquery", + publisher: "firebase", + version: "0.1.0", + }, + }, + metricKind: MetricKind.GAUGE, + resource: { + labels: { + extension: "export-bigquery", + publisher: "firebase", + version: "all", + }, + type: "firebaseextensions.googleapis.com/ExtensionVersion", + }, + valueType: ValueType.INT64, + points: [ + { + interval: { + startTime: "2021-10-30T17:56:21.027Z", + endTime: "2021-10-30T17:56:21.027Z", + }, + value: { + int64Value: 10, + }, + }, + ], + }, + { + metric: { + type: "firebaseextensions.googleapis.com/extension/version/active_instances", + labels: { + extension: "export-bigquery", + publisher: "firebase", + version: "0.1.0", + }, + }, + metricKind: MetricKind.GAUGE, + resource: { + labels: { + extension: "export-bigquery", + publisher: "firebase", + version: "0.1.0", + }, + type: "firebaseextensions.googleapis.com/ExtensionVersion", + }, + valueType: ValueType.INT64, + points: [ + { + interval: { + startTime: "2021-10-30T17:56:21.027Z", + endTime: "2021-10-30T17:56:21.027Z", + }, + value: { + int64Value: 10, + }, + }, + { + interval: { + startTime: "2021-10-29T17:56:21.027Z", + endTime: "2021-10-29T17:56:21.027Z", + }, + value: { + int64Value: 20, + }, + }, + { + interval: { + startTime: "2021-10-28T17:56:21.027Z", + endTime: "2021-10-28T17:56:21.027Z", + }, + value: { + int64Value: 30, + }, + }, + { + interval: { + startTime: "2021-10-27T17:56:21.027Z", + endTime: "2021-10-27T17:56:21.027Z", + }, + value: { + int64Value: 40, + }, + }, + { + interval: { + startTime: "2021-10-26T17:56:21.027Z", + endTime: "2021-10-26T17:56:21.027Z", + }, + value: { + int64Value: 50, + }, + }, + { + interval: { + startTime: "2021-10-25T17:56:21.027Z", + endTime: "2021-10-25T17:56:21.027Z", + }, + value: { + int64Value: 60, + }, + }, + { + interval: { + startTime: "2021-10-24T17:56:21.027Z", + endTime: "2021-10-24T17:56:21.027Z", + }, + value: { + int64Value: 70, + }, + }, + { + interval: { + startTime: "2021-10-23T17:56:21.027Z", + endTime: "2021-10-23T17:56:21.027Z", + }, + value: { + int64Value: 80, + }, + }, + { + interval: { + startTime: "2021-10-22T17:56:21.027Z", + endTime: "2021-10-22T17:56:21.027Z", + }, + value: { + int64Value: 90, + }, + }, + { + interval: { + startTime: "2021-10-21T17:56:21.027Z", + endTime: "2021-10-21T17:56:21.027Z", + }, + value: { + int64Value: 100, + }, + }, + { + interval: { + startTime: "2021-10-20T17:56:21.027Z", + endTime: "2021-10-20T17:56:21.027Z", + }, + value: { + int64Value: 200, + }, + }, + { + interval: { + startTime: "2021-10-19T17:56:21.027Z", + endTime: "2021-10-19T17:56:21.027Z", + }, + value: { + int64Value: 300, + }, + }, + { + interval: { + startTime: "2021-10-18T17:56:21.027Z", + endTime: "2021-10-18T17:56:21.027Z", + }, + value: { + int64Value: 400, + }, + }, + { + interval: { + startTime: "2021-10-17T17:56:21.027Z", + endTime: "2021-10-17T17:56:21.027Z", + }, + value: { + int64Value: 500, + }, + }, + { + interval: { + startTime: "2021-10-16T17:56:21.027Z", + endTime: "2021-10-16T17:56:21.027Z", + }, + value: { + int64Value: 600, + }, + }, + { + interval: { + startTime: "2021-10-15T17:56:21.027Z", + endTime: "2021-10-15T17:56:21.027Z", + }, + value: { + int64Value: 700, + }, + }, + { + interval: { + startTime: "2021-10-14T17:56:21.027Z", + endTime: "2021-10-14T17:56:21.027Z", + }, + value: { + int64Value: 800, + }, + }, + { + interval: { + startTime: "2021-10-13T17:56:21.027Z", + endTime: "2021-10-13T17:56:21.027Z", + }, + value: { + int64Value: 900, + }, + }, + { + interval: { + startTime: "2021-10-12T17:56:21.027Z", + endTime: "2021-10-12T17:56:21.027Z", + }, + value: { + int64Value: 1000, + }, + }, + { + interval: { + startTime: "2021-10-11T17:56:21.027Z", + endTime: "2021-10-11T17:56:21.027Z", + }, + value: { + int64Value: 1100, + }, + }, + { + interval: { + startTime: "2021-10-10T17:56:21.027Z", + endTime: "2021-10-10T17:56:21.027Z", + }, + value: { + int64Value: 1200, + }, + }, + { + interval: { + startTime: "2021-10-09T17:56:21.027Z", + endTime: "2021-10-09T17:56:21.027Z", + }, + value: { + int64Value: 1300, + }, + }, + { + interval: { + startTime: "2021-10-08T17:56:21.027Z", + endTime: "2021-10-08T17:56:21.027Z", + }, + value: { + int64Value: 1400, + }, + }, + { + interval: { + startTime: "2021-10-07T17:56:21.027Z", + endTime: "2021-10-07T17:56:21.027Z", + }, + value: { + int64Value: 1500, + }, + }, + { + interval: { + startTime: "2021-10-06T17:56:21.027Z", + endTime: "2021-10-06T17:56:21.027Z", + }, + value: { + int64Value: 1600, + }, + }, + { + interval: { + startTime: "2021-10-05T17:56:21.027Z", + endTime: "2021-10-05T17:56:21.027Z", + }, + value: { + int64Value: 1700, + }, + }, + { + interval: { + startTime: "2021-10-04T17:56:21.027Z", + endTime: "2021-10-04T17:56:21.027Z", + }, + value: { + int64Value: 1800, + }, + }, + { + interval: { + startTime: "2021-10-03T17:56:21.027Z", + endTime: "2021-10-03T17:56:21.027Z", + }, + value: { + int64Value: 1900, + }, + }, + { + interval: { + startTime: "2021-10-02T17:56:21.027Z", + endTime: "2021-10-02T17:56:21.027Z", + }, + value: { + int64Value: 2000, + }, + }, + { + interval: { + startTime: "2021-10-01T17:56:21.027Z", + endTime: "2021-10-01T17:56:21.027Z", + }, + value: { + int64Value: 2100, + }, + }, + ], + }, + ]; + + expect(parseTimeseriesResponse(series)).to.deep.equals([ + { + ref: { + extensionId: "export-bigquery", + publisherId: "firebase", + version: "0.1.0", + }, + value28dAgo: { + high: 1900, + low: 1800, + }, + value7dAgo: { + high: 70, + low: 60, + }, + valueToday: { + high: 10, + low: 0, + }, + }, + // Should sort "all" to the end. + { + ref: { + extensionId: "export-bigquery", + publisherId: "firebase", + version: "all", + }, + value28dAgo: undefined, + value7dAgo: undefined, + valueToday: { + high: 10, + low: 0, + }, + }, + ]); + }); + }); +}); diff --git a/src/test/gcp/cloudmonitoring.spec.ts b/src/test/gcp/cloudmonitoring.spec.ts new file mode 100644 index 00000000000..73a1b0b3df0 --- /dev/null +++ b/src/test/gcp/cloudmonitoring.spec.ts @@ -0,0 +1,48 @@ +import { expect } from "chai"; +import * as nock from "nock"; +import * as api from "../../api"; +import { FirebaseError } from "../../error"; +import { Aligner, CmQuery, queryTimeSeries, TimeSeriesView } from "../../gcp/cloudmonitoring"; + +const CLOUD_MONITORING_VERSION = "v3"; +const PROJECT_NUMBER = 1; + +describe("queryTimeSeries", () => { + afterEach(() => { + nock.cleanAll(); + }); + + const query: CmQuery = { + filter: + 'metric.type="firebaseextensions.googleapis.com/extension/version/active_instances" resource.type="firebaseextensions.googleapis.com/ExtensionVersion"', + "interval.endTime": new Date().toJSON(), + "interval.startTime": new Date().toJSON(), + view: TimeSeriesView.FULL, + "aggregation.alignmentPeriod": (60 * 60 * 24).toString() + "s", + "aggregation.perSeriesAligner": Aligner.ALIGN_MAX, + }; + + const RESPONSE = { + timeSeries: [], + }; + + it("should make a POST call to the correct endpoint", async () => { + nock(api.cloudMonitoringOrigin) + .get(`/${CLOUD_MONITORING_VERSION}/projects/${PROJECT_NUMBER}/timeSeries/`) + .query(true) + .reply(200, RESPONSE); + + const res = await queryTimeSeries(query, PROJECT_NUMBER); + expect(res).to.deep.equal(RESPONSE.timeSeries); + expect(nock.isDone()).to.be.true; + }); + + it("should throw a FirebaseError if the endpoint returns an error response", async () => { + nock(api.cloudMonitoringOrigin) + .get(`/${CLOUD_MONITORING_VERSION}/projects/${PROJECT_NUMBER}/timeSeries/`) + .query(true) + .reply(404); + await expect(queryTimeSeries(query, PROJECT_NUMBER)).to.be.rejectedWith(FirebaseError); + expect(nock.isDone()).to.be.true; + }); +}); From 9736bb003c634e27698f91c3afc963bfdc9cb2c2 Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Wed, 29 Dec 2021 12:56:34 -0500 Subject: [PATCH 0008/1699] Added finer grained metrics to ext:install. (#3940) * Added finer grained metrics to ext:install. * Updated tracking metric labels. --- src/commands/ext-install.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index c745c97adba..3ca011afc30 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -34,6 +34,7 @@ import { update } from "../extensions/updateHelper"; import { getRandomString } from "../extensions/utils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; +import { track } from "../track"; import { logger } from "../logger"; import { previews } from "../previews"; @@ -242,7 +243,8 @@ async function infoInstallBySource( } async function infoInstallByReference( - extensionName: string + extensionName: string, + interactive: boolean ): Promise { // Infer firebase if publisher ID not provided. if (extensionName.split("/").length < 2) { @@ -253,6 +255,7 @@ async function infoInstallByReference( const ref = refs.parse(extensionName); const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref)); if (!ref.version) { + track("Extension Install", "Install by Extension Version Ref", interactive ? 1 : 0); extensionName = `${extensionName}@latest`; } const extVersion = await extensionsApi.getExtensionVersion(extensionName); @@ -302,9 +305,11 @@ export default new Command("ext:install [extensionName]") // If the user types in URL, or a local path (prefixed with ~/, ../, or ./), install from local/URL source. // Otherwise, treat the input as an extension reference and proceed with reference-based installation. if (isLocalOrURLPath(extensionName)) { + track("Extension Install", "Install by Source", options.interactive ? 1 : 0); source = await infoInstallBySource(projectId, extensionName); } else { - extVersion = await infoInstallByReference(extensionName); + track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); + extVersion = await infoInstallByReference(extensionName, options.interactive); } if ( !(await confirm({ From 754c8e43bd04831c332ae0208a1e6959cb914646 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 31 Dec 2021 11:50:59 -0800 Subject: [PATCH 0009/1699] Fix bug where empty vpc connector setting was removed. (#3973) Today, empty vpc connector setting is removed in the function update request, e.g.: ```js exports.vpc = functions.runWith({ vpcConnector: '' }).https.onRequest((request, response) => { functions.logger.info("Hello logs!", {structuredData: true}); response.send("Hello from Firebase!"); }); ``` This is unfortunate since the existing behavior makes it impossible to clear vpc connector setting (see https://github.com/firebase/firebase-tools/issues/3968 for discussion). The change here preserves the empty string as a vpc connector setting which in turn allows someone to clear the vpc connector setting on deploy. --- CHANGELOG.md | 1 + .../functions/runtimes/node/parseTriggers.ts | 4 ++-- .../runtimes/node/parseTriggers.spec.ts | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..dbacb12198a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Preserve empty vpc connector setting on function deploy. (#3973) diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 707aab8517a..8e801a902c2 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -212,9 +212,9 @@ export function addResourcesToBackend( runtime: runtime, ...triggered, }; - if (annotation.vpcConnector) { + if (annotation.vpcConnector != null) { let maybeId = annotation.vpcConnector; - if (!maybeId.includes("/")) { + if (maybeId && !maybeId.includes("/")) { maybeId = `projects/${projectId}/locations/${region}/connectors/${maybeId}`; } endpoint.vpcConnector = maybeId; diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index e148de92a52..675c8157de8 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -289,4 +289,23 @@ describe("addResourcesToBackend", () => { expect(result).to.deep.equal(expected); }); + + it("should preserve empty vpc connector setting", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + httpsTrigger: {}, + vpcConnector: "", + }; + + const result = backend.empty(); + parseTriggers.addResourcesToBackend("project", "nodejs16", trigger, result); + + const expected: backend.Backend = backend.of({ + ...BASIC_ENDPOINT, + httpsTrigger: {}, + vpcConnector: "", + }); + + expect(result).to.deep.equal(expected); + }); }); From 1ca83640e0b894017ad15d33e6ff66777c3c5182 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Tue, 4 Jan 2022 11:00:25 -0600 Subject: [PATCH 0010/1699] Upgrade google-auth-library (#3972) * Upgrade google-auth-library for https://github.com/FirebaseExtended/action-hosting-deploy/issues/174 * Add changelog entry * Format CHANGELOG.md Co-authored-by: Bryan Kendall --- CHANGELOG.md | 1 + package-lock.json | 440 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +- 3 files changed, 436 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbacb12198a..5ac8662bedd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Preserve empty vpc connector setting on function deploy. (#3973) +- Upgrades google-auth-library to 7.x.x, enabling support for workload identity federation diff --git a/package-lock.json b/package-lock.json index e4ee035487e..c66c748940d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "filesize": "^6.1.0", "fs-extra": "^5.0.0", "glob": "^7.1.2", - "google-auth-library": "^6.1.3", + "google-auth-library": "^7.11.0", "inquirer": "~6.3.1", "js-yaml": "^3.13.1", "JSONStream": "^1.2.1", @@ -1106,6 +1106,50 @@ "node": ">=10" } }, + "node_modules/@google-cloud/common/node_modules/google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "dev": true, + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/common/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@google-cloud/common/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==", + "dev": true, + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/@google-cloud/firestore": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.8.0.tgz", @@ -1189,6 +1233,44 @@ "node": ">=10" } }, + "node_modules/@google-cloud/pubsub/node_modules/google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/pubsub/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@google-cloud/pubsub/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==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/@google-cloud/storage": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.7.0.tgz", @@ -1405,6 +1487,25 @@ "node": ">=8" } }, + "node_modules/@grpc/grpc-js/node_modules/google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@grpc/grpc-js/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", @@ -1413,6 +1514,25 @@ "node": ">=8" } }, + "node_modules/@grpc/grpc-js/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@grpc/grpc-js/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==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/@grpc/grpc-js/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -6460,6 +6580,44 @@ "node": ">=10" } }, + "node_modules/gcs-resumable-upload/node_modules/google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "dev": true, + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcs-resumable-upload/node_modules/google-auth-library/node_modules/gaxios": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", + "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", + "dev": true, + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gcs-resumable-upload/node_modules/is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -6470,6 +6628,29 @@ "node": ">=8" } }, + "node_modules/gcs-resumable-upload/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gcs-resumable-upload/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==", + "dev": true, + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -6657,9 +6838,9 @@ } }, "node_modules/google-auth-library": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.3.tgz", - "integrity": "sha512-m9mwvY3GWbr7ZYEbl61isWmk+fvTmOt0YNUfPOUY2VH8K5pZlAIWJjxEi0PqR3OjMretyiQLI6GURMrPSwHQ2g==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.11.0.tgz", + "integrity": "sha512-3S5jn2quRumvh9F/Ubf7GFrIq71HZ5a6vqosgdIu105kkk0WtSqc2jGCRqtWWOLRS8SX3AHACMOEDxhyWAQIcg==", "dependencies": { "arrify": "^2.0.0", "base64-js": "^1.3.0", @@ -6743,6 +6924,44 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", "integrity": "sha512-ctzZJ+XsmHQwe3xp07gFUq4JxBaRSYzKHPgblR76//UanGST7vfFNF0+ty5eEbgTqsENopzoDK090xlha9dccQ==" }, + "node_modules/google-gax/node_modules/google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-gax/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-gax/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==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/google-gax/node_modules/protobufjs": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", @@ -15049,6 +15268,49 @@ "google-auth-library": "^6.1.1", "retry-request": "^4.1.1", "teeny-request": "^7.0.0" + }, + "dependencies": { + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "dev": true, + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } } }, "@google-cloud/firestore": { @@ -15116,6 +15378,43 @@ "is-stream-ended": "^0.1.4", "lodash.snakecase": "^4.1.1", "p-defer": "^3.0.0" + }, + "dependencies": { + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } } }, "@google-cloud/storage": { @@ -15279,11 +15578,46 @@ "path-exists": "^4.0.0" } }, + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, "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==" }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -19337,12 +19671,69 @@ "node-fetch": "^2.3.0" } }, + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "dev": true, + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "gaxios": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", + "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", + "dev": true, + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.1" + } + } + } + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true, "optional": true + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } } } }, @@ -19488,9 +19879,9 @@ } }, "google-auth-library": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.3.tgz", - "integrity": "sha512-m9mwvY3GWbr7ZYEbl61isWmk+fvTmOt0YNUfPOUY2VH8K5pZlAIWJjxEi0PqR3OjMretyiQLI6GURMrPSwHQ2g==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.11.0.tgz", + "integrity": "sha512-3S5jn2quRumvh9F/Ubf7GFrIq71HZ5a6vqosgdIu105kkk0WtSqc2jGCRqtWWOLRS8SX3AHACMOEDxhyWAQIcg==", "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", @@ -19569,6 +19960,41 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", "integrity": "sha512-ctzZJ+XsmHQwe3xp07gFUq4JxBaRSYzKHPgblR76//UanGST7vfFNF0+ty5eEbgTqsENopzoDK090xlha9dccQ==" }, + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "protobufjs": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", diff --git a/package.json b/package.json index 2ca5fc42bde..b06a6ab6acc 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,6 @@ "dependencies": { "@google-cloud/pubsub": "^2.7.0", "@types/archiver": "^5.1.0", - "JSONStream": "^1.2.1", "abort-controller": "^3.0.0", "ajv": "^6.12.6", "archiver": "^5.0.0", @@ -109,9 +108,10 @@ "filesize": "^6.1.0", "fs-extra": "^5.0.0", "glob": "^7.1.2", - "google-auth-library": "^6.1.3", + "google-auth-library": "^7.11.0", "inquirer": "~6.3.1", "js-yaml": "^3.13.1", + "JSONStream": "^1.2.1", "jsonwebtoken": "^8.5.1", "leven": "^3.1.0", "lodash": "^4.17.21", From 012f327e07537eef2fc2791cfd49072295023d1f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 4 Jan 2022 10:50:06 -0800 Subject: [PATCH 0011/1699] Revive the runtime config loader in the emulator runtime. (#3974) Most recent version of Firebase Functions SDK automatically picks up locally stored `.runtimeconfig.json` to populate the config entries. However, due to a bug in some older version of the Function SDK, this process may fail. See the following issues for more detail: * https://github.com/firebase/firebase-tools/issues/3793 * https://github.com/firebase/firebase-functions/issues/877 As a workaround, the emulator runtime will load the contents of the `.runtimeconfig.json` to the `CLOUD_RUNTIME_CONFIG` environment variable. In the future, we will bump up the minimum version of the Firebase Functions SDK required to run the functions emulator to v3.15.1 and get rid of this workaround. Also - noticed a dead code that can be removed, so removed. --- src/emulator/functionsEmulatorRuntime.ts | 47 ++++++++++++++++++------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index e9e43496527..821db72de04 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -1,3 +1,5 @@ +import * as fs from "fs"; + import { EmulatorLog } from "./types"; import { CloudFunction, DeploymentOptions, https } from "firebase-functions"; import { @@ -521,6 +523,38 @@ function getDefaultConfig(): any { return JSON.parse(process.env.FIREBASE_CONFIG || "{}"); } +function initializeRuntimeConfig(frb: FunctionsRuntimeBundle) { + // Most recent version of Firebase Functions SDK automatically picks up locally + // stored .runtimeconfig.json to populate the config entries. + // However, due to a bug in some older version of the Function SDK, this process may fail. + // + // See the following issues for more detail: + // https://github.com/firebase/firebase-tools/issues/3793 + // https://github.com/firebase/firebase-functions/issues/877 + // + // As a workaround, the emulator runtime will load the contents of the .runtimeconfig.json + // to the CLOUD_RUNTIME_CONFIG environment variable IF the env var is unused. + // In the future, we will bump up the minimum version of the Firebase Functions SDK + // required to run the functions emulator to v3.15.1 and get rid of this workaround. + if (!process.env.CLOUD_RUNTIME_CONFIG) { + const configPath = `${frb.cwd}/.runtimeconfig.json`; + try { + const configContent = fs.readFileSync(configPath, "utf8"); + if (configContent) { + try { + JSON.parse(configContent.toString()); + logDebug(`Found local functions config: ${configPath}`); + process.env.CLOUD_RUNTIME_CONFIG = configContent.toString(); + } catch (e) { + new EmulatorLog("SYSTEM", "function-runtimeconfig-json-invalid", "").log(); + } + } + } catch (e) { + // Ignore, config is optional + } + } +} + /** * This stub is the most important and one of the only non-optional stubs.This feature redirects * writes from the admin SDK back into emulated resources. @@ -711,18 +745,6 @@ async function initializeFunctionsConfigHelper(frb: FunctionsRuntimeBundle): Pro }); } -/** - * Setup predefined environment variables for Node.js 10 and subsequent runtimes - * https://cloud.google.com/functions/docs/env-var - */ -function setNode10EnvVars(target: string, mode: "event" | "http", service: string) { - process.env.FUNCTION_TARGET = target; - process.env.FUNCTION_SIGNATURE_TYPE = mode; - process.env.K_SERVICE = service; - process.env.K_REVISION = "1"; - process.env.PORT = "80"; -} - /* Retains a reference to the raw body buffer to allow access to the raw body for things like request signature validation. This is used as the "verify" function in body-parser options. @@ -991,6 +1013,7 @@ async function initializeRuntime( return; } + initializeRuntimeConfig(frb); initializeNetworkFiltering(frb); await initializeFunctionsConfigHelper(frb); await initializeFirebaseFunctionsStubs(frb); From edbe9698a826ca3d8cb9d118ea233c7a313fca0a Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 5 Jan 2022 09:39:29 -0600 Subject: [PATCH 0012/1699] disabling http in the fetchwebsetup requests makes them much faster (#3984) --- src/test/fetchWebSetup.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/fetchWebSetup.spec.ts b/src/test/fetchWebSetup.spec.ts index 55ae6dc49db..8d88be7330a 100644 --- a/src/test/fetchWebSetup.spec.ts +++ b/src/test/fetchWebSetup.spec.ts @@ -8,6 +8,14 @@ import { firebaseApiOrigin } from "../api"; import { FirebaseError } from "../error"; describe("fetchWebSetup module", () => { + before(() => { + nock.disableNetConnect(); + }); + + after(() => { + nock.enableNetConnect(); + }); + afterEach(() => { expect(nock.isDone()).to.be.true; }); From 9e9a8cd757a38b2ff37589606a4e388fe4d32ec0 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 5 Jan 2022 10:26:23 -0600 Subject: [PATCH 0013/1699] upgrade typescript to 4.5 (#3957) * upgrade typescript to 4.5 * remove type from js file * fix missing * fix a couple more error types * let appliedValue take any arguments --- CONTRIBUTING.md | 2 +- package-lock.json | 14 ++++---- package.json | 2 +- .../functionsEmulatorRuntime.spec.ts | 2 +- scripts/integration-helpers/cli.ts | 2 +- scripts/lint-changed-files.ts | 2 +- scripts/storage-emulator-integration/tests.ts | 12 +++---- src/apiv2.ts | 4 +-- src/appdistribution/client.ts | 10 +++--- src/appdistribution/distribution.ts | 2 +- src/auth.ts | 8 ++--- src/command.ts | 2 +- src/commands/appdistribution-distribute.ts | 4 +-- .../appdistribution-testers-remove.ts | 2 +- src/commands/apps-create.ts | 6 ++-- src/commands/apps-list.ts | 2 +- src/commands/apps-sdkconfig.ts | 2 +- src/commands/database-get.ts | 4 +-- src/commands/database-instances-list.ts | 4 +-- src/commands/database-push.ts | 2 +- src/commands/database-set.ts | 2 +- src/commands/database-settings-get.ts | 2 +- src/commands/database-settings-set.ts | 2 +- src/commands/database-update.ts | 2 +- src/commands/emulators-start.ts | 2 +- src/commands/ext-configure.ts | 4 +-- src/commands/ext-dev-emulators-start.ts | 2 +- src/commands/ext-dev-init.ts | 2 +- src/commands/ext-dev-list.ts | 2 +- src/commands/ext-dev-register.ts | 5 ++- src/commands/ext-dev-usage.ts | 4 +-- src/commands/ext-install.ts | 6 ++-- src/commands/ext-uninstall.ts | 4 +-- src/commands/ext-update.ts | 4 +-- src/commands/ext.ts | 2 +- src/commands/functions-delete.ts | 2 +- src/commands/functions-deletegcfartifacts.ts | 2 +- src/commands/functions-list.ts | 2 +- src/commands/functions-log.ts | 2 +- src/commands/hosting-channel-create.ts | 4 +-- src/commands/hosting-channel-delete.ts | 2 +- src/commands/hosting-channel-deploy.ts | 4 +-- src/commands/hosting-clone.ts | 6 ++-- src/commands/hosting-sites-create.ts | 2 +- src/commands/logout.ts | 4 +-- src/commands/projects-list.ts | 2 +- src/config.ts | 6 ++-- src/deploy/extensions/params.ts | 2 +- src/deploy/extensions/planner.ts | 2 +- src/deploy/extensions/secrets.ts | 2 +- src/deploy/extensions/tasks.ts | 2 +- src/deploy/functions/backend.ts | 2 +- src/deploy/functions/checkIam.ts | 8 ++--- src/deploy/functions/containerCleaner.ts | 24 +++++++------- src/deploy/functions/deploy.ts | 2 +- .../functions/ensureCloudBuildEnabled.ts | 2 +- .../functions/prepareFunctionsUpload.ts | 4 +-- src/deploy/functions/release/executor.ts | 2 +- src/deploy/functions/release/fabricator.ts | 4 +-- .../functions/runtimes/discovery/index.ts | 8 ++--- src/deploy/functions/runtimes/golang/index.ts | 4 +-- .../node/parseRuntimeAndValidateSDK.ts | 2 +- .../functions/runtimes/node/validate.ts | 2 +- .../functions/runtimes/node/versioning.ts | 4 +-- src/deploy/functions/services/storage.ts | 2 +- src/deploy/hosting/deploy.ts | 2 +- src/emulator/adminSdkConfig.ts | 4 +-- src/emulator/auth/cloudFunctions.ts | 2 +- src/emulator/auth/handlers.ts | 4 +-- src/emulator/auth/operations.ts | 4 +-- src/emulator/auth/widget_ui.ts | 2 +- src/emulator/commandUtils.ts | 4 +-- src/emulator/controller.ts | 8 ++--- src/emulator/databaseEmulator.ts | 6 ++-- src/emulator/downloadableEmulators.ts | 2 +- src/emulator/functionsEmulator.ts | 12 +++---- src/emulator/functionsEmulatorRuntime.ts | 26 +++++++-------- src/emulator/functionsEmulatorShared.ts | 2 +- src/emulator/functionsRuntimeWorker.ts | 2 +- src/emulator/hub.ts | 2 +- src/emulator/loggingEmulator.ts | 2 +- src/emulator/portUtils.ts | 4 +-- src/emulator/pubsubEmulator.ts | 6 ++-- src/emulator/registry.ts | 2 +- src/emulator/storage/apis/firebase.ts | 2 +- src/emulator/storage/cloudFunctions.ts | 2 +- src/emulator/storage/files.ts | 2 +- src/emulator/storage/rules/runtime.ts | 2 +- src/emulator/types.ts | 2 +- src/emulator/workQueue.ts | 8 ++--- src/ensureApiEnabled.ts | 2 +- src/extensions/checkProjectBilling.ts | 2 +- src/extensions/emulator/optionsHelper.ts | 2 +- src/extensions/emulator/specHelper.ts | 2 +- src/extensions/extensionsApi.ts | 14 ++++---- src/extensions/extensionsHelper.ts | 14 ++++---- src/extensions/localHelper.ts | 8 ++--- src/extensions/paramHelper.ts | 2 +- src/extensions/resolveSource.ts | 2 +- src/extensions/updateHelper.ts | 8 ++--- src/fetchWebSetup.ts | 2 +- src/firestore/checkDatabaseType.ts | 2 +- src/firestore/delete.ts | 2 +- src/fsutils.ts | 4 +-- src/functions/env.ts | 4 +-- src/functions/runtimeConfigExport.ts | 4 +-- src/functionsConfig.ts | 2 +- src/gcp/cloudfunctions.ts | 14 ++++---- src/gcp/cloudfunctionsv2.ts | 8 ++--- src/gcp/cloudlogging.ts | 2 +- src/gcp/cloudmonitoring.ts | 2 +- src/gcp/cloudscheduler.ts | 2 +- src/gcp/cloudtasks.ts | 4 +-- src/gcp/run.ts | 8 ++--- src/gcp/secretManager.ts | 2 +- src/gcp/storage.ts | 6 ++-- src/hosting/api.ts | 8 ++--- src/hosting/implicitInit.ts | 2 +- src/hosting/proxy.ts | 4 +-- src/init/features/database.ts | 2 +- src/init/features/hosting/github.ts | 6 ++-- src/management/apps.ts | 16 ++++----- src/management/database.ts | 8 ++--- src/management/projects.ts | 14 ++++---- src/operation-poller.ts | 2 +- src/rc.ts | 2 +- src/remoteconfig/get.ts | 2 +- src/remoteconfig/versionslist.ts | 2 +- src/requireAuth.ts | 2 +- src/requireDatabaseInstance.ts | 2 +- src/requirePermissions.ts | 2 +- src/rulesDeploy.ts | 4 +-- src/shortenUrl.ts | 2 +- .../deploy/remoteconfig/remoteconfig.spec.ts | 2 +- src/test/init/features/project.spec.ts | 4 +-- src/test/management/apps.spec.ts | 33 ++++++++++--------- src/test/management/database.spec.ts | 10 +++--- src/test/management/projects.spec.ts | 24 +++++++------- src/test/operation-poller.spec.ts | 4 +-- src/test/remoteconfig/get.spec.ts | 2 +- src/test/remoteconfig/rollback.spec.ts | 4 +-- src/test/remoteconfig/versionslist.spec.ts | 2 +- src/test/throttler/throttler.spec.ts | 10 +++--- src/throttler/throttler.ts | 4 +-- src/utils.ts | 8 ++--- standalone/firepit.js | 8 ++--- templates/init/hosting/index.html | 2 +- 147 files changed, 343 insertions(+), 341 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92eb719f4cb..dac519a1fe1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -312,7 +312,7 @@ import { FirebaseError } from "../error"; async function myFunc(options: any): void { try { return await somethingThatMayFail(options.projectId); - } catch (err) { + } catch (err: any) { throw FirebaseError(`Project ${clc.bold(projectId)} caused an issue.', { original: err }); } } diff --git a/package-lock.json b/package-lock.json index c66c748940d..992370c5189 100644 --- a/package-lock.json +++ b/package-lock.json @@ -139,7 +139,7 @@ "supertest": "^3.3.0", "swagger2openapi": "^6.0.3", "ts-node": "^9.1.1", - "typescript": "^3.9.5", + "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1" }, "engines": { @@ -13212,9 +13212,9 @@ } }, "node_modules/typescript": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", - "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -24948,9 +24948,9 @@ } }, "typescript": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", - "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true }, "typescript-json-schema": { diff --git a/package.json b/package.json index b06a6ab6acc..9e3b3144970 100644 --- a/package.json +++ b/package.json @@ -211,7 +211,7 @@ "supertest": "^3.3.0", "swagger2openapi": "^6.0.3", "ts-node": "^9.1.1", - "typescript": "^3.9.5", + "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1" } } diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index 1a2888a178b..662340333c9 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -890,7 +890,7 @@ describe("FunctionsEmulator-Runtime", () => { try { await callHTTPSFunction(worker, frb); - } catch (e) { + } catch (e: any) { // No-op } diff --git a/scripts/integration-helpers/cli.ts b/scripts/integration-helpers/cli.ts index 53192ef8aba..bd75ffca280 100644 --- a/scripts/integration-helpers/cli.ts +++ b/scripts/integration-helpers/cli.ts @@ -66,7 +66,7 @@ export class CLIProcess { return Promise.resolve(); } - const stopped = new Promise((resolve) => { + const stopped = new Promise((resolve) => { p.once("exit", (/* exitCode, signal */) => { this.process = undefined; resolve(); diff --git a/scripts/lint-changed-files.ts b/scripts/lint-changed-files.ts index 8b48792aec2..84d9f837795 100644 --- a/scripts/lint-changed-files.ts +++ b/scripts/lint-changed-files.ts @@ -70,7 +70,7 @@ function main(): void { cwd: root, stdio: ["pipe", process.stdout, process.stderr], }); - } catch (e) { + } catch (e: any) { console.error("eslint failed, see errors above."); console.error(); process.exit(e.status); diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index d1a6adaa5ab..2ed262755eb 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -807,7 +807,7 @@ describe("Storage emulator", () => { .ref(filename) .putString(IMAGE_FILE_BASE64, "base64"); return task.state; - } catch (err) { + } catch (err: any) { throw err.message; } }, @@ -833,7 +833,7 @@ describe("Storage emulator", () => { .ref(filename) .putString(IMAGE_FILE_BASE64, "base64"); return task.state; - } catch (err) { + } catch (err: any) { throw err.message; } }, @@ -871,7 +871,7 @@ describe("Storage emulator", () => { .ref(filename) .putString(IMAGE_FILE_BASE64, "base64"); return task.state; - } catch (err) { + } catch (err: any) { throw err.message; } }, @@ -909,7 +909,7 @@ describe("Storage emulator", () => { .ref(filename) .putString(IMAGE_FILE_BASE64, "base64"); return task.state; - } catch (err) { + } catch (err: any) { throw err.message; } }, @@ -955,7 +955,7 @@ describe("Storage emulator", () => { .ref(filename) .putString(IMAGE_FILE_BASE64, "base64"); return task.state; - } catch (err) { + } catch (err: any) { throw err.message; } }, @@ -1067,7 +1067,7 @@ describe("Storage emulator", () => { downloadUrl = await page.evaluate((filename) => { return firebase.storage().ref(filename).getDownloadURL(); }, filename); - } catch (err) { + } catch (err: any) { expect(err).to.equal(""); } const expectedHost = TEST_CONFIG.useProductionServers diff --git a/src/apiv2.ts b/src/apiv2.ts index fd51c779e83..2f3c850c135 100644 --- a/src/apiv2.ts +++ b/src/apiv2.ts @@ -216,7 +216,7 @@ export class Client { } try { return await this.doRequest(internalReqOptions); - } catch (thrown) { + } catch (thrown: any) { if (thrown instanceof FirebaseError) { throw thrown; } @@ -347,7 +347,7 @@ export class Client { let res: Response; try { res = await fetch(fetchURL, fetchOptions); - } catch (thrown) { + } catch (thrown: any) { const err = thrown instanceof Error ? thrown : new Error(thrown); const isAbortError = err.name.includes("AbortError"); if (isAbortError) { diff --git a/src/appdistribution/client.ts b/src/appdistribution/client.ts index 219e79388f2..76d926e5c7c 100644 --- a/src/appdistribution/client.ts +++ b/src/appdistribution/client.ts @@ -127,8 +127,8 @@ export class AppDistributionClient { auth: true, data, }); - } catch (err) { - throw new FirebaseError(`failed to update release notes with ${err.message}`, { exit: 1 }); + } catch (err: any) { + throw new FirebaseError(`failed to update release notes with ${err?.message}`, { exit: 1 }); } utils.logSuccess("added release notes successfully"); @@ -157,7 +157,7 @@ export class AppDistributionClient { auth: true, data, }); - } catch (err) { + } catch (err: any) { let errorMessage = err.message; if (_.has(err, "context.body.error")) { const errorStatus = _.get(err, "context.body.error.status"); @@ -182,7 +182,7 @@ export class AppDistributionClient { path: `${projectName}/testers:batchAdd`, body: { emails: emails }, }); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to add testers ${err}`); } @@ -200,7 +200,7 @@ export class AppDistributionClient { path: `${projectName}/testers:batchRemove`, body: { emails: emails }, }); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to remove testers ${err}`); } return apiResponse.body; diff --git a/src/appdistribution/distribution.ts b/src/appdistribution/distribution.ts index d1d2ee2dae7..3175bde6c03 100644 --- a/src/appdistribution/distribution.ts +++ b/src/appdistribution/distribution.ts @@ -33,7 +33,7 @@ export class Distribution { let stat; try { stat = fs.statSync(path); - } catch (err) { + } catch (err: any) { logger.info(err); throw new FirebaseError(`File ${path} does not exist: verify that file points to a binary`); } diff --git a/src/auth.ts b/src/auth.ts index e1f938dce29..d90da2c19a3 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -341,7 +341,7 @@ async function getTokensFromAuthorizationCode(code: string, callbackUrl: string) grant_type: "authorization_code", }, }); - } catch (err) { + } catch (err: any) { if (err instanceof Error) { logger.debug("Token Fetch Error:", err.stack || ""); } else { @@ -490,7 +490,7 @@ async function loginWithLocalhost( const tokens = await getTokens(queryCode, callbackUrl); await respondWithFile(req, res, 200, successTemplate); resolve(tokens); - } catch (err) { + } catch (err: any) { await respondWithFile(req, res, 400, "../templates/loginFailure.html"); reject(err); } @@ -651,7 +651,7 @@ async function refreshTokens( } return lastAccessToken!; - } catch (err) { + } catch (err: any) { if (err?.context?.body?.error === "invalid_scope") { throw new FirebaseError( "This command requires new authorization scopes not granted to your current session. Please run " + @@ -687,7 +687,7 @@ export async function logout(refreshToken: string) { token: refreshToken, }, }); - } catch (thrown) { + } catch (thrown: any) { const err: Error = thrown instanceof Error ? thrown : new Error(thrown); throw new FirebaseError("Authentication Error.", { exit: 1, diff --git a/src/command.ts b/src/command.ts index db70c24d726..0a566bd0680 100644 --- a/src/command.ts +++ b/src/command.ts @@ -253,7 +253,7 @@ export class Command { try { options.config = Config.load(options); - } catch (e) { + } catch (e: any) { options.configError = e; } diff --git a/src/commands/appdistribution-distribute.ts b/src/commands/appdistribution-distribute.ts index c371b5e5283..4fa605b0fe9 100644 --- a/src/commands/appdistribution-distribute.ts +++ b/src/commands/appdistribution-distribute.ts @@ -56,7 +56,7 @@ module.exports = new Command("appdistribution:distribute ") if (distribution.distributionFileType() === DistributionFileType.AAB) { try { aabInfo = await requests.getAabInfo(appName); - } catch (err) { + } catch (err: any) { if (err.status === 404) { throw new FirebaseError( `App Distribution could not find your app ${options.app}. ` + @@ -130,7 +130,7 @@ module.exports = new Command("appdistribution:distribute ") ); } releaseName = uploadResponse.release.name; - } catch (err) { + } catch (err: any) { if (err.status === 404) { throw new FirebaseError( `App Distribution could not find your app ${options.app}. ` + diff --git a/src/commands/appdistribution-testers-remove.ts b/src/commands/appdistribution-testers-remove.ts index 7d9a01f155b..6b5c28bfe3e 100644 --- a/src/commands/appdistribution-testers-remove.ts +++ b/src/commands/appdistribution-testers-remove.ts @@ -18,7 +18,7 @@ module.exports = new Command("appdistribution:testers:remove [emails...]") try { utils.logBullet(`Deleting ${emailsArr.length} testers from project`); deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to remove testers ${err}`); } diff --git a/src/commands/apps-create.ts b/src/commands/apps-create.ts index c4c19c7e824..85a4abfdbfd 100644 --- a/src/commands/apps-create.ts +++ b/src/commands/apps-create.ts @@ -103,7 +103,7 @@ async function initiateIosAppCreation(options: CreateIosAppOptions): Promise") let d; try { d = JSON.parse(r); - } catch (e) { + } catch (e: any) { throw new FirebaseError("Malformed JSON response", { original: e, exit: 2 }); } throw responseToError({ statusCode: res.status }, d); @@ -130,7 +130,7 @@ export default new Command("database:get ") res.body.pipe(outStream, { end: false }); - return new Promise((resolve) => { + return new Promise((resolve) => { // Tack on a single newline at the end of the stream. res.body.once("end", () => { if (outStream === process.stdout) { diff --git a/src/commands/database-instances-list.ts b/src/commands/database-instances-list.ts index 465a2115351..75be0dd71be 100644 --- a/src/commands/database-instances-list.ts +++ b/src/commands/database-instances-list.ts @@ -56,7 +56,7 @@ let cmd = new Command("database:instances:list") const projectId = needProjectId(options); try { instances = await listDatabaseInstances(projectId, location); - } catch (err) { + } catch (err: any) { spinner.fail(); throw err; } @@ -68,7 +68,7 @@ let cmd = new Command("database:instances:list") const projectNumber = await needProjectNumber(options); try { instances = await firedata.listDatabaseInstances(projectNumber); - } catch (err) { + } catch (err: any) { spinner.fail(); throw err; } diff --git a/src/commands/database-push.ts b/src/commands/database-push.ts index dc2e43dbc36..0aced5949e2 100644 --- a/src/commands/database-push.ts +++ b/src/commands/database-push.ts @@ -48,7 +48,7 @@ export default new Command("database:push [infile]") path: u.pathname, body: inStream, }); - } catch (err) { + } catch (err: any) { logger.debug(err); throw new FirebaseError(`Unexpected error while pushing data: ${err}`, { exit: 2 }); } diff --git a/src/commands/database-set.ts b/src/commands/database-set.ts index 33ea976b6ee..ab3edc52809 100644 --- a/src/commands/database-set.ts +++ b/src/commands/database-set.ts @@ -63,7 +63,7 @@ export default new Command("database:set [infile]") path: dbJsonURL.pathname, body: inStream, }); - } catch (err) { + } catch (err: any) { logger.debug(err); throw new FirebaseError(`Unexpected error while setting data: ${err}`, { exit: 2 }); } diff --git a/src/commands/database-settings-get.ts b/src/commands/database-settings-get.ts index da21a6d3208..589eaf82c80 100644 --- a/src/commands/database-settings-get.ts +++ b/src/commands/database-settings-get.ts @@ -40,7 +40,7 @@ export default new Command("database:settings:get ") let res; try { res = await c.get(u.pathname); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Unexpected error fetching configs at ${path}`, { exit: 2, original: err, diff --git a/src/commands/database-settings-set.ts b/src/commands/database-settings-set.ts index e0b3575cd8c..780b01a43fe 100644 --- a/src/commands/database-settings-set.ts +++ b/src/commands/database-settings-set.ts @@ -44,7 +44,7 @@ export default new Command("database:settings:set ") const c = new Client({ urlPrefix: u.origin, auth: true }); try { await c.put(u.pathname, JSON.stringify(parsedValue)); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Unexpected error fetching configs at ${path}`, { exit: 2, original: err, diff --git a/src/commands/database-update.ts b/src/commands/database-update.ts index ef4259e17e2..bb760b52ee9 100644 --- a/src/commands/database-update.ts +++ b/src/commands/database-update.ts @@ -63,7 +63,7 @@ export default new Command("database:update [infile]") path: jsonUrl.pathname, body: inStream, }); - } catch (err) { + } catch (err: any) { throw new FirebaseError("Unexpected error while setting data"); } diff --git a/src/commands/emulators-start.ts b/src/commands/emulators-start.ts index d22814493ef..1b7709db53b 100644 --- a/src/commands/emulators-start.ts +++ b/src/commands/emulators-start.ts @@ -28,7 +28,7 @@ module.exports = new Command("emulators:start") try { await controller.startAll(options); - } catch (e) { + } catch (e: any) { await controller.cleanShutdown(); throw e; } diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 761e16a4e9f..51d72b24497 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -40,7 +40,7 @@ export default new Command("ext:configure ") let existingInstance: extensionsApi.ExtensionInstance; try { existingInstance = await extensionsApi.getInstance(projectId, instanceId); - } catch (err) { + } catch (err: any) { if (err.status === 404) { return utils.reject( `No extension instance ${instanceId} found in project ${projectId}.`, @@ -97,7 +97,7 @@ export default new Command("ext:configure ") ) ); return res; - } catch (err) { + } catch (err: any) { if (spinner.isSpinning) { spinner.fail(); } diff --git a/src/commands/ext-dev-emulators-start.ts b/src/commands/ext-dev-emulators-start.ts index 15ad271801e..782ce63fa5f 100644 --- a/src/commands/ext-dev-emulators-start.ts +++ b/src/commands/ext-dev-emulators-start.ts @@ -21,7 +21,7 @@ module.exports = new Command("ext:dev:emulators:start") try { commandUtils.beforeEmulatorCommand(emulatorOptions); await controller.startAll(emulatorOptions); - } catch (e) { + } catch (e: any) { await controller.cleanShutdown(); if (!(e instanceof FirebaseError)) { throw new FirebaseError("Error in ext:dev:emulator:start", e); diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index b2b382ec24d..2a30ba1f389 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -172,7 +172,7 @@ export default new Command("ext:dev:init") const welcome = fs.readFileSync(path.join(TEMPLATE_ROOT, lang, "WELCOME.md"), "utf8"); return logger.info("\n" + marked(welcome)); - } catch (err) { + } catch (err: any) { if (!(err instanceof FirebaseError)) { throw new FirebaseError( `Error occurred when initializing files for new extension: ${err.message}`, diff --git a/src/commands/ext-dev-list.ts b/src/commands/ext-dev-list.ts index 9697135e12f..ba909c38601 100644 --- a/src/commands/ext-dev-list.ts +++ b/src/commands/ext-dev-list.ts @@ -21,7 +21,7 @@ export default new Command("ext:dev:list ") let extensions; try { extensions = await listExtensions(publisherId); - } catch (err) { + } catch (err: any) { throw new FirebaseError(err); } diff --git a/src/commands/ext-dev-register.ts b/src/commands/ext-dev-register.ts index adb73b2fc3d..32637fd0a89 100644 --- a/src/commands/ext-dev-register.ts +++ b/src/commands/ext-dev-register.ts @@ -35,7 +35,7 @@ export default new Command("ext:dev:register") }); try { await registerPublisherProfile(projectId, publisherId); - } catch (err) { + } catch (err: any) { if (err.status === 409) { const error = `Couldn't register the publisher ID '${clc.bold(publisherId)}' to the project '${clc.bold( @@ -52,8 +52,7 @@ export default new Command("ext:dev:register") throw new FirebaseError( `Failed to register publisher ID ${clc.bold(publisherId)} for project ${clc.bold( projectId - )}: ${err.message}`, - { exit: 1 } + )}: ${err.message}` ); } return utils.logLabeledSuccess( diff --git a/src/commands/ext-dev-usage.ts b/src/commands/ext-dev-usage.ts index 70989df61ad..548777a4fb6 100644 --- a/src/commands/ext-dev-usage.ts +++ b/src/commands/ext-dev-usage.ts @@ -37,7 +37,7 @@ module.exports = new Command("ext:dev:usage ") let extensions; try { extensions = await listExtensions(publisherId); - } catch (err) { + } catch (err: any) { throw new FirebaseError(err); } @@ -88,7 +88,7 @@ module.exports = new Command("ext:dev:usage ") let response; try { response = await queryTimeSeries(query, projectNumber); - } catch (err) { + } catch (err: any) { throw new FirebaseError( `Error occurred when fetching usage data for extension ${extensionName}`, { diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 3ca011afc30..db6a77f2140 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -209,7 +209,7 @@ async function installExtension(options: InstallExtensionOptions): Promise "including those to update, reconfigure, or delete your installed extension." ) ); - } catch (err) { + } catch (err: any) { if (spinner.isSpinning) { spinner.fail(); } @@ -230,7 +230,7 @@ async function infoInstallBySource( let source; try { source = await createSourceFromLocation(projectId, extensionName); - } catch (err) { + } catch (err: any) { throw new FirebaseError( `Unable to find published extension '${clc.bold(extensionName)}', ` + `and encountered the following error when trying to create an instance of extension '${clc.bold( @@ -351,7 +351,7 @@ export default new Command("ext:install [extensionName]") nonInteractive: options.nonInteractive, force: options.force, }); - } catch (err) { + } catch (err: any) { if (!(err instanceof FirebaseError)) { throw new FirebaseError(`Error occurred installing the extension: ${err.message}`, { original: err, diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 1d15de190ae..0e1af789d8f 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -53,7 +53,7 @@ export default new Command("ext:uninstall ") try { instance = await extensionsApi.getInstance(projectId, instanceId); - } catch (err) { + } catch (err: any) { if (err.status === 404) { return utils.reject(`No extension instance ${instanceId} in project ${projectId}.`, { exit: 1, @@ -125,7 +125,7 @@ export default new Command("ext:uninstall ") spinner.succeed( ` ${clc.green.bold(logPrefix)}: deleted your extension instance's resources.` ); - } catch (err) { + } catch (err: any) { if (spinner.isSpinning) { spinner.fail(); } diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 45786079402..01646fa6e3c 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -79,7 +79,7 @@ export default new Command("ext:update [updateSource]") let existingInstance: extensionsApi.ExtensionInstance; try { existingInstance = await extensionsApi.getInstance(projectId, instanceId); - } catch (err) { + } catch (err: any) { if (err.status === 404) { throw new FirebaseError( `Extension instance '${clc.bold(instanceId)}' not found in project '${clc.bold( @@ -277,7 +277,7 @@ export default new Command("ext:update [updateSource]") )}` ) ); - } catch (err) { + } catch (err: any) { if (spinner.isSpinning) { spinner.fail(); } diff --git a/src/commands/ext.ts b/src/commands/ext.ts index 877703c99ba..3a968279bd8 100644 --- a/src/commands/ext.ts +++ b/src/commands/ext.ts @@ -41,7 +41,7 @@ module.exports = new Command("ext") await requirePermissions(options, ["firebaseextensions.instances.list"]); const projectId = needProjectId(options); return listExtensions(projectId); - } catch (err) { + } catch (err: any) { return; } }); diff --git a/src/commands/functions-delete.ts b/src/commands/functions-delete.ts index a02d20b6127..5147ae388df 100644 --- a/src/commands/functions-delete.ts +++ b/src/commands/functions-delete.ts @@ -97,7 +97,7 @@ export default new Command("functions:delete [filters...]") const summary = await fab.applyPlan(plan); await reporter.logAndTrackDeployStats(summary); reporter.printErrors(summary); - } catch (err) { + } catch (err: any) { throw new FirebaseError("Failed to delete functions", { original: err as Error, exit: 1, diff --git a/src/commands/functions-deletegcfartifacts.ts b/src/commands/functions-deletegcfartifacts.ts index d34b70c0e2a..de84c8f495f 100644 --- a/src/commands/functions-deletegcfartifacts.ts +++ b/src/commands/functions-deletegcfartifacts.ts @@ -50,7 +50,7 @@ export default new Command("functions:deletegcfartifacts") throw new FirebaseError("Command aborted.", { exit: 1 }); } await deleteGcfArtifacts(projectId, regions, dockerHelper); - } catch (err) { + } catch (err: any) { throw new FirebaseError("Command failed.", { original: err }); } }); diff --git a/src/commands/functions-list.ts b/src/commands/functions-list.ts index 929d40804bb..a81e0995bda 100644 --- a/src/commands/functions-list.ts +++ b/src/commands/functions-list.ts @@ -45,7 +45,7 @@ export default new Command("functions:list") } logger.info(table.toString()); return endpointsList; - } catch (err) { + } catch (err: any) { throw new FirebaseError("Failed to list functions", { exit: 1, original: err, diff --git a/src/commands/functions-log.ts b/src/commands/functions-log.ts index 1d0a11747cd..1bac0344b82 100644 --- a/src/commands/functions-log.ts +++ b/src/commands/functions-log.ts @@ -37,7 +37,7 @@ module.exports = new Command("functions:log") ); functionsLog.logEntries(entries); return entries; - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to list log entries ${err.message}`, { exit: 1 }); } }); diff --git a/src/commands/hosting-channel-create.ts b/src/commands/hosting-channel-create.ts index 46a03a47f7c..807f67e7667 100644 --- a/src/commands/hosting-channel-create.ts +++ b/src/commands/hosting-channel-create.ts @@ -54,7 +54,7 @@ export default new Command("hosting:channel:create [channelId]") let channel: Channel; try { channel = await createChannel(projectId, site, channelId, expireTTL); - } catch (e) { + } catch (e: any) { if (e.status === 409) { throw new FirebaseError( `Channel ${bold(channelId)} already exists on site ${bold(site)}. Deploy to ${bold( @@ -68,7 +68,7 @@ export default new Command("hosting:channel:create [channelId]") try { await addAuthDomains(projectId, [channel.url]); - } catch (e) { + } catch (e: any) { logLabeledWarning( LOG_TAG, marked( diff --git a/src/commands/hosting-channel-delete.ts b/src/commands/hosting-channel-delete.ts index 90480b44279..27ef98fc0d8 100644 --- a/src/commands/hosting-channel-delete.ts +++ b/src/commands/hosting-channel-delete.ts @@ -49,7 +49,7 @@ export default new Command("hosting:channel:delete ") if (channel) { try { await removeAuthDomain(projectId, channel.url); - } catch (e) { + } catch (e: any) { logLabeledWarning( "hosting:channel", marked( diff --git a/src/commands/hosting-channel-deploy.ts b/src/commands/hosting-channel-deploy.ts index 95b675bc2b9..96def9ea970 100644 --- a/src/commands/hosting-channel-deploy.ts +++ b/src/commands/hosting-channel-deploy.ts @@ -192,7 +192,7 @@ async function syncAuthState(projectId: string, sites: ChannelInfo[]) { try { await addAuthDomains(projectId, urlNames); logger.debug("[hosting] added auth domain for urls", urlNames); - } catch (e) { + } catch (e: any) { logLabeledWarning( LOG_TAG, marked( @@ -206,7 +206,7 @@ async function syncAuthState(projectId: string, sites: ChannelInfo[]) { } try { await cleanAuthState(projectId, siteNames); - } catch (e) { + } catch (e: any) { logLabeledWarning(LOG_TAG, "Unable to sync Firebase Auth state."); logger.debug("[hosting] unable to sync auth domain", e); } diff --git a/src/commands/hosting-clone.ts b/src/commands/hosting-clone.ts index 0792f5d3424..475e9b2cc47 100644 --- a/src/commands/hosting-clone.ts +++ b/src/commands/hosting-clone.ts @@ -80,7 +80,7 @@ export default new Command("hosting:clone ") ); try { tChannel = await createChannel("-", targetSiteId, targetChannelId); - } catch (e) { + } catch (e: any) { throw new FirebaseError( `Could not create the channel ${bold(targetChannelId)} for site ${bold(targetSiteId)}.`, { original: e } @@ -90,7 +90,7 @@ export default new Command("hosting:clone ") try { const tProjectId = parseProjectId(tChannel.name); await addAuthDomains(tProjectId, [tChannel.url]); - } catch (e) { + } catch (e: any) { utils.logLabeledWarning( "hosting:clone", marked( @@ -127,7 +127,7 @@ export default new Command("hosting:clone ") targetVersionName = targetVersion.name; } await createRelease(targetSiteId, targetChannelId, targetVersionName); - } catch (err) { + } catch (err: any) { spinner.fail(); throw err; } diff --git a/src/commands/hosting-sites-create.ts b/src/commands/hosting-sites-create.ts index 59802fc15fd..5bea47719f8 100644 --- a/src/commands/hosting-sites-create.ts +++ b/src/commands/hosting-sites-create.ts @@ -43,7 +43,7 @@ export default new Command("hosting:sites:create [siteId]") let site: Site; try { site = await createSite(projectId, siteId, appId); - } catch (e) { + } catch (e: any) { if (e.status === 409) { throw new FirebaseError( `Site ${bold(siteId)} already exists in project ${bold(projectId)}.`, diff --git a/src/commands/logout.ts b/src/commands/logout.ts index cfc126d3399..6bbac2a7f31 100644 --- a/src/commands/logout.ts +++ b/src/commands/logout.ts @@ -61,7 +61,7 @@ module.exports = new Command("logout [email]") auth.setRefreshToken(token); try { await auth.logout(token); - } catch (e) { + } catch (e: any) { utils.logWarning( `Invalid refresh token for ${account.user.email}, did not need to deauthorize` ); @@ -75,7 +75,7 @@ module.exports = new Command("logout [email]") auth.setRefreshToken(globalToken); try { await auth.logout(globalToken); - } catch (e) { + } catch (e: any) { utils.logWarning("Invalid refresh token, did not need to deauthorize"); } diff --git a/src/commands/projects-list.ts b/src/commands/projects-list.ts index 00dd55de670..e4657f2c672 100644 --- a/src/commands/projects-list.ts +++ b/src/commands/projects-list.ts @@ -56,7 +56,7 @@ module.exports = new Command("projects:list") try { projects = await listFirebaseProjects(); - } catch (err) { + } catch (err: any) { spinner.fail(); throw err; } diff --git a/src/config.ts b/src/config.ts index bb36211255f..63fc757b5b2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -123,7 +123,7 @@ export class Config { this.notes.databaseRulesFile = filePath; try { return fs.readFileSync(fullPath, "utf8"); - } catch (e) { + } catch (e: any) { if (e.code === "ENOENT") { throw new FirebaseError(`File not found: ${fullPath}`, { original: e }); } @@ -183,7 +183,7 @@ export class Config { return JSON.parse(content); } return content; - } catch (e) { + } catch (e: any) { if (options.fallback) { return options.fallback; } @@ -248,7 +248,7 @@ export class Config { } return new Config(data, options); - } catch (e) { + } catch (e: any) { throw new FirebaseError(`There was an error loading ${filename}:\n\n` + e.message, { exit: 1, }); diff --git a/src/deploy/extensions/params.ts b/src/deploy/extensions/params.ts index b04bfa4612d..b32beb00920 100644 --- a/src/deploy/extensions/params.ts +++ b/src/deploy/extensions/params.ts @@ -37,7 +37,7 @@ export function readParams(args: { logger.debug(`Successfully read params from ${fileToCheck}`); noFilesFound = false; Object.assign(combinedParams, params); - } catch (err) { + } catch (err: any) { logger.debug(`${err}`); } } diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 10221b5e514..701b789f73f 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -104,7 +104,7 @@ export async function want(args: { ref, params: subbedParams, }); - } catch (err) { + } catch (err: any) { logger.debug(`Got error reading extensions entry ${e}: ${err}`); errors.push(err as FirebaseError); } diff --git a/src/deploy/extensions/secrets.ts b/src/deploy/extensions/secrets.ts index 19a8e42df2e..c86fcbf9e2a 100644 --- a/src/deploy/extensions/secrets.ts +++ b/src/deploy/extensions/secrets.ts @@ -211,7 +211,7 @@ async function getSecretInfo( try { secretInfo.secret = await secretManager.getSecret(projectId, secretName); secretInfo.secretVersion = await secretManager.getSecretVersion(projectId, secretName, version); - } catch (err) { + } catch (err: any) { // Throw anything other than the expected 404 errors. if (err.status !== 404) { throw err; diff --git a/src/deploy/extensions/tasks.ts b/src/deploy/extensions/tasks.ts index 58a54f524b1..514581ba4bc 100644 --- a/src/deploy/extensions/tasks.ts +++ b/src/deploy/extensions/tasks.ts @@ -21,7 +21,7 @@ export function extensionsDeploymentHandler( let result; try { result = await task.run(); - } catch (err) { + } catch (err: any) { if (isRetryable(err)) { // Rethrow quota errors or operation already in progress so that throttler retries them. throw err; diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index f26a7e612b0..71845f7de53 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -369,7 +369,7 @@ async function loadExistingBackend(ctx: Context & PrivateContextFields): Promise let gcfV2Results; try { gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId); - } catch (err) { + } catch (err: any) { if (err.status === 404 && err.message?.toLowerCase().includes("method not found")) { return; // customer has preview enabled without allowlist set } diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index 832cfd65787..0a8362f6fa0 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -31,7 +31,7 @@ export async function checkServiceAccountIam(projectId: string): Promise { ["iam.serviceAccounts.actAs"] ); passed = iamResult.passed; - } catch (err) { + } catch (err: any) { logger.debug("[functions] service account IAM check errored, deploy may fail:", err); // we want to fail this check open and not rethrow since it's informational only return; @@ -85,7 +85,7 @@ export async function checkHttpIam( try { const iamResult = await iam.testIamPermissions(context.projectId, [PERMISSION]); passed = iamResult.passed; - } catch (e) { + } catch (e: any) { logger.debug( "[functions] failed http create setIamPolicy permission check. deploy may fail:", e @@ -165,7 +165,7 @@ export async function ensureServiceAgentRoles( let policy: iam.Policy; try { policy = await getIamPolicy(projectId); - } catch (err) { + } catch (err: any) { utils.logLabeledBullet( "functions", "Could not verify the necessary IAM configuration for the following newly-integrated services: " + @@ -185,7 +185,7 @@ export async function ensureServiceAgentRoles( // set the updated policy try { await setIamPolicy(projectId, policy, "bindings"); - } catch (err) { + } catch (err: any) { throw new FirebaseError( "We failed to modify the IAM policy for the project. The functions " + "deployment requires specific roles to be granted to service agents," + diff --git a/src/deploy/functions/containerCleaner.ts b/src/deploy/functions/containerCleaner.ts index 85d52c19f87..4c14f2fb537 100644 --- a/src/deploy/functions/containerCleaner.ts +++ b/src/deploy/functions/containerCleaner.ts @@ -28,11 +28,11 @@ async function retry(func: () => Promise): Promise { setTimeout(() => reject(new Error("Timeout")), TIMEOUT_MS); }); return await Promise.race([func(), timeout]); - } catch (error) { - logger.debug("Failed docker command with error ", error); + } catch (err: any) { + logger.debug("Failed docker command with error ", err); retry += 1; if (retry >= MAX_RETRIES) { - throw new FirebaseError("Failed to clean up artifacts", { original: error }); + throw new FirebaseError("Failed to clean up artifacts", { original: err }); } await sleep(Math.pow(INITIAL_BACKOFF, retry - 1)); } @@ -55,7 +55,7 @@ export async function cleanupBuildImages( ...haveFunctions.map(async (func) => { try { await arCleaner.cleanupFunction(func); - } catch (err) { + } catch (err: any) { const path = `${func.project}/${func.region}/gcf-artifacts`; failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`); } @@ -65,7 +65,7 @@ export async function cleanupBuildImages( ...deletedFunctions.map(async (func) => { try { await Promise.all([arCleaner.cleanupFunction(func), arCleaner.cleanupFunctionCache(func)]); - } catch (err) { + } catch (err: any) { const path = `${func.project}/${func.region}/gcf-artifacts`; failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`); } @@ -76,7 +76,7 @@ export async function cleanupBuildImages( ...[...haveFunctions, ...deletedFunctions].map(async (func) => { try { await gcrCleaner.cleanupFunction(func); - } catch (err) { + } catch (err: any) { const path = `${func.project}/${docker.GCR_SUBDOMAIN_MAPPING[func.region]}/gcf`; failedDomains.add(`https://console.cloud.google.com/gcr/images/${path}`); } @@ -135,7 +135,7 @@ export class ArtifactRegistryCleaner { let op: artifactregistry.Operation; try { op = await artifactregistry.deletePackage(ArtifactRegistryCleaner.packagePath(func)); - } catch (err) { + } catch (err: any) { // The client was not enrolled in the AR experimenet and the package // was missing if (err.status === 404) { @@ -276,7 +276,7 @@ export async function listGcfPaths( (async () => { try { return getHelper(dockerHelpers, subdomain).ls(`${projectId}/gcf`); - } catch (err) { + } catch (err: any) { failedSubdomains.push(subdomain); logger.debug(err); const stat: Stat = { @@ -336,7 +336,7 @@ export async function deleteGcfArtifacts( const subdomain = docker.GCR_SUBDOMAIN_MAPPING[loc]!; try { return getHelper(dockerHelpers, subdomain).rm(`${projectId}/gcf/${loc}`); - } catch (err) { + } catch (err: any) { failedSubdomains.push(subdomain); logger.debug(err); } @@ -387,7 +387,7 @@ export class DockerHelper { try { await this.rm(`${path}/${child}`); stat.children.splice(stat.children.indexOf(child), 1); - } catch (err) { + } catch (err: any) { toThrowLater = err; } }); @@ -400,7 +400,7 @@ export class DockerHelper { try { await retry(() => this.client.deleteTag(path, tag)); stat.tags.splice(stat.tags.indexOf(tag), 1); - } catch (err) { + } catch (err: any) { logger.debug("Got error trying to remove docker tag:", err); toThrowLater = err; } @@ -411,7 +411,7 @@ export class DockerHelper { try { await retry(() => this.client.deleteImage(path, digest)); stat.digests.splice(stat.digests.indexOf(digest), 1); - } catch (err) { + } catch (err: any) { logger.debug("Got error trying to remove docker image:", err); toThrowLater = err; } diff --git a/src/deploy/functions/deploy.ts b/src/deploy/functions/deploy.ts index 54f7f72c963..b8b7e45444d 100644 --- a/src/deploy/functions/deploy.ts +++ b/src/deploy/functions/deploy.ts @@ -89,7 +89,7 @@ export async function deploy( " folder uploaded successfully" ); } - } catch (err) { + } catch (err: any) { logWarning(clc.yellow("functions:") + " Upload Error: " + err.message); throw err; } diff --git a/src/deploy/functions/ensureCloudBuildEnabled.ts b/src/deploy/functions/ensureCloudBuildEnabled.ts index bda6d8cd083..2f2c6d3c2d8 100644 --- a/src/deploy/functions/ensureCloudBuildEnabled.ts +++ b/src/deploy/functions/ensureCloudBuildEnabled.ts @@ -49,7 +49,7 @@ function isPermissionError(e: { context?: { body?: { error?: { status?: string } export async function ensureCloudBuildEnabled(projectId: string): Promise { try { await ensure(projectId, CLOUD_BUILD_API, "functions"); - } catch (e) { + } catch (e: any) { if (isBillingError(e)) { throw nodeBillingError(projectId); } else if (isPermissionError(e)) { diff --git a/src/deploy/functions/prepareFunctionsUpload.ts b/src/deploy/functions/prepareFunctionsUpload.ts index 1c167830295..f8e32827274 100644 --- a/src/deploy/functions/prepareFunctionsUpload.ts +++ b/src/deploy/functions/prepareFunctionsUpload.ts @@ -24,7 +24,7 @@ export async function getFunctionsConfig(context: args.Context): Promise<{ [key: if (context.runtimeConfigEnabled) { try { config = await functionsConfig.materializeAll(context.firebaseConfig!.projectId); - } catch (err) { + } catch (err: any) { logger.debug(err); let errorCode = err?.context?.response?.statusCode; if (!errorCode) { @@ -89,7 +89,7 @@ async function packageSource(options: Options, sourceDir: string, configValues: } archive.finalize(); await pipeAsync(archive, fileStream); - } catch (err) { + } catch (err: any) { throw new FirebaseError( "Could not read source directory. Remove links and shortcuts and try again.", { diff --git a/src/deploy/functions/release/executor.ts b/src/deploy/functions/release/executor.ts index a32eca51dea..6e26c982d12 100644 --- a/src/deploy/functions/release/executor.ts +++ b/src/deploy/functions/release/executor.ts @@ -17,7 +17,7 @@ interface Operation { async function handler(op: Operation): Promise { try { op.result = await op.func(); - } catch (err) { + } catch (err: any) { // Throw retry functions back to the queue where they will be retried // with backoffs. To do this we cast a wide net for possible error codes. // These can be either TOO MANY REQUESTS (429) errors or CONFLICT (409) diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 969c5720d57..0f8504fe40a 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -116,7 +116,7 @@ export class Fabricator { try { await fn(); this.logOpSuccess(op, endpoint); - } catch (err) { + } catch (err: any) { result.error = err as Error; } result.durationMs = timer.stop(); @@ -267,7 +267,7 @@ export class Fabricator { .run(async () => { try { await pubsub.createTopic({ name: topic }); - } catch (err) { + } catch (err: any) { // Pub/Sub uses HTTP 409 (CONFLICT) with a status message of // ALREADY_EXISTS if the topic already exists. if (err.status === 409) { diff --git a/src/deploy/functions/runtimes/discovery/index.ts b/src/deploy/functions/runtimes/discovery/index.ts index 80a906c474a..dc0ec78a443 100644 --- a/src/deploy/functions/runtimes/discovery/index.ts +++ b/src/deploy/functions/runtimes/discovery/index.ts @@ -29,7 +29,7 @@ export function yamlToBackend( throw new FirebaseError( "It seems you are using a newer SDK than this version of the CLI can handle. Please update your CLI with `npm install -g firebase-tools`" ); - } catch (err) { + } catch (err: any) { throw new FirebaseError("Failed to parse backend specification", { children: [err] }); } } @@ -42,7 +42,7 @@ export async function detectFromYaml( let text: string; try { text = await exports.readFileAsync(path.join(directory, "backend.yaml"), "utf8"); - } catch (err) { + } catch (err: any) { if (err.code === "ENOENT") { logger.debug("Could not find backend.yaml. Must use http discovery"); } else { @@ -74,7 +74,7 @@ export async function detectFromPort( try { res = await Promise.race([fetch(`http://localhost:${port}/backend.yaml`), timedOut]); break; - } catch (err) { + } catch (err: any) { // Allow us to wait until the server is listening. if (err?.code === "ECONNREFUSED") { continue; @@ -89,7 +89,7 @@ export async function detectFromPort( let parsed: any; try { parsed = yaml.load(text); - } catch (err) { + } catch (err: any) { throw new FirebaseError("Failed to parse backend specification", { children: [err] }); } diff --git a/src/deploy/functions/runtimes/golang/index.ts b/src/deploy/functions/runtimes/golang/index.ts index dd00781cea6..938570aa47e 100644 --- a/src/deploy/functions/runtimes/golang/index.ts +++ b/src/deploy/functions/runtimes/golang/index.ts @@ -39,7 +39,7 @@ export async function tryCreateDelegate( try { const modBuffer = await promisify(fs.readFile)(goModPath); module = gomod.parseModule(modBuffer.toString("utf8")); - } catch (err) { + } catch (err: any) { logger.debug("Customer code is not Golang code (or they aren't using gomod)"); return; } @@ -80,7 +80,7 @@ export class Delegate { async build(): Promise { try { await promisify(fs.mkdir)(path.join(this.sourceDir, "autogen")); - } catch (err) { + } catch (err: any) { if (err?.code !== "EEXIST") { throw new FirebaseError("Failed to create codegen directory", { children: [err] }); } diff --git a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts index 479b8f2ad7e..bb0140a1c47 100644 --- a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts +++ b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts @@ -49,7 +49,7 @@ function getRuntimeChoiceFromPackageJson( let loaded; try { loaded = cjson.load(packageJsonPath); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Unable to load ${packageJsonPath}: ${err}`); } const engines = loaded.engines; diff --git a/src/deploy/functions/runtimes/node/validate.ts b/src/deploy/functions/runtimes/node/validate.ts index 510d0aef2e5..512aea0a2a1 100644 --- a/src/deploy/functions/runtimes/node/validate.ts +++ b/src/deploy/functions/runtimes/node/validate.ts @@ -50,7 +50,7 @@ export function packageJsonIsValid( data = cjson.load(packageJsonFile); logger.debug("> [functions] package.json contents:", JSON.stringify(data, null, 2)); assertFunctionsSourcePresent(data, sourceDir, projectDir); - } catch (e) { + } catch (e: any) { const msg = `There was an error reading ${sourceDirName}${path.sep}package.json:\n\n ${e.message}`; throw new FirebaseError(msg); } diff --git a/src/deploy/functions/runtimes/node/versioning.ts b/src/deploy/functions/runtimes/node/versioning.ts index 6ae7187bcf0..234122798a9 100644 --- a/src/deploy/functions/runtimes/node/versioning.ts +++ b/src/deploy/functions/runtimes/node/versioning.ts @@ -53,7 +53,7 @@ export function getFunctionsSDKVersion(sourceDir: string): string | void { } const output: NpmListResult = JSON.parse(child.stdout); return _.get(output, ["dependencies", "firebase-functions", "version"]); - } catch (e) { + } catch (e: any) { logger.debug("getFunctionsSDKVersion encountered error:", e); return; } @@ -110,7 +110,7 @@ export function checkFunctionsSDKVersion(currentVersion: string): void { "Please note that there will be breaking changes when you upgrade." ); } - } catch (e) { + } catch (e: any) { logger.debug("checkFunctionsSDKVersion encountered error:", e); return; } diff --git a/src/deploy/functions/services/storage.ts b/src/deploy/functions/services/storage.ts index 430b69c6f09..1dd282d24c5 100644 --- a/src/deploy/functions/services/storage.ts +++ b/src/deploy/functions/services/storage.ts @@ -49,7 +49,7 @@ export async function ensureStorageTriggerRegion( ); eventTrigger.region = bucket.location.toLowerCase(); logger.debug("Setting the event trigger region to", eventTrigger.region, "."); - } catch (err) { + } catch (err: any) { throw new FirebaseError("Can't find the storage bucket region", { original: err }); } } diff --git a/src/deploy/hosting/deploy.ts b/src/deploy/hosting/deploy.ts index e9054134720..e39df63a281 100644 --- a/src/deploy/hosting/deploy.ts +++ b/src/deploy/hosting/deploy.ts @@ -82,7 +82,7 @@ export async function deploy( try { await uploader.start(); - } catch (err) { + } catch (err: any) { track("Hosting Deploy", "failure"); throw err; } finally { diff --git a/src/emulator/adminSdkConfig.ts b/src/emulator/adminSdkConfig.ts index 72ded827be2..5eedcbd63a1 100644 --- a/src/emulator/adminSdkConfig.ts +++ b/src/emulator/adminSdkConfig.ts @@ -43,7 +43,7 @@ export async function getProjectAdminSdkConfigOrCached( const config = await getProjectAdminSdkConfig(projectId); setCacheAdminSdkConfig(projectId, config); return config; - } catch (e) { + } catch (e: any) { logger.debug(`Failed to get Admin SDK config for ${projectId}, falling back to cache`, e); return getCachedAdminSdkConfig(projectId); } @@ -62,7 +62,7 @@ async function getProjectAdminSdkConfig(projectId: string): Promise(`projects/${projectId}/adminSdkConfig`); return res.body; - } catch (err) { + } catch (err: any) { throw new FirebaseError( `Failed to get Admin SDK for Firebase project ${projectId}. ` + "Please make sure the project exists and your account has permission to access it.", diff --git a/src/emulator/auth/cloudFunctions.ts b/src/emulator/auth/cloudFunctions.ts index 7804d012bf7..c7e94bbab6a 100644 --- a/src/emulator/auth/cloudFunctions.ts +++ b/src/emulator/auth/cloudFunctions.ts @@ -45,7 +45,7 @@ export class AuthCloudFunction { let err: Error | undefined; try { res = await c.post(this.multicastPath, multicastEventBody); - } catch (e) { + } catch (e: any) { err = e; } diff --git a/src/emulator/auth/handlers.ts b/src/emulator/auth/handlers.ts index 5d79581625b..17ceb8482f9 100644 --- a/src/emulator/auth/handlers.ts +++ b/src/emulator/auth/handlers.ts @@ -59,7 +59,7 @@ export function registerHandlers( return res.status(200).json({ authEmulator: { success: `The email has been successfully reset.`, email }, }); - } catch (e) { + } catch (e: any) { if ( e instanceof NotImplementedError || (e instanceof BadRequestError && e.message === "INVALID_OOB_CODE") @@ -127,7 +127,7 @@ export function registerHandlers( authEmulator: { success: `The email has been successfully verified.`, email }, }); } - } catch (e) { + } catch (e: any) { if ( e instanceof NotImplementedError || (e instanceof BadRequestError && e.message === "INVALID_OOB_CODE") diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index 79b3fd3e06f..5bb0cd78165 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -462,7 +462,7 @@ function batchCreate( ); } state.overwriteUserWithLocalId(userInfo.localId, fields); - } catch (e) { + } catch (e: any) { if (e instanceof BadRequestError) { // Use friendlier messages for some codes, consistent with production. let message = e.message; @@ -1512,7 +1512,7 @@ function signInWithIdp( userMatchingProvider )); } - } catch (err) { + } catch (err: any) { if (reqBody.returnIdpCredential && err instanceof BadRequestError) { response.errorMessage = err.message; return response; diff --git a/src/emulator/auth/widget_ui.ts b/src/emulator/auth/widget_ui.ts index 32e3c4e1739..96ca57f727d 100644 --- a/src/emulator/auth/widget_ui.ts +++ b/src/emulator/auth/widget_ui.ts @@ -91,7 +91,7 @@ function sendAuthEventViaIframeRelay(authEvent, cb) { }, '*'); sent = true; } - } catch (e) { + } catch (e: any) { // The frame does not have the same origin } } diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index df9b9a5d1f5..992995d053e 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -139,7 +139,7 @@ export async function beforeEmulatorCommand(options: any): Promise { try { await requireAuth(options); - } catch (e) { + } catch (e: any) { logger.debug(e); utils.logLabeledWarning( "emulators", @@ -284,7 +284,7 @@ function processKillSignal( } } res(); - } catch (e) { + } catch (e: any) { logger.debug(e); rej(); } diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 9dcc89c9ec5..6695e2d6d35 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -145,7 +145,7 @@ export async function exportOnExit(options: any) { "please wait for the export to finish..." ); await exportEmulatorData(exportOnExitDir, options); - } catch (e) { + } catch (e: any) { utils.logWarning(e); utils.logWarning(`Automatic export to "${exportOnExitDir}" failed, going to exit now...`); } @@ -539,7 +539,7 @@ export async function startAll(options: Options, showUI: boolean = true): Promis if (!options.instance) { options.instance = await getDefaultDatabaseInstance(options); } - } catch (e) { + } catch (e: any) { databaseLogger.log( "DEBUG", `Failed to retrieve default database instance: ${JSON.stringify(e)}` @@ -733,7 +733,7 @@ export async function exportEmulatorData(exportPath: string, options: any) { try { await hubClient.getStatus(); - } catch (e) { + } catch (e: any) { const filePath = EmulatorHub.getLocatorFilePath(projectId); throw new FirebaseError( `The emulator hub for ${projectId} did not respond to a status check. If this error continues try shutting down all running emulators and deleting the file ${filePath}`, @@ -776,7 +776,7 @@ export async function exportEmulatorData(exportPath: string, options: any) { utils.logBullet(`Exporting data to: ${exportAbsPath}`); try { await hubClient.postExport(exportAbsPath); - } catch (e) { + } catch (e: any) { throw new FirebaseError("Export request failed, see emulator logs for more information.", { exit: 1, original: e, diff --git a/src/emulator/databaseEmulator.ts b/src/emulator/databaseEmulator.ts index 0c436da4495..bc340aba67c 100644 --- a/src/emulator/databaseEmulator.ts +++ b/src/emulator/databaseEmulator.ts @@ -64,7 +64,7 @@ export class DatabaseEmulator implements EmulatorInstance { try { await this.updateRules(c.instance, c.rules); this.logger.logLabeled("SUCCESS", "database", "Rules updated."); - } catch (e) { + } catch (e: any) { this.logger.logLabeled("WARN", "database", this.prettyPrintRulesError(c.rules, e)); this.logger.logLabeled("WARN", "database", "Failed to update rules"); } @@ -119,7 +119,7 @@ export class DatabaseEmulator implements EmulatorInstance { const readStream = fs.createReadStream(fPath); const { host, port } = this.getInfo(); - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { const req = http.request( { method: "PUT", @@ -168,7 +168,7 @@ export class DatabaseEmulator implements EmulatorInstance { data: content, json: false, }); - } catch (e) { + } catch (e: any) { // The body is already parsed as JSON if (e.context && e.context.body) { throw e.context.body.error; diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 90d2ea79edd..9be90d7a8d5 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -299,7 +299,7 @@ async function _runBinary( detached: true, stdio: ["inherit", "pipe", "pipe"], }); - } catch (e) { + } catch (e: any) { if (e.code === "EACCES") { // Known issue when WSL users don't have java // https://github.com/Microsoft/WSL/issues/3886 diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 4d7ca8cb502..2c0b2a772e4 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -407,7 +407,7 @@ export class FunctionsEmulator implements EmulatorInstance { async stop(): Promise { try { await this.workQueue.flush(); - } catch (e) { + } catch (e: any) { this.logger.logLabeled( "WARN", "functions", @@ -500,7 +500,7 @@ export class FunctionsEmulator implements EmulatorInstance { // Skip function with invalid id. try { functionIdsAreValid([definition]); - } catch (e) { + } catch (e: any) { this.logger.logLabeled( "WARN", `functions[${definition.id}]`, @@ -704,7 +704,7 @@ export class FunctionsEmulator implements EmulatorInstance { try { await pubsubEmulator.addTrigger(topic, key, signatureType); return true; - } catch (e) { + } catch (e: any) { return false; } } @@ -844,7 +844,7 @@ export class FunctionsEmulator implements EmulatorInstance { try { const localNodeOutput = spawnSync(localNodePath, ["--version"]).stdout.toString(); localMajorVersion = localNodeOutput.slice(1).split(".")[0]; - } catch (err) { + } catch (err: any) { // Will happen if we haven't asked about local version yet } @@ -887,7 +887,7 @@ export class FunctionsEmulator implements EmulatorInstance { if (functionsEnv.hasUserEnvs(projectInfo)) { try { return functionsEnv.loadUserEnvs(projectInfo); - } catch (e) { + } catch (e: any) { // Ignore - user envs are optional. logger.debug("Failed to load local environment variables", e); } @@ -1216,7 +1216,7 @@ export class FunctionsEmulator implements EmulatorInstance { claims.uid = claims.sub; return claims; - } catch (e) { + } catch (e: any) { return; } } diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 821db72de04..76ac894001f 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -53,7 +53,7 @@ function requireAsync(moduleName: string, opts?: { paths: string[] }): Promise { try { res(require(require.resolve(moduleName, opts))); // eslint-disable-line @typescript-eslint/no-var-requires - } catch (e) { + } catch (e: any) { rej(e); } }); @@ -63,7 +63,7 @@ function requireResolveAsync(moduleName: string, opts?: { paths: string[] }): Pr return new Promise((res, rej) => { try { res(require.resolve(moduleName, opts)); - } catch (e) { + } catch (e: any) { rej(e); } }); @@ -139,7 +139,7 @@ class Proxied { proxy: T; private anyValue?: (target: T, key: string) => any; - private appliedValue?: () => any; + private appliedValue?: (...args: any[]) => any; private rewrites: { [key: string]: (target: T, key: string) => any; } = {}; @@ -307,7 +307,7 @@ function requirePackageJson(frb: FunctionsRuntimeBundle): PackageJSON | undefine devDependencies: pkg.devDependencies || {}, }; return developerPkgJSON; - } catch (err) { + } catch (err: any) { return; } } @@ -353,7 +353,7 @@ function initializeNetworkFiltering(frb: FunctionsRuntimeBundle): void { try { new URL(arg); return arg; - } catch (err) { + } catch (err: any) { return; } } else if (typeof arg === "object") { @@ -382,7 +382,7 @@ function initializeNetworkFiltering(frb: FunctionsRuntimeBundle): void { try { return original(...args); - } catch (e) { + } catch (e: any) { const newed = new original(...args); // eslint-disable-line new-cap return newed; } @@ -423,7 +423,7 @@ async function initializeFirebaseFunctionsStubs(frb: FunctionsRuntimeBundle): Pr let httpsProvider: any; try { httpsProvider = require(httpsProviderV1Resolution); - } catch (e) { + } catch (e: any) { httpsProvider = require(httpsProviderResolution); } @@ -763,7 +763,7 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge return; } - await new Promise((resolveEphemeralServer, rejectEphemeralServer) => { + await new Promise((resolveEphemeralServer, rejectEphemeralServer) => { const handler = async (req: express.Request, res: express.Response) => { try { logDebug(`Ephemeral server handling ${req.method} request`); @@ -779,7 +779,7 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge }); await runHTTPS([req, res], func); - } catch (err) { + } catch (err: any) { rejectEphemeralServer(err); } }; @@ -862,7 +862,7 @@ async function runFunction(func: () => Promise): Promise { let caughtErr; try { await func(); - } catch (err) { + } catch (err: any) { caughtErr = err; } @@ -1028,7 +1028,7 @@ async function initializeRuntime( } else { try { triggerModule = require(frb.cwd); - } catch (err) { + } catch (err: any) { if (err.code !== "ERR_REQUIRE_ESM") { await moduleResolutionDetective(frb, err); return; @@ -1069,7 +1069,7 @@ async function handleMessage(message: string) { let runtimeArgs: FunctionsRuntimeArgs; try { runtimeArgs = JSON.parse(message) as FunctionsRuntimeArgs; - } catch (e) { + } catch (e: any) { new EmulatorLog("FATAL", "runtime-error", `Got unexpected message body: ${message}`).log(); await flushAndExit(1); return; @@ -1114,7 +1114,7 @@ async function handleMessage(message: string) { } else { await goIdle(); } - } catch (err) { + } catch (err: any) { new EmulatorLog("FATAL", "runtime-error", err.stack ? err.stack : err).log(); await flushAndExit(1); } diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index c41c568e5a4..2a752a64151 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -275,7 +275,7 @@ export function findModuleRoot(moduleName: string, filepath: string): string { return chunks.join("/"); } break; - } catch (err) { + } catch (err: any) { /**/ } } diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index 10c2445b805..7eca3b642e7 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -126,7 +126,7 @@ export class RuntimeWorker { return Promise.resolve(); } - return new Promise((res) => { + return new Promise((res) => { const listener = () => { this.stateEvents.removeListener(RuntimeWorkerState.IDLE, listener); this.stateEvents.removeListener(RuntimeWorkerState.FINISHED, listener); diff --git a/src/emulator/hub.ts b/src/emulator/hub.ts index 47c3bf2ab1f..446097d143d 100644 --- a/src/emulator/hub.ts +++ b/src/emulator/hub.ts @@ -99,7 +99,7 @@ export class EmulatorHub implements EmulatorInstance { res.status(200).send({ message: "OK", }); - } catch (e) { + } catch (e: any) { const errorString = e.message || JSON.stringify(e); utils.logLabeledWarning("emulators", `Export failed: ${errorString}`); res.status(500).json({ diff --git a/src/emulator/loggingEmulator.ts b/src/emulator/loggingEmulator.ts index 38d1666416e..e2b69110dd0 100644 --- a/src/emulator/loggingEmulator.ts +++ b/src/emulator/loggingEmulator.ts @@ -119,7 +119,7 @@ class WebSocketTransport extends TransportStream { try { bundle.data = { ...bundle.data, ...JSON.parse(value) }; return null; - } catch (err) { + } catch (err: any) { // If the value isn't JSONable, just treat it like a string return value; } diff --git a/src/emulator/portUtils.ts b/src/emulator/portUtils.ts index d911c054541..73877fc0695 100644 --- a/src/emulator/portUtils.ts +++ b/src/emulator/portUtils.ts @@ -128,7 +128,7 @@ export async function checkPortOpen(port: number, host: string): Promise { + await new Promise((resolve) => { req.on("end", () => { req.body = Buffer.concat(bufs); resolve(); diff --git a/src/emulator/storage/cloudFunctions.ts b/src/emulator/storage/cloudFunctions.ts index 8c914b0e9ea..620d8e56e9b 100644 --- a/src/emulator/storage/cloudFunctions.ts +++ b/src/emulator/storage/cloudFunctions.ts @@ -67,7 +67,7 @@ export class StorageCloudFunctions { if (cloudEventRes.status !== 200) { errStatus.push(cloudEventRes.status); } - } catch (e) { + } catch (e: any) { err = e as Error; } diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index ecc9152ccad..1fd0efb24e7 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -643,7 +643,7 @@ export class Persistence { deleteFile(fileName: string, failSilently = false): void { try { unlinkSync(this.getDiskPath(fileName)); - } catch (err) { + } catch (err: any) { if (!failSilently) { throw err; } diff --git a/src/emulator/storage/rules/runtime.ts b/src/emulator/storage/rules/runtime.ts index b153bca85b5..091ac56d79f 100644 --- a/src/emulator/storage/rules/runtime.ts +++ b/src/emulator/storage/rules/runtime.ts @@ -172,7 +172,7 @@ export class StorageRulesRuntime { let rap; try { rap = JSON.parse(serializedRuntimeActionResponse) as RuntimeActionResponse; - } catch (err) { + } catch (err: any) { EmulatorLogger.forEmulator(Emulators.STORAGE).log( "INFO", serializedRuntimeActionResponse diff --git a/src/emulator/types.ts b/src/emulator/types.ts index 458c0005085..35d72b4e50e 100644 --- a/src/emulator/types.ts +++ b/src/emulator/types.ts @@ -232,7 +232,7 @@ export class EmulatorLog { let isNotJSON = false; try { parsedLog = JSON.parse(json); - } catch (err) { + } catch (err: any) { isNotJSON = true; } diff --git a/src/emulator/workQueue.ts b/src/emulator/workQueue.ts index c32fdaf67ec..ec9bfbb39a4 100644 --- a/src/emulator/workQueue.ts +++ b/src/emulator/workQueue.ts @@ -68,7 +68,7 @@ export class WorkQueue { while (!this.stopped) { // If the queue is empty, wait until something is added. if (!this.queue.length) { - await new Promise((res) => { + await new Promise((res) => { this.notifyQueue = res; }); } @@ -80,7 +80,7 @@ export class WorkQueue { "work-queue", `waiting for work to finish (running=${this.workRunningCount})` ); - await new Promise((res) => { + await new Promise((res) => { this.notifyWorkFinish = res; }); } @@ -105,7 +105,7 @@ export class WorkQueue { } this.logger.logLabeled("BULLET", "functions", "Waiting for all functions to finish..."); - return new Promise((res, rej) => { + return new Promise((res, rej) => { const delta = 100; let elapsed = 0; @@ -143,7 +143,7 @@ export class WorkQueue { try { await next(); - } catch (e) { + } catch (e: any) { this.logger.log("DEBUG", e); } finally { this.workRunningCount--; diff --git a/src/ensureApiEnabled.ts b/src/ensureApiEnabled.ts index cf328079ad8..5bcaebb4422 100644 --- a/src/ensureApiEnabled.ts +++ b/src/ensureApiEnabled.ts @@ -49,7 +49,7 @@ export async function check( async function enable(projectId: string, apiName: string): Promise { try { await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`); - } catch (err) { + } catch (err: any) { if (isBillingError(err)) { throw new FirebaseError(`Your project ${bold( projectId diff --git a/src/extensions/checkProjectBilling.ts b/src/extensions/checkProjectBilling.ts index 542aa1d52c6..09d580b3394 100644 --- a/src/extensions/checkProjectBilling.ts +++ b/src/extensions/checkProjectBilling.ts @@ -30,7 +30,7 @@ async function openBillingAccount(projectId: string, url: string, open: boolean) if (open) { try { opn(url); - } catch (err) { + } catch (err: any) { logger.debug("Unable to open billing URL: " + err.stack); } } diff --git a/src/extensions/emulator/optionsHelper.ts b/src/extensions/emulator/optionsHelper.ts index 19fe0454560..f330d4676d7 100644 --- a/src/extensions/emulator/optionsHelper.ts +++ b/src/extensions/emulator/optionsHelper.ts @@ -115,7 +115,7 @@ function readTestConfigFile(testConfigPath: string): { [key: string]: any } { try { const buf = fs.readFileSync(path.resolve(testConfigPath)); return JSON.parse(buf.toString()); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Error reading --test-config file: ${err.message}\n`, { original: err, }); diff --git a/src/extensions/emulator/specHelper.ts b/src/extensions/emulator/specHelper.ts index ab6f84a36cf..2c7a218b32b 100644 --- a/src/extensions/emulator/specHelper.ts +++ b/src/extensions/emulator/specHelper.ts @@ -22,7 +22,7 @@ const validFunctionTypes = [ function wrappedSafeLoad(source: string): any { try { return yaml.safeLoad(source); - } catch (err) { + } catch (err: any) { if (err instanceof yaml.YAMLException) { throw new FirebaseError(`YAML Error: ${err.message}`, { original: err }); } diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index df1ce1eba33..24794f33837 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -454,7 +454,7 @@ function populateResourceProperties(spec: ExtensionSpec): void { if (r.propertiesYaml) { r.properties = yaml.safeLoad(r.propertiesYaml); } - } catch (err) { + } catch (err: any) { logger.debug(`[ext] failed to parse resource properties yaml: ${err}`); } }); @@ -528,7 +528,7 @@ export async function getExtensionVersion(extensionVersionRef: string): Promise< populateResourceProperties(res.body.spec); } return res.body; - } catch (err) { + } catch (err: any) { if (err.status === 404) { throw refNotFoundError(ref); } else if (err instanceof FirebaseError) { @@ -662,7 +662,7 @@ export async function deprecateExtensionVersion( } ); return res.body; - } catch (err) { + } catch (err: any) { if (err.status === 403) { throw new FirebaseError( `You are not the owner of extension '${clc.bold( @@ -699,7 +699,7 @@ export async function undeprecateExtensionVersion(extensionRef: string): Promise } ); return res.body; - } catch (err) { + } catch (err: any) { if (err.status === 403) { throw new FirebaseError( `You are not the owner of extension '${clc.bold( @@ -775,7 +775,7 @@ export async function unpublishExtension(extensionRef: string): Promise { auth: true, origin: api.extensionsOrigin, }); - } catch (err) { + } catch (err: any) { if (err.status === 403) { throw new FirebaseError( `You are not the owner of extension '${clc.bold( @@ -808,7 +808,7 @@ export async function deleteExtension(extensionRef: string): Promise { auth: true, origin: api.extensionsOrigin, }); - } catch (err) { + } catch (err: any) { if (err.status === 403) { throw new FirebaseError( `You are not the owner of extension '${clc.bold( @@ -839,7 +839,7 @@ export async function getExtension(extensionRef: string): Promise { origin: api.extensionsOrigin, }); return res.body; - } catch (err) { + } catch (err: any) { if (err.status === 404) { throw refNotFoundError(ref); } else if (err instanceof FirebaseError) { diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 85630e09c88..78699d78e0d 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -407,7 +407,7 @@ export async function publishExtensionVersionFromLocalSource(args: { let extension; try { extension = await getExtension(`${args.publisherId}/${args.extensionId}`); - } catch (err) { + } catch (err: any) { // Silently fail and continue the publish flow if extension not found. } @@ -415,7 +415,7 @@ export async function publishExtensionVersionFromLocalSource(args: { try { const changes = getLocalChangelog(args.rootDirectory); notes = changes[extensionSpec.version]; - } catch (err) { + } catch (err: any) { throw new FirebaseError( "No CHANGELOG.md file found. " + "Please create one and add an entry for this version. " + @@ -487,7 +487,7 @@ export async function publishExtensionVersionFromLocalSource(args: { objectPath = await archiveAndUploadSource(args.rootDirectory, EXTENSIONS_BUCKET_NAME); uploadSpinner.succeed(" Uploaded extension source code"); packageUri = storageOrigin + objectPath + "?alt=media"; - } catch (err) { + } catch (err: any) { uploadSpinner.fail(); throw err; } @@ -497,7 +497,7 @@ export async function publishExtensionVersionFromLocalSource(args: { publishSpinner.start(); res = await publishExtensionVersion(ref, packageUri); publishSpinner.succeed(` Successfully published ${clc.bold(ref)}`); - } catch (err) { + } catch (err: any) { publishSpinner.fail(); if (err.status == 404) { throw new FirebaseError( @@ -535,7 +535,7 @@ export async function createSourceFromLocation( uploadSpinner.succeed(" Uploaded extension source code"); packageUri = storageOrigin + objectPath + "?alt=media"; extensionRoot = "/"; - } catch (err) { + } catch (err: any) { uploadSpinner.fail(); throw err; } @@ -557,7 +557,7 @@ async function deleteUploadedSource(objectPath: string) { try { await deleteObject(objectPath); logger.debug("Cleaned up uploaded source archive"); - } catch (err) { + } catch (err: any) { logger.debug("Unable to clean up uploaded source archive"); } } @@ -721,7 +721,7 @@ export function getSourceOrigin(sourceOrVersion: string): SourceOrigin { let ref; try { ref = refs.parse(sourceOrVersion); - } catch (err) { + } catch (err: any) { // Silently fail. } if (ref && ref.publisherId && ref.extensionId && !ref.version) { diff --git a/src/extensions/localHelper.ts b/src/extensions/localHelper.ts index 48a0f93bdef..0b36541562c 100644 --- a/src/extensions/localHelper.ts +++ b/src/extensions/localHelper.ts @@ -19,7 +19,7 @@ export async function getLocalExtensionSpec(directory: string): Promise { let registry: { [key: string]: RegistryEntry }; try { registry = await getExtensionRegistry(); - } catch (err) { + } catch (err: any) { logger.debug( "Couldn't get extensions registry, assuming no trusted publishers except Firebase." ); diff --git a/src/extensions/updateHelper.ts b/src/extensions/updateHelper.ts index c8e189e7d90..dae983e0922 100644 --- a/src/extensions/updateHelper.ts +++ b/src/extensions/updateHelper.ts @@ -171,7 +171,7 @@ export async function updateFromLocalSource( let source; try { source = await createSourceFromLocation(projectId, localSource); - } catch (err) { + } catch (err: any) { throw new FirebaseError(invalidSourceErrMsgTemplate(instanceId, localSource)); } utils.logLabeledBullet( @@ -201,7 +201,7 @@ export async function updateFromUrlSource( let source; try { source = await createSourceFromLocation(projectId, urlSource); - } catch (err) { + } catch (err: any) { throw new FirebaseError(invalidSourceErrMsgTemplate(instanceId, urlSource)); } utils.logLabeledBullet( @@ -233,7 +233,7 @@ export async function updateToVersionFromPublisherSource( const extension = await extensionsApi.getExtension(extensionRef); try { source = await extensionsApi.getExtensionVersion(extVersionRef); - } catch (err) { + } catch (err: any) { throw new FirebaseError( `Could not find source '${clc.bold(extVersionRef)}' because (${clc.bold( version @@ -245,7 +245,7 @@ export async function updateToVersionFromPublisherSource( let registryEntry; try { registryEntry = await resolveSource.resolveRegistryEntry(existingSpec.name); - } catch (err) { + } catch (err: any) { logger.debug(`Unable to fetch registry.json entry for ${existingSpec.name}`); } diff --git a/src/fetchWebSetup.ts b/src/fetchWebSetup.ts index 7cb69d0408b..a42c82fe1b6 100644 --- a/src/fetchWebSetup.ts +++ b/src/fetchWebSetup.ts @@ -111,7 +111,7 @@ export async function fetchWebSetup(options: any): Promise { if (defaultSite && defaultSite.appId) { hostingAppId = defaultSite.appId; } - } catch (e) { + } catch (e: any) { logger.debug("Failed to list hosting sites"); logger.debug(e); } diff --git a/src/firestore/checkDatabaseType.ts b/src/firestore/checkDatabaseType.ts index 7fd8c666c5d..79c2d37b967 100644 --- a/src/firestore/checkDatabaseType.ts +++ b/src/firestore/checkDatabaseType.ts @@ -18,7 +18,7 @@ export async function checkDatabaseType(projectId: string): Promise { + return new Promise((resolve, reject) => { const intervalId = setInterval(() => { if (queueLoop()) { clearInterval(intervalId); diff --git a/src/fsutils.ts b/src/fsutils.ts index 98300331461..ef32aa64c9a 100644 --- a/src/fsutils.ts +++ b/src/fsutils.ts @@ -3,7 +3,7 @@ import { statSync } from "fs"; export function fileExistsSync(path: string): boolean { try { return statSync(path).isFile(); - } catch (e) { + } catch (e: any) { return false; } } @@ -11,7 +11,7 @@ export function fileExistsSync(path: string): boolean { export function dirExistsSync(path: string): boolean { try { return statSync(path).isDirectory(); - } catch (e) { + } catch (e: any) { return false; } } diff --git a/src/functions/env.ts b/src/functions/env.ts index 644cd24be8f..fb5feca6e1f 100644 --- a/src/functions/env.ts +++ b/src/functions/env.ts @@ -171,7 +171,7 @@ function parseStrict(data: string): Record { for (const key of Object.keys(envs)) { try { validateKey(key); - } catch (err) { + } catch (err: any) { logger.debug(`Failed to validate key ${key}: ${err}`); if (err instanceof KeyValidationError) { validationErrors.push(err); @@ -276,7 +276,7 @@ export function loadUserEnvs({ try { const data = fs.readFileSync(path.join(functionsSource, f), "utf8"); envs = { ...envs, ...parseStrict(data) }; - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to load environment variables from ${f}.`, { exit: 2, children: err.children?.length > 0 ? err.children : [err], diff --git a/src/functions/runtimeConfigExport.ts b/src/functions/runtimeConfigExport.ts index 935acd594b4..0c135ea3a9c 100644 --- a/src/functions/runtimeConfigExport.ts +++ b/src/functions/runtimeConfigExport.ts @@ -107,7 +107,7 @@ export function convertKey(configKey: string, prefix: string): string { let envKey = baseKey; try { env.validateKey(envKey); - } catch (err) { + } catch (err: any) { if (err instanceof env.KeyValidationError) { envKey = prefix + envKey; env.validateKey(envKey); @@ -127,7 +127,7 @@ export function configToEnv(configs: Record, prefix: string): C try { const envKey = convertKey(configKey, prefix); success.push({ origKey: configKey, newKey: envKey, value: value as string }); - } catch (err) { + } catch (err: any) { if (err instanceof env.KeyValidationError) { errors.push({ origKey: configKey, diff --git a/src/functionsConfig.ts b/src/functionsConfig.ts index e33109e46c5..03fa41e1858 100644 --- a/src/functionsConfig.ts +++ b/src/functionsConfig.ts @@ -90,7 +90,7 @@ export async function setVariablesRecursive( try { // Only attempt to parse 'val' if it is a String (takes care of unparsed JSON, numbers, quoted string, etc.) parsed = JSON.parse(val); - } catch (e) { + } catch (e: any) { // 'val' is just a String } } diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index e1fe8ecb6c4..63859131479 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -185,7 +185,7 @@ export async function generateUploadUrl(projectId: string, location: string): Pr }); const responseBody = JSON.parse(res.body); return responseBody.uploadUrl; - } catch (err) { + } catch (err: any) { logger.info( "\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support." ); @@ -220,7 +220,7 @@ export async function createFunction( type: "create", done: false, }; - } catch (err) { + } catch (err: any) { throw functionsOpLogReject(cloudFunction.name, "create", err); } } @@ -250,7 +250,7 @@ export async function setIamPolicy(options: IamOptions): Promise { }, origin: api.functionsOrigin, }); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to set the IAM Policy on the function ${options.name}`, { original: err, }); @@ -276,7 +276,7 @@ export async function getIamPolicy(fnName: string): Promise { auth: true, origin: api.functionsOrigin, }); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to get the IAM Policy on the function ${fnName}`, { original: err, }); @@ -392,7 +392,7 @@ export async function updateFunction( name: res.body.name, type: "update", }; - } catch (err) { + } catch (err: any) { throw functionsOpLogReject(cloudFunction.name, "update", err); } } @@ -413,7 +413,7 @@ export async function deleteFunction(name: string): Promise { name: res.body.name, type: "delete", }; - } catch (err) { + } catch (err: any) { throw functionsOpLogReject(name, "delete", err); } } @@ -441,7 +441,7 @@ async function list(projectId: string, region: string): Promise try { const res = await client.delete(cloudFunction); return res.body; - } catch (err) { + } catch (err: any) { throw functionsOpLogReject(cloudFunction, "update", err); } } diff --git a/src/gcp/cloudlogging.ts b/src/gcp/cloudlogging.ts index fdc773d1c43..adc83e3c776 100644 --- a/src/gcp/cloudlogging.ts +++ b/src/gcp/cloudlogging.ts @@ -45,7 +45,7 @@ export async function listEntries( origin: api.cloudloggingOrigin, }); return result.body.entries; - } catch (err) { + } catch (err: any) { throw new FirebaseError("Failed to retrieve log entries from Google Cloud.", { original: err, }); diff --git a/src/gcp/cloudmonitoring.ts b/src/gcp/cloudmonitoring.ts index 93f6d92cacf..d5b7f3efb29 100644 --- a/src/gcp/cloudmonitoring.ts +++ b/src/gcp/cloudmonitoring.ts @@ -144,7 +144,7 @@ export async function queryTimeSeries( } ); return res.body.timeSeries; - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to get extension usage: ${err}`, { status: err.status, }); diff --git a/src/gcp/cloudscheduler.ts b/src/gcp/cloudscheduler.ts index 729c2c4aaa6..1b1a7913dd6 100644 --- a/src/gcp/cloudscheduler.ts +++ b/src/gcp/cloudscheduler.ts @@ -154,7 +154,7 @@ export async function createOrReplaceJob(job: Job): Promise { let newJob; try { newJob = await createJob(job); - } catch (err) { + } catch (err: any) { // Cloud resource location is not set so we error here and exit. if (err?.context?.response?.statusCode === 404) { throw new FirebaseError( diff --git a/src/gcp/cloudtasks.ts b/src/gcp/cloudtasks.ts index 6e48ab2587a..25317765e73 100644 --- a/src/gcp/cloudtasks.ts +++ b/src/gcp/cloudtasks.ts @@ -117,7 +117,7 @@ export async function upsertQueue(queue: Queue): Promise { await (module.exports.updateQueue as typeof updateQueue)(queue); return false; - } catch (err) { + } catch (err: any) { if (err?.context?.response?.statusCode === 404) { await (module.exports.createQueue as typeof createQueue)(queue); return true; @@ -189,7 +189,7 @@ export async function setEnqueuer( try { await (module.exports.setIamPolicy as typeof setIamPolicy)(name, policy); return; - } catch (err) { + } catch (err: any) { // Re-fetch on conflict if (err?.context?.response?.statusCode === 429) { existing = await (module.exports.getIamPolicy as typeof getIamPolicy)(name); diff --git a/src/gcp/run.ts b/src/gcp/run.ts index 25a19f6682a..2ab5b39a550 100644 --- a/src/gcp/run.ts +++ b/src/gcp/run.ts @@ -117,7 +117,7 @@ export async function getService(name: string): Promise { try { const response = await client.get(name); return response.body; - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to fetch Run service ${name}`, { original: err, }); @@ -128,7 +128,7 @@ export async function replaceService(name: string, service: Service): Promise(name, service); return response.body; - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to update Run service ${name}`, { original: err, }); @@ -157,7 +157,7 @@ export async function setIamPolicy( policy, updateMask: proto.fieldMasks(policy).join(","), }); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to set the IAM Policy on the Service ${name}`, { original: err, }); @@ -171,7 +171,7 @@ export async function getIamPolicy( try { const response = await httpClient.get(`${serviceName}:getIamPolicy`); return response.body; - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to get the IAM Policy on the Service ${serviceName}`, { original: err, }); diff --git a/src/gcp/secretManager.ts b/src/gcp/secretManager.ts index 424b8a10a3b..df7cd4136e7 100644 --- a/src/gcp/secretManager.ts +++ b/src/gcp/secretManager.ts @@ -54,7 +54,7 @@ export async function secretExists(projectId: string, name: string): Promise { ); } return resp.body.defaultBucket; - } catch (err) { + } catch (err: any) { logger.info( "\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support." ); @@ -223,7 +223,7 @@ export async function getBucket(bucketName: string): Promise { origin: api.storageOrigin, }); return result.body; - } catch (err) { + } catch (err: any) { logger.debug(err); throw new FirebaseError("Failed to obtain the storage bucket", { original: err, @@ -248,7 +248,7 @@ export async function getServiceAccount(projectId: string): Promise { if (!nextPageToken) { return sites; } - } catch (e) { + } catch (e: any) { if (e.status === 404) { throw new FirebaseError(`could not find sites for project "${project}"`, { original: e, @@ -406,7 +406,7 @@ export async function getSite(project: string, site: string): Promise { try { const res = await apiClient.get(`/projects/${project}/sites/${site}`); return res.body; - } catch (e) { + } catch (e: any) { if (e.status === 404) { throw new FirebaseError(`could not find site "${site}" for project "${project}"`, { original: e, diff --git a/src/hosting/implicitInit.ts b/src/hosting/implicitInit.ts index 242cbac4304..f0070286a8f 100644 --- a/src/hosting/implicitInit.ts +++ b/src/hosting/implicitInit.ts @@ -31,7 +31,7 @@ export async function implicitInit(options: any): Promise { let res; try { res = await apiClient.get>(options.operationResourceName); - } catch (err) { + } catch (err: any) { // Responses with 500 or 503 status code are treated as retriable errors. if (err.status === 500 || err.status === 503) { throw err; diff --git a/src/rc.ts b/src/rc.ts index 6f0c66a79bc..d8a29372b60 100644 --- a/src/rc.ts +++ b/src/rc.ts @@ -43,7 +43,7 @@ export class RC { if (fsutils.fileExistsSync(rcpath)) { try { data = cjson.load(rcpath); - } catch (e) { + } catch (e: any) { // malformed rc file is a warning, not an error utils.logWarning("JSON error trying to load " + clc.bold(rcpath)); } diff --git a/src/remoteconfig/get.ts b/src/remoteconfig/get.ts index eee4334a807..600ebfb25f0 100644 --- a/src/remoteconfig/get.ts +++ b/src/remoteconfig/get.ts @@ -52,7 +52,7 @@ export async function getTemplate( timeout: TIMEOUT, }); return response.body; - } catch (err) { + } catch (err: any) { logger.debug(err.message); throw new FirebaseError( `Failed to get Firebase Remote Config template for project ${projectId}. `, diff --git a/src/remoteconfig/versionslist.ts b/src/remoteconfig/versionslist.ts index 9f58b8baf0f..f4642c02b20 100644 --- a/src/remoteconfig/versionslist.ts +++ b/src/remoteconfig/versionslist.ts @@ -24,7 +24,7 @@ export async function getVersions(projectId: string, maxResults = 10): Promise { } else { try { return await autoAuth(options, options.authScopes); - } catch (e) { + } catch (e: any) { throw new FirebaseError( `Failed to authenticate, have you run ${clc.bold("firebase login")}?`, { original: e } diff --git a/src/requireDatabaseInstance.ts b/src/requireDatabaseInstance.ts index e1ed760346e..d2693cdee72 100644 --- a/src/requireDatabaseInstance.ts +++ b/src/requireDatabaseInstance.ts @@ -21,7 +21,7 @@ export async function requireDatabaseInstance(options: any): Promise { let instance; try { instance = await getDefaultDatabaseInstance(options); - } catch (err) { + } catch (err: any) { throw new FirebaseError(`Failed to get details for project: ${options.project}.`, { original: err, }); diff --git a/src/requirePermissions.ts b/src/requirePermissions.ts index aaa9b2efc86..8c52c440ed4 100644 --- a/src/requirePermissions.ts +++ b/src/requirePermissions.ts @@ -34,7 +34,7 @@ export async function requirePermissions(options: any, permissions: string[] = [ )}:\n\n ${iamResult.missing.join("\n ")}` ); } - } catch (err) { + } catch (err: any) { logger.debug(`[iam] error while checking permissions, command may fail: ${err}`); return; } diff --git a/src/rulesDeploy.ts b/src/rulesDeploy.ts index 29ab6d9d00f..846bce49bee 100644 --- a/src/rulesDeploy.ts +++ b/src/rulesDeploy.ts @@ -64,7 +64,7 @@ export class RulesDeploy { let src; try { src = fs.readFileSync(fullPath, "utf8"); - } catch (e) { + } catch (e: any) { logger.debug("[rules read error]", e.stack); throw new FirebaseError("Error reading rules file " + clc.bold(path)); } @@ -145,7 +145,7 @@ export class RulesDeploy { this.rulesetNames[filename] = await rulesetName; createdRulesetNames.push(await rulesetName); } - } catch (err) { + } catch (err: any) { if (err.status !== QUOTA_EXCEEDED_STATUS_CODE) { throw err; } diff --git a/src/shortenUrl.ts b/src/shortenUrl.ts index e69bad7f4d7..e4b7c7cdeeb 100644 --- a/src/shortenUrl.ts +++ b/src/shortenUrl.ts @@ -44,7 +44,7 @@ export async function shortenUrl(url: string, guessable = false): Promise { try { await rcDeploy.publishTemplate(PROJECT_NUMBER, currentTemplate, etag); - } catch (e) { + } catch (e: any) { e; } diff --git a/src/test/init/features/project.spec.ts b/src/test/init/features/project.spec.ts index 3a525b13077..ba6dd76e16c 100644 --- a/src/test/init/features/project.spec.ts +++ b/src/test/init/features/project.spec.ts @@ -115,7 +115,7 @@ describe("project", () => { let err; try { await doSetup(setup, emptyConfig, options); - } catch (e) { + } catch (e: any) { err = e; } @@ -159,7 +159,7 @@ describe("project", () => { let err; try { await doSetup(setup, emptyConfig, options); - } catch (e) { + } catch (e: any) { err = e; } diff --git a/src/test/management/apps.spec.ts b/src/test/management/apps.spec.ts index 2ac60b26d84..2c5c21562d5 100644 --- a/src/test/management/apps.spec.ts +++ b/src/test/management/apps.spec.ts @@ -160,7 +160,7 @@ describe("App management", () => { bundleId: IOS_APP_BUNDLE_ID, appStoreId: IOS_APP_STORE_ID, }); - } catch (e) { + } catch (e: any) { err = e; } @@ -197,7 +197,7 @@ describe("App management", () => { bundleId: IOS_APP_BUNDLE_ID, appStoreId: IOS_APP_STORE_ID, }); - } catch (e) { + } catch (e: any) { err = e; } @@ -275,7 +275,7 @@ describe("App management", () => { displayName: ANDROID_APP_DISPLAY_NAME, packageName: ANDROID_APP_PACKAGE_NAME, }); - } catch (e) { + } catch (e: any) { err = e; } @@ -310,7 +310,7 @@ describe("App management", () => { displayName: ANDROID_APP_DISPLAY_NAME, packageName: ANDROID_APP_PACKAGE_NAME, }); - } catch (e) { + } catch (e: any) { err = e; } @@ -379,7 +379,7 @@ describe("App management", () => { let err; try { await createWebApp(PROJECT_ID, { displayName: WEB_APP_DISPLAY_NAME }); - } catch (e) { + } catch (e: any) { err = e; } @@ -412,7 +412,7 @@ describe("App management", () => { let err; try { await createWebApp(PROJECT_ID, { displayName: WEB_APP_DISPLAY_NAME }); - } catch (e) { + } catch (e: any) { err = e; } @@ -469,7 +469,8 @@ describe("App management", () => { const appCounts = 10; const expectedAppList = generateIosAppList(appCounts); const apiResponseAppList = expectedAppList.map((app) => { - const iosApp = { ...app }; + // TODO: this is gross typing to make it invalid. Might be possible to do better. + const iosApp: any = { ...app }; delete iosApp.platform; return iosApp; }); @@ -488,7 +489,8 @@ describe("App management", () => { const appCounts = 10; const expectedAppList = generateAndroidAppList(appCounts); const apiResponseAppList = expectedAppList.map((app) => { - const androidApps = { ...app }; + // TODO: this is gross typing to make it invalid. Might be possible to do better. + const androidApps: any = { ...app }; delete androidApps.platform; return androidApps; }); @@ -507,7 +509,8 @@ describe("App management", () => { const appCounts = 10; const expectedAppList = generateWebAppList(appCounts); const apiResponseAppList = expectedAppList.map((app) => { - const webApp = { ...app }; + // TODO: this is gross typing to make it invalid. Might be possible to do better. + const webApp: any = { ...app }; delete webApp.platform; return webApp; }); @@ -557,7 +560,7 @@ describe("App management", () => { let err; try { await listFirebaseApps(PROJECT_ID, AppPlatform.ANY); - } catch (e) { + } catch (e: any) { err = e; } @@ -586,7 +589,7 @@ describe("App management", () => { let err; try { await listFirebaseApps(PROJECT_ID, AppPlatform.ANY, pageSize); - } catch (e) { + } catch (e: any) { err = e; } @@ -611,7 +614,7 @@ describe("App management", () => { let err; try { await listFirebaseApps(PROJECT_ID, AppPlatform.IOS); - } catch (e) { + } catch (e: any) { err = e; } @@ -632,7 +635,7 @@ describe("App management", () => { let err; try { await listFirebaseApps(PROJECT_ID, AppPlatform.ANDROID); - } catch (e) { + } catch (e: any) { err = e; } @@ -653,7 +656,7 @@ describe("App management", () => { let err; try { await listFirebaseApps(PROJECT_ID, AppPlatform.WEB); - } catch (e) { + } catch (e: any) { err = e; } @@ -774,7 +777,7 @@ describe("App management", () => { let err; try { await getAppConfig(APP_ID, AppPlatform.ANDROID); - } catch (e) { + } catch (e: any) { err = e; } diff --git a/src/test/management/database.spec.ts b/src/test/management/database.spec.ts index a2b7547cfb4..fa50128d67e 100644 --- a/src/test/management/database.spec.ts +++ b/src/test/management/database.spec.ts @@ -129,7 +129,7 @@ describe("Database management", () => { let err; try { await getDatabaseInstanceDetails(PROJECT_ID, badInstanceName); - } catch (e) { + } catch (e: any) { err = e; } expect(err.message).to.equal( @@ -186,7 +186,7 @@ describe("Database management", () => { DatabaseLocation.US_CENTRAL1, DatabaseInstanceType.DEFAULT_DATABASE ); - } catch (e) { + } catch (e: any) { err = e; } @@ -302,7 +302,7 @@ describe("Database management", () => { DatabaseInstanceType.DEFAULT_DATABASE, DatabaseLocation.US_CENTRAL1 ); - } catch (e) { + } catch (e: any) { err = e; } @@ -442,7 +442,7 @@ describe("Database management", () => { let err; try { await listDatabaseInstances(PROJECT_ID, DatabaseLocation.ANY); - } catch (e) { + } catch (e: any) { err = e; } @@ -483,7 +483,7 @@ describe("Database management", () => { let err; try { await listDatabaseInstances(PROJECT_ID, DatabaseLocation.US_CENTRAL1, pageSize); - } catch (e) { + } catch (e: any) { err = e; } diff --git a/src/test/management/projects.spec.ts b/src/test/management/projects.spec.ts index 7280c213ef4..8e92f97aeb1 100644 --- a/src/test/management/projects.spec.ts +++ b/src/test/management/projects.spec.ts @@ -149,7 +149,7 @@ describe("Project management", () => { let err; try { await projectManager.getOrPromptProject({}); - } catch (e) { + } catch (e: any) { err = e; } @@ -182,7 +182,7 @@ describe("Project management", () => { let err; try { await projectManager.getOrPromptProject(options); - } catch (e) { + } catch (e: any) { err = e; } @@ -243,7 +243,7 @@ describe("Project management", () => { let err; try { await projectManager.promptAvailableProjectId(); - } catch (e) { + } catch (e: any) { err = e; } @@ -297,7 +297,7 @@ describe("Project management", () => { displayName: PROJECT_NAME, parentResource: PARENT_RESOURCE, }); - } catch (e) { + } catch (e: any) { err = e; } @@ -325,7 +325,7 @@ describe("Project management", () => { displayName: PROJECT_NAME, parentResource: PARENT_RESOURCE, }); - } catch (e) { + } catch (e: any) { err = e; } @@ -383,7 +383,7 @@ describe("Project management", () => { let err; try { await projectManager.addFirebaseToCloudProject(PROJECT_ID); - } catch (e) { + } catch (e: any) { err = e; } @@ -411,7 +411,7 @@ describe("Project management", () => { let err; try { await projectManager.addFirebaseToCloudProject(PROJECT_ID); - } catch (e) { + } catch (e: any) { err = e; } @@ -494,7 +494,7 @@ describe("Project management", () => { let err; try { await projectManager.getAvailableCloudProjectPage(pageSize, PAGE_TOKEN); - } catch (e) { + } catch (e: any) { err = e; } @@ -563,7 +563,7 @@ describe("Project management", () => { let err; try { await projectManager.getFirebaseProjectPage(pageSize, PAGE_TOKEN); - } catch (e) { + } catch (e: any) { err = e; } @@ -621,7 +621,7 @@ describe("Project management", () => { let err; try { await projectManager.listFirebaseProjects(); - } catch (e) { + } catch (e: any) { err = e; } @@ -649,7 +649,7 @@ describe("Project management", () => { let err; try { await projectManager.listFirebaseProjects(pageSize); - } catch (e) { + } catch (e: any) { err = e; } @@ -693,7 +693,7 @@ describe("Project management", () => { let err; try { await projectManager.getFirebaseProject(PROJECT_ID); - } catch (e) { + } catch (e: any) { err = e; } diff --git a/src/test/operation-poller.spec.ts b/src/test/operation-poller.spec.ts index b6c118d56db..4edb028d833 100644 --- a/src/test/operation-poller.spec.ts +++ b/src/test/operation-poller.spec.ts @@ -51,7 +51,7 @@ describe("OperationPoller", () => { let err; try { await pollOperation(pollerOptions); - } catch (e) { + } catch (e: any) { err = e; } expect(err.message).to.equal("failed"); @@ -93,7 +93,7 @@ describe("OperationPoller", () => { let error; try { await pollOperation(pollerOptions); - } catch (err) { + } catch (err: any) { error = err; } expect(error).to.be.instanceOf(TimeoutError); diff --git a/src/test/remoteconfig/get.spec.ts b/src/test/remoteconfig/get.spec.ts index fa96bbb3c75..de6a3c82d41 100644 --- a/src/test/remoteconfig/get.spec.ts +++ b/src/test/remoteconfig/get.spec.ts @@ -162,7 +162,7 @@ describe("Remote Config GET", () => { let err; try { await remoteconfig.getTemplate(PROJECT_ID); - } catch (e) { + } catch (e: any) { err = e; } diff --git a/src/test/remoteconfig/rollback.spec.ts b/src/test/remoteconfig/rollback.spec.ts index cc1624e95aa..19bf3ec942d 100644 --- a/src/test/remoteconfig/rollback.spec.ts +++ b/src/test/remoteconfig/rollback.spec.ts @@ -81,7 +81,7 @@ describe("RemoteConfig Rollback", () => { ); try { await remoteconfig.rollbackTemplate(PROJECT_ID); - } catch (e) { + } catch (e: any) { e; } }); @@ -106,7 +106,7 @@ describe("RemoteConfig Rollback", () => { it("should reject if the api call fails", async () => { try { await remoteconfig.rollbackTemplate(PROJECT_ID); - } catch (e) { + } catch (e: any) { e; } diff --git a/src/test/remoteconfig/versionslist.spec.ts b/src/test/remoteconfig/versionslist.spec.ts index 55bcf06c683..84c11b10baf 100644 --- a/src/test/remoteconfig/versionslist.spec.ts +++ b/src/test/remoteconfig/versionslist.spec.ts @@ -119,7 +119,7 @@ describe("RemoteConfig ListVersions", () => { let err; try { await remoteconfig.getVersions(PROJECT_ID); - } catch (e) { + } catch (e: any) { err = e; } diff --git a/src/test/throttler/throttler.spec.ts b/src/test/throttler/throttler.spec.ts index 86429ec3089..a717f2ed2bd 100644 --- a/src/test/throttler/throttler.spec.ts +++ b/src/test/throttler/throttler.spec.ts @@ -280,7 +280,7 @@ const throttlerTest = (ThrottlerConstructor: ThrottlerConstructorType): void => let err; try { await q.run(2, 100); - } catch (e) { + } catch (e: any) { err = e; } expect(err).to.be.instanceOf(TimeoutError); @@ -299,7 +299,7 @@ const throttlerTest = (ThrottlerConstructor: ThrottlerConstructorType): void => let err; try { await q.run(2, 200); - } catch (e) { + } catch (e: any) { err = e; } expect(err).to.be.instanceOf(RetriesExhaustedError); @@ -327,7 +327,7 @@ const throttlerTest = (ThrottlerConstructor: ThrottlerConstructorType): void => let err; try { await q.run(2, 100); - } catch (e) { + } catch (e: any) { err = e; } expect(err).to.be.instanceOf(TimeoutError); @@ -357,7 +357,7 @@ const throttlerTest = (ThrottlerConstructor: ThrottlerConstructorType): void => let err; try { await q.wait(); - } catch (e) { + } catch (e: any) { err = e; } expect(err).to.be.instanceOf(TimeoutError); @@ -385,7 +385,7 @@ const throttlerTest = (ThrottlerConstructor: ThrottlerConstructorType): void => let err; try { await q.wait(); - } catch (e) { + } catch (e: any) { err = e; } expect(err).to.be.instanceOf(RetriesExhaustedError); diff --git a/src/throttler/throttler.ts b/src/throttler/throttler.ts index 452d70ab8a1..d27d8592290 100644 --- a/src/throttler/throttler.ts +++ b/src/throttler/throttler.ts @@ -170,7 +170,7 @@ export abstract class Throttler { let result; try { result = await Promise.race(promises); - } catch (err) { + } catch (err: any) { this.errored++; this.complete++; this.active--; @@ -266,7 +266,7 @@ export abstract class Throttler { let result; try { result = await this.handler(taskData.task); - } catch (err) { + } catch (err: any) { if (taskData.retryCount === this.retries) { throw new RetriesExhaustedError(this.taskName(cursorIndex), this.retries, err); } diff --git a/src/utils.ts b/src/utils.ts index 033ebad392e..0853b31e22d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -61,7 +61,7 @@ export function envOverride( if (coerce) { try { return coerce(currentEnvValue, value); - } catch (e) { + } catch (e: any) { return value; } } @@ -369,7 +369,7 @@ export function promiseAllSettled(promises: Array>): Promise( return resolve(res); } setTimeout(run, interval); - } catch (err) { + } catch (err: any) { return promiseReject(err); } }; @@ -480,7 +480,7 @@ export async function promiseWithSpinner(action: () => Promise, message: s try { data = await action(); spinner.succeed(); - } catch (err) { + } catch (err: any) { spinner.fail(); throw err; } diff --git a/standalone/firepit.js b/standalone/firepit.js index 137895d4ff7..926757b7b90 100644 --- a/standalone/firepit.js +++ b/standalone/firepit.js @@ -145,7 +145,7 @@ const runtime = require("./runtime"); let config; try { config = require("./config"); -} catch (err) { +} catch (err: any) { console.warn("Invalid Firepit configuration, this may be a broken build."); process.exit(2); } @@ -684,7 +684,7 @@ node "${FindTool("npm/bin/npm-cli")[0]}" ${npmArgs.join(" ")} %*`, try { shell.mkdir("-p", runtimeBinsPath); - } catch (err) { + } catch (err: any) { debug(err); } @@ -693,7 +693,7 @@ node "${FindTool("npm/bin/npm-cli")[0]}" ${npmArgs.join(" ")} %*`, const runtimeBinPath = path.join(runtimeBinsPath, filename); try { shell.rm("-rf", runtimeBinPath); - } catch (err) { + } catch (err: any) { debug(err); } fs.writeFileSync(runtimeBinPath, runtimeBins[filename]); @@ -840,7 +840,7 @@ function uninstallLegacyFirepit() { installedFirebaseToolsPackage = JSON.parse( shell.cat(installedFirebaseToolsPackagePath) ); - } catch (err) { + } catch (err: any) { debug("No existing firebase-tools install found."); } diff --git a/templates/init/hosting/index.html b/templates/init/hosting/index.html index 60e92365e8a..daca6dda5d8 100644 --- a/templates/init/hosting/index.html +++ b/templates/init/hosting/index.html @@ -79,7 +79,7 @@

Firebase Hosting Setup Complete

'performance', ].filter(feature => typeof app[feature] === 'function'); loadEl.textContent = `Firebase SDK loaded with ${features.join(', ')}`; - } catch (e) { + } catch (e: any) { console.error(e); loadEl.textContent = 'Error loading the Firebase SDK, check the console.'; } From 8998185d4386d93f323f4bbcb940c7a59da72171 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 5 Jan 2022 11:22:11 -0600 Subject: [PATCH 0014/1699] upgrade exegesis* packages and regenerate api to update messages (#3977) * upgrade typescript to 4.5 * remove type from js file * fix missing * fix a couple more error types * upgrade exegesis* packages and regenerate api to update messages * Disable byte validation and fix ajv errors. Co-authored-by: Yuchen Shi --- package-lock.json | 180 ++++++++++-------- package.json | 4 +- src/emulator/auth/apiSpec.js | 22 ++- src/emulator/auth/schema.ts | 16 +- src/emulator/auth/server.ts | 6 + src/emulator/functionsEmulatorRuntime.ts | 2 +- src/test/emulators/auth/batch.spec.ts | 1 - .../emulators/auth/setAccountInfo.spec.ts | 2 +- 8 files changed, 137 insertions(+), 96 deletions(-) diff --git a/package-lock.json b/package-lock.json index 992370c5189..b334f9fc0ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,8 +26,8 @@ "cross-spawn": "^7.0.1", "csv-streamify": "^3.0.4", "dotenv": "^6.1.0", - "exegesis": "^2.5.7", - "exegesis-express": "^2.0.0", + "exegesis": "^4.1.0", + "exegesis-express": "^4.0.0", "exit-code": "^1.0.2", "express": "^4.16.4", "filesize": "^6.1.0", @@ -2956,6 +2956,42 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -5596,18 +5632,19 @@ "integrity": "sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g==" }, "node_modules/exegesis": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/exegesis/-/exegesis-2.5.7.tgz", - "integrity": "sha512-Y0gEY3hgoLa80aMUm8rhhlIW3/KWo4uqN5hKJqok2GLh3maZjRLRC+p0gj33Jw3upAOKOXeRgScT5rtRoMyxwQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/exegesis/-/exegesis-4.1.0.tgz", + "integrity": "sha512-iqc55n+hmv8d1KYNMjq7bCcp4u74oRY6MBcj6Vsux7Wd4mRvlgahKqrBTyLIWwscNjEF3qvPmeJ0RPTj8ORMNg==", "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.3", - "ajv": "^6.12.2", + "ajv": "^8.3.0", + "ajv-formats": "^2.1.0", "body-parser": "^1.18.3", "content-type": "^1.0.4", "deep-freeze": "0.0.1", "events-listener": "^1.1.0", "glob": "^7.1.3", - "json-ptr": "^2.2.0", + "json-ptr": "^3.0.1", "json-schema-traverse": "^1.0.0", "lodash": "^4.17.11", "openapi3-ts": "^2.0.1", @@ -5623,11 +5660,11 @@ } }, "node_modules/exegesis-express": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/exegesis-express/-/exegesis-express-2.0.0.tgz", - "integrity": "sha512-NKvKBsBa2OvU+1BFpWbz3PzoRMhA9q7/wU2oMmQ9X8lPy/FRatADvhlkGO1zYOMgeo35k1ZLO9ZV0uIs9pPnXg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/exegesis-express/-/exegesis-express-4.0.0.tgz", + "integrity": "sha512-V2hqwTtYRj0bj43K4MCtm0caD97YWkqOUHFMRCBW5L1x9IjyqOEc7Xa4oQjjiFbeFOSQzzwPV+BzXsQjSz08fw==", "dependencies": { - "exegesis": "^2.0.0" + "exegesis": "^4.1.0" }, "engines": { "node": ">=6.0.0", @@ -5635,13 +5672,13 @@ } }, "node_modules/exegesis/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -5649,16 +5686,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/exegesis/node_modules/ajv/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/exegesis/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==" - }, "node_modules/exegesis/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -7927,17 +7954,9 @@ } }, "node_modules/json-ptr": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json-ptr/-/json-ptr-2.2.0.tgz", - "integrity": "sha512-w9f6/zhz4kykltXMG7MLJWMajxiPj0q+uzQPR1cggNAE/sXoq/C5vjUb/7QNcC3rJsVIIKy37ALTXy1O+3c8QQ==", - "dependencies": { - "tslib": "^2.2.0" - } - }, - "node_modules/json-ptr/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-ptr/-/json-ptr-3.0.1.tgz", + "integrity": "sha512-hrZ4tElT8huJUH3OwOK+d7F8PRqw09QnGM3Mm3GmqKWDyCCPCG8lGHxXOwQAj0VOxzLirOds07Kz10B5F8M8EA==" }, "node_modules/json-schema": { "version": "0.2.3", @@ -11261,7 +11280,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -16787,6 +16805,32 @@ "uri-js": "^4.2.2" } }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -18858,18 +18902,19 @@ "integrity": "sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g==" }, "exegesis": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/exegesis/-/exegesis-2.5.7.tgz", - "integrity": "sha512-Y0gEY3hgoLa80aMUm8rhhlIW3/KWo4uqN5hKJqok2GLh3maZjRLRC+p0gj33Jw3upAOKOXeRgScT5rtRoMyxwQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/exegesis/-/exegesis-4.1.0.tgz", + "integrity": "sha512-iqc55n+hmv8d1KYNMjq7bCcp4u74oRY6MBcj6Vsux7Wd4mRvlgahKqrBTyLIWwscNjEF3qvPmeJ0RPTj8ORMNg==", "requires": { "@apidevtools/json-schema-ref-parser": "^9.0.3", - "ajv": "^6.12.2", + "ajv": "^8.3.0", + "ajv-formats": "^2.1.0", "body-parser": "^1.18.3", "content-type": "^1.0.4", "deep-freeze": "0.0.1", "events-listener": "^1.1.0", "glob": "^7.1.3", - "json-ptr": "^2.2.0", + "json-ptr": "^3.0.1", "json-schema-traverse": "^1.0.0", "lodash": "^4.17.11", "openapi3-ts": "^2.0.1", @@ -18881,28 +18926,16 @@ }, "dependencies": { "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" - }, - "dependencies": { - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - } } }, - "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==" - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -18919,11 +18952,11 @@ } }, "exegesis-express": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/exegesis-express/-/exegesis-express-2.0.0.tgz", - "integrity": "sha512-NKvKBsBa2OvU+1BFpWbz3PzoRMhA9q7/wU2oMmQ9X8lPy/FRatADvhlkGO1zYOMgeo35k1ZLO9ZV0uIs9pPnXg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/exegesis-express/-/exegesis-express-4.0.0.tgz", + "integrity": "sha512-V2hqwTtYRj0bj43K4MCtm0caD97YWkqOUHFMRCBW5L1x9IjyqOEc7Xa4oQjjiFbeFOSQzzwPV+BzXsQjSz08fw==", "requires": { - "exegesis": "^2.0.0" + "exegesis": "^4.1.0" } }, "exit-code": { @@ -20755,19 +20788,9 @@ } }, "json-ptr": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json-ptr/-/json-ptr-2.2.0.tgz", - "integrity": "sha512-w9f6/zhz4kykltXMG7MLJWMajxiPj0q+uzQPR1cggNAE/sXoq/C5vjUb/7QNcC3rJsVIIKy37ALTXy1O+3c8QQ==", - "requires": { - "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - } - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-ptr/-/json-ptr-3.0.1.tgz", + "integrity": "sha512-hrZ4tElT8huJUH3OwOK+d7F8PRqw09QnGM3Mm3GmqKWDyCCPCG8lGHxXOwQAj0VOxzLirOds07Kz10B5F8M8EA==" }, "json-schema": { "version": "0.2.3", @@ -23382,8 +23405,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "require-main-filename": { "version": "2.0.0", diff --git a/package.json b/package.json index 9e3b3144970..1b0b88af8d7 100644 --- a/package.json +++ b/package.json @@ -101,8 +101,8 @@ "cross-spawn": "^7.0.1", "csv-streamify": "^3.0.4", "dotenv": "^6.1.0", - "exegesis": "^2.5.7", - "exegesis-express": "^2.0.0", + "exegesis": "^4.1.0", + "exegesis-express": "^4.0.0", "exit-code": "^1.0.2", "express": "^4.16.4", "filesize": "^6.1.0", diff --git a/src/emulator/auth/apiSpec.js b/src/emulator/auth/apiSpec.js index f7990fa3caf..fb042263c1a 100644 --- a/src/emulator/auth/apiSpec.js +++ b/src/emulator/auth/apiSpec.js @@ -4383,7 +4383,7 @@ export default { }, email: { description: - "The account's email address to send the OOB code to, and generally the email address of the account that needs to be updated. Required for PASSWORD_RESET, EMAIL_SIGNIN, and VERIFY_EMAIL.", + "The account's email address to send the OOB code to, and generally the email address of the account that needs to be updated. Required for PASSWORD_RESET, EMAIL_SIGNIN, and VERIFY_EMAIL. Only required for VERIFY_AND_CHANGE_EMAIL requests when return_oob_link is set to true. In this case, it is the original email of the user.", type: "string", }, iOSAppStoreId: { @@ -4396,11 +4396,19 @@ export default { "If an associated iOS app can handle the OOB code, the iOS bundle id of this app. This will allow the correct app to open if it is already installed.", type: "string", }, - idToken: { type: "string" }, - newEmail: { type: "string" }, + idToken: { + description: + "An ID token for the account. It is required for VERIFY_AND_CHANGE_EMAIL and VERIFY_EMAIL requests unless return_oob_link is set to true.", + type: "string", + }, + newEmail: { + description: + "The email address the account is being updated to. Required only for VERIFY_AND_CHANGE_EMAIL requests.", + type: "string", + }, requestType: { description: - "Required. The type of out-of-band (OOB) code to send. Depending on this value, other fields in this request will be required and/or have different meanings. There are 3 different OOB codes that can be sent: * PASSWORD_RESET * EMAIL_SIGNIN * VERIFY_EMAIL", + "Required. The type of out-of-band (OOB) code to send. Depending on this value, other fields in this request will be required and/or have different meanings. There are 4 different OOB codes that can be sent: * PASSWORD_RESET * EMAIL_SIGNIN * VERIFY_EMAIL * VERIFY_AND_CHANGE_EMAIL", enum: [ "OOB_REQ_TYPE_UNSPECIFIED", "PASSWORD_RESET", @@ -5482,7 +5490,7 @@ export default { captchaChallenge: { type: "string" }, captchaResponse: { description: - "The response from a reCaptcha challenge. A recaptcha response is required when the service detects possible abuse activity.", + "The reCAPTCHA token provided by the reCAPTCHA client-side integration. reCAPTCHA Enterprise uses it for risk assessment. Required when reCAPTCHA Enterprise is enabled.", type: "string", }, delegatedProjectNumber: { format: "int64", type: "string" }, @@ -5659,7 +5667,7 @@ export default { captchaChallenge: { type: "string" }, captchaResponse: { description: - "The response from a reCaptcha challenge. A reCaptcha response is required when the service detects potential abuse activity.", + "The reCAPTCHA token provided by the reCAPTCHA client-side integration. reCAPTCHA Enterprise uses it for assessment. Required when reCAPTCHA enterprise is enabled.", type: "string", }, disabled: { @@ -7123,7 +7131,7 @@ export default { properties: { requestedPolicyVersion: { description: - "Optional. The policy format version to be returned. Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. Requests for policies with any conditional bindings must specify version 3. Policies without any conditional bindings may specify any valid value or leave the field unset. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies).", + "Optional. The maximum policy version that will be used to format the policy. Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. Requests for policies with any conditional role bindings must specify version 3. Policies with no conditional role bindings may specify any valid value or leave the field unset. The policy in the response might use the policy version that you specified, or it might use a lower policy version. For example, if you specify version 3, but the policy has no conditional role bindings, the response uses version 1. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies).", format: "int32", type: "integer", }, diff --git a/src/emulator/auth/schema.ts b/src/emulator/auth/schema.ts index 7c3ae4d0aaf..d40d54f0603 100644 --- a/src/emulator/auth/schema.ts +++ b/src/emulator/auth/schema.ts @@ -397,7 +397,7 @@ export interface components { */ dynamicLinkDomain?: string; /** - * The account's email address to send the OOB code to, and generally the email address of the account that needs to be updated. Required for PASSWORD_RESET, EMAIL_SIGNIN, and VERIFY_EMAIL. + * The account's email address to send the OOB code to, and generally the email address of the account that needs to be updated. Required for PASSWORD_RESET, EMAIL_SIGNIN, and VERIFY_EMAIL. Only required for VERIFY_AND_CHANGE_EMAIL requests when return_oob_link is set to true. In this case, it is the original email of the user. */ email?: string; /** @@ -408,10 +408,16 @@ export interface components { * If an associated iOS app can handle the OOB code, the iOS bundle id of this app. This will allow the correct app to open if it is already installed. */ iOSBundleId?: string; + /** + * An ID token for the account. It is required for VERIFY_AND_CHANGE_EMAIL and VERIFY_EMAIL requests unless return_oob_link is set to true. + */ idToken?: string; + /** + * The email address the account is being updated to. Required only for VERIFY_AND_CHANGE_EMAIL requests. + */ newEmail?: string; /** - * Required. The type of out-of-band (OOB) code to send. Depending on this value, other fields in this request will be required and/or have different meanings. There are 3 different OOB codes that can be sent: * PASSWORD_RESET * EMAIL_SIGNIN * VERIFY_EMAIL + * Required. The type of out-of-band (OOB) code to send. Depending on this value, other fields in this request will be required and/or have different meanings. There are 4 different OOB codes that can be sent: * PASSWORD_RESET * EMAIL_SIGNIN * VERIFY_EMAIL * VERIFY_AND_CHANGE_EMAIL */ requestType?: | "OOB_REQ_TYPE_UNSPECIFIED" @@ -1375,7 +1381,7 @@ export interface components { GoogleCloudIdentitytoolkitV1SignInWithPasswordRequest: { captchaChallenge?: string; /** - * The response from a reCaptcha challenge. A recaptcha response is required when the service detects possible abuse activity. + * The reCAPTCHA token provided by the reCAPTCHA client-side integration. reCAPTCHA Enterprise uses it for risk assessment. Required when reCAPTCHA Enterprise is enabled. */ captchaResponse?: string; delegatedProjectNumber?: string; @@ -1539,7 +1545,7 @@ export interface components { GoogleCloudIdentitytoolkitV1SignUpRequest: { captchaChallenge?: string; /** - * The response from a reCaptcha challenge. A reCaptcha response is required when the service detects potential abuse activity. + * The reCAPTCHA token provided by the reCAPTCHA client-side integration. reCAPTCHA Enterprise uses it for assessment. Required when reCAPTCHA enterprise is enabled. */ captchaResponse?: string; /** @@ -2803,7 +2809,7 @@ export interface components { */ GoogleIamV1GetPolicyOptions: { /** - * Optional. The policy format version to be returned. Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. Requests for policies with any conditional bindings must specify version 3. Policies without any conditional bindings may specify any valid value or leave the field unset. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). + * Optional. The maximum policy version that will be used to format the policy. Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. Requests for policies with any conditional role bindings must specify version 3. Policies with no conditional role bindings may specify any valid value or leave the field unset. The policy in the response might use the policy version that you specified, or it might use a lower policy version. For example, if you specify version 3, but the policy has no conditional role bindings, the response uses version 1. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). */ requestedPolicyVersion?: number; }; diff --git a/src/emulator/auth/server.ts b/src/emulator/auth/server.ts index e3d526ce629..6e386928886 100644 --- a/src/emulator/auth/server.ts +++ b/src/emulator/auth/server.ts @@ -271,6 +271,12 @@ export async function createApp( // TODO return true; }, + byte() { + // Disable the "byte" format validation to allow stuffing arbitary + // strings in passwordHash etc. Needed because the emulator generates + // non-base64 hash strings like "fakeHash:salt=foo:password=bar". + return true; + }, }, plugins: [ { diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 76ac894001f..5de8a03a638 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -166,7 +166,7 @@ class Proxied { }, apply: (target, thisArg, argArray) => { if (this.appliedValue) { - return this.appliedValue.apply(thisArg, argArray); + return this.appliedValue.apply(thisArg); } else { return Proxied.applyOriginal(target, thisArg, argArray); } diff --git a/src/test/emulators/auth/batch.spec.ts b/src/test/emulators/auth/batch.spec.ts index bd17ff61557..f89604831f7 100644 --- a/src/test/emulators/auth/batch.spec.ts +++ b/src/test/emulators/auth/batch.spec.ts @@ -159,7 +159,6 @@ describeAuthEmulator("accounts:batchGet", ({ authApi }) => { .set("Authorization", "Bearer owner") .then((res) => { expectStatusCode(200, res); - console.log(nextPageToken, res.body.users); // Empty page with no page token returned. expect(res.body.users || []).to.have.length(0); expect(res.body).not.to.have.property("nextPageToken"); diff --git a/src/test/emulators/auth/setAccountInfo.spec.ts b/src/test/emulators/auth/setAccountInfo.spec.ts index 876056a4806..5cc56992a13 100644 --- a/src/test/emulators/auth/setAccountInfo.spec.ts +++ b/src/test/emulators/auth/setAccountInfo.spec.ts @@ -919,7 +919,7 @@ describeAuthEmulator("accounts:update", ({ authApi, getClock }) => { .then((res) => { expectStatusCode(400, res); expect(res.body.error.message).to.eq( - "Invalid JSON payload received. /mfa/enrollments should be array" + "Invalid JSON payload received. /mfa/enrollments must be array" ); }); }); From e05d2e0a7825b0716a9c2a15a701880db5290a4b Mon Sep 17 00:00:00 2001 From: Avi Vahl Date: Wed, 5 Jan 2022 20:07:23 +0200 Subject: [PATCH 0015/1699] security(deps): upgrade to ora@5.4.1 (#3981) - last stable version before the ESM-only ora@6 - fixes *one* of the dependency vulnerabilities `firebase-tools` package has (CVE-2021-3807) Co-authored-by: Bryan Kendall --- package-lock.json | 435 ++++++++++++++++++++++++++-- package.json | 2 +- src/commands/ext-configure.ts | 2 +- src/commands/ext-install.ts | 2 +- src/commands/ext-uninstall.ts | 2 +- src/commands/ext-update.ts | 4 +- src/extensions/extensionsHelper.ts | 6 +- src/init/features/hosting/github.ts | 4 +- 8 files changed, 413 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index b334f9fc0ae..d3a90a6efd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "morgan": "^1.10.0", "node-fetch": "^2.6.1", "open": "^6.3.0", - "ora": "^3.4.0", + "ora": "^5.4.1", "portfinder": "^1.0.23", "progress": "^2.0.3", "proxy-agent": "^5.0.0", @@ -3999,11 +3999,14 @@ } }, "node_modules/cli-spinners": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", - "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-table": { @@ -7602,6 +7605,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", @@ -7658,6 +7669,17 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", @@ -8419,14 +8441,82 @@ } }, "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/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==", "dependencies": { - "chalk": "^2.0.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/log-symbols/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/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==" + }, + "node_modules/log-symbols/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/logform": { @@ -10230,19 +10320,153 @@ } }, "node_modules/ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "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", + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/ora/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/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==" + }, + "node_modules/ora/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ora/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/os-tmpdir": { @@ -17631,9 +17855,9 @@ } }, "cli-spinners": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", - "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==" + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==" }, "cli-table": { "version": "0.3.11", @@ -20505,6 +20729,11 @@ "is-path-inside": "^3.0.1" } }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, "is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", @@ -20546,6 +20775,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, "is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", @@ -21197,11 +21431,57 @@ } }, "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "requires": { - "chalk": "^2.0.1" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } } }, "logform": { @@ -22601,16 +22881,107 @@ } }, "ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "requires": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.2.0", + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } } }, "os-tmpdir": { diff --git a/package.json b/package.json index 1b0b88af8d7..85f8fba3be4 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "morgan": "^1.10.0", "node-fetch": "^2.6.1", "open": "^6.3.0", - "ora": "^3.4.0", + "ora": "^5.4.1", "portfinder": "^1.0.23", "progress": "^2.0.3", "proxy-agent": "^5.0.0", diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 51d72b24497..d22660bcbf3 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -32,7 +32,7 @@ export default new Command("ext:configure ") ]) .before(checkMinRequiredVersion, "extMinVersion") .action(async (instanceId: string, options: any) => { - const spinner = ora.default( + const spinner = ora( `Configuring ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...` ); try { diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index db6a77f2140..f037acf9624 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -68,7 +68,7 @@ async function installExtension(options: InstallExtensionOptions): Promise `Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.` ); } - const spinner = ora.default(); + const spinner = ora(); try { await provisioningHelper.checkProductsProvisioned(projectId, spec); diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 0e1af789d8f..6e6f29d2cd2 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -111,7 +111,7 @@ export default new Command("ext:uninstall ") } } - const spinner = ora.default( + const spinner = ora( ` ${clc.green.bold(logPrefix)}: uninstalling ${clc.bold( instanceId )}. This usually takes 1 to 2 minutes...` diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 01646fa6e3c..e19e55f5348 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -71,9 +71,7 @@ export default new Command("ext:update [updateSource]") .withForce() .option("--params ", "name of params variables file with .env format.") .action(async (instanceId: string, updateSource: string, options: any) => { - const spinner = ora.default( - `Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...` - ); + const spinner = ora(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`); try { const projectId = needProjectId(options); let existingInstance: extensionsApi.ExtensionInstance; diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 78699d78e0d..e8081223f8a 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -481,7 +481,7 @@ export async function publishExtensionVersionFromLocalSource(args: { const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`; let packageUri: string; let objectPath = ""; - const uploadSpinner = ora.default(" Archiving and uploading extension source code"); + const uploadSpinner = ora(" Archiving and uploading extension source code"); try { uploadSpinner.start(); objectPath = await archiveAndUploadSource(args.rootDirectory, EXTENSIONS_BUCKET_NAME); @@ -491,7 +491,7 @@ export async function publishExtensionVersionFromLocalSource(args: { uploadSpinner.fail(); throw err; } - const publishSpinner = ora.default(`Publishing ${clc.bold(ref)}`); + const publishSpinner = ora(`Publishing ${clc.bold(ref)}`); let res; try { publishSpinner.start(); @@ -528,7 +528,7 @@ export async function createSourceFromLocation( let extensionRoot: string; let objectPath = ""; if (!URL_REGEX.test(sourceUri)) { - const uploadSpinner = ora.default(" Archiving and uploading extension source code"); + const uploadSpinner = ora(" Archiving and uploading extension source code"); try { uploadSpinner.start(); objectPath = await archiveAndUploadSource(sourceUri, EXTENSIONS_BUCKET_NAME); diff --git a/src/init/features/hosting/github.ts b/src/init/features/hosting/github.ts index 4453309c9a2..5b581a7d479 100644 --- a/src/init/features/hosting/github.ts +++ b/src/init/features/hosting/github.ts @@ -109,7 +109,7 @@ export async function initGitHub(setup: Setup, config: any, options: any): Promi `Created service account ${bold(serviceAccountName)} with Firebase Hosting admin permissions.` ); - const spinnerSecrets = ora.default(`Uploading service account secrets to repository: ${repo}`); + const spinnerSecrets = ora(`Uploading service account secrets to repository: ${repo}`); spinnerSecrets.start(); const encryptedServiceAccountJSON = encryptServiceAccountJSON(serviceAccountJSON, key); @@ -539,7 +539,7 @@ async function createServiceAccountAndKeyWithRetry( repo: string, accountId: string ): Promise { - const spinnerServiceAccount = ora.default("Retrieving a service account."); + const spinnerServiceAccount = ora("Retrieving a service account."); spinnerServiceAccount.start(); try { From e5d55e2b4745f5a462f1155c629cb7011315391c Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 5 Jan 2022 14:33:30 -0600 Subject: [PATCH 0016/1699] increase auth emulator timeout (#3986) --- src/test/emulators/auth/setup.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/emulators/auth/setup.ts b/src/test/emulators/auth/setup.ts index b9ac28b1758..c50e6549d5c 100644 --- a/src/test/emulators/auth/setup.ts +++ b/src/test/emulators/auth/setup.ts @@ -19,8 +19,11 @@ export function describeAuthEmulator( return describe(`Auth Emulator: ${title}`, function (this) { let authApp: Express.Application; beforeEach("setup or reuse auth server", async function (this) { - this.timeout(10000); + this.timeout(20000); + const t0 = new Date(); authApp = await createOrReuseApp(); + const t1 = new Date(); + console.log(`it took ${(t1.valueOf() - t0.valueOf()) / 1000}s to get the app`); }); let clock: sinon.SinonFakeTimers; From bf9021e2b4b4d4846ac8fe180daa95befeb0aa85 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 5 Jan 2022 14:56:30 -0600 Subject: [PATCH 0017/1699] updates inquirer; updates a couple packages in package-json for audit (#3985) * easy audit fix first * upgrade inquirer --- package-lock.json | 2886 +++++++-------------------- package.json | 4 +- src/extensions/askUserForConsent.ts | 9 +- src/management/projects.ts | 3 +- src/prompt.ts | 14 +- 5 files changed, 785 insertions(+), 2131 deletions(-) diff --git a/package-lock.json b/package-lock.json index d3a90a6efd6..ae02e6debad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "fs-extra": "^5.0.0", "glob": "^7.1.2", "google-auth-library": "^7.11.0", - "inquirer": "~6.3.1", + "inquirer": "^8.2.0", "js-yaml": "^3.13.1", "JSONStream": "^1.2.1", "jsonwebtoken": "^8.5.1", @@ -88,7 +88,7 @@ "@types/express-serve-static-core": "^4.17.8", "@types/fs-extra": "^5.0.5", "@types/glob": "^7.1.1", - "@types/inquirer": "^6.0.3", + "@types/inquirer": "^8.1.3", "@types/js-yaml": "^3.12.2", "@types/jsonwebtoken": "^8.3.8", "@types/lodash": "^4.14.149", @@ -501,22 +501,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "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" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@eslint/eslintrc/node_modules/debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -534,12 +518,6 @@ } } }, - "node_modules/@eslint/eslintrc/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==", - "dev": true - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -1165,13 +1143,6 @@ "node": ">=10.10.0" } }, - "node_modules/@google-cloud/firestore/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==", - "dev": true, - "optional": true - }, "node_modules/@google-cloud/paginator": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz", @@ -1316,45 +1287,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@google-cloud/storage/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true, - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/@google-cloud/storage/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, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@google-cloud/storage/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, - "optional": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@google-cloud/storage/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1422,14 +1354,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.9.tgz", "integrity": "sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q==" }, - "node_modules/@grpc/grpc-js/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "engines": { - "node": ">=8" - } - }, "node_modules/@grpc/grpc-js/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1470,11 +1394,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/@grpc/grpc-js/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==" - }, "node_modules/@grpc/grpc-js/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -1606,17 +1525,6 @@ "node": ">=8" } }, - "node_modules/@grpc/grpc-js/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@grpc/grpc-js/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -1829,18 +1737,6 @@ "node": ">=8" } }, - "node_modules/@manifoldco/swagger-to-ts/node_modules/prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@manifoldco/swagger-to-ts/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2248,13 +2144,13 @@ } }, "node_modules/@types/inquirer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.0.3.tgz", - "integrity": "sha512-lBsdZScFMaFYYIE3Y6CWX22B9VeY2NerT1kyU2heTc3u/W6a+Om6Au2q0rMzBrzynN0l4QoABhI0cbNdyz6fDg==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.1.3.tgz", + "integrity": "sha512-AayK4ZL5ssPzR1OtnOLGAwpT0Dda3Xi/h1G0l1oJDNrowp7T1423q4Zb8/emr7tzRlCy4ssEri0LWVexAqHyKQ==", "dev": true, "dependencies": { "@types/through": "*", - "rxjs": "^6.4.0" + "rxjs": "^7.2.0" } }, "node_modules/@types/js-yaml": { @@ -3000,19 +2896,6 @@ "string-width": "^4.1.0" } }, - "node_modules/ansi-align/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/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==" - }, "node_modules/ansi-align/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", @@ -3034,17 +2917,6 @@ "node": ">=8" } }, - "node_modules/ansi-align/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==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -3087,12 +2959,15 @@ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, "node_modules/anymatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", - "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, "node_modules/append-transform": { @@ -3351,9 +3226,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/atlassian-openapi": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.8.tgz", - "integrity": "sha512-aecHFJuhu5mUNdVOKbOd17+ZrCnuTw7ZFZBGaMb/fHyqUX3FEVz5e4RRgbvn1EE1+w2vmAUA+vkB9fiOzTjhQA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.13.tgz", + "integrity": "sha512-2/CRqPWZ15BBr9s6/c48QaBKvpxbonTeFGGXKFSmcSVFqH0KfFMWkgMSnCWKyAQ7gZ8Ch9BrqCDAG7ENzFWX2A==", "dev": true, "dependencies": { "jsonpointer": "^4.0.1", @@ -3520,14 +3395,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "engines": { - "node": ">=8" - } - }, "node_modules/boxen/node_modules/ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -3571,11 +3438,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/boxen/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==" - }, "node_modules/boxen/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3605,17 +3467,6 @@ "node": ">=8" } }, - "node_modules/boxen/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/boxen/node_modules/supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -3913,23 +3764,23 @@ } }, "node_modules/chokidar": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", - "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dependencies": { - "anymatch": "^3.0.1", - "braces": "^3.0.2", - "glob-parent": "^5.0.0", - "is-binary-path": "^2.1.0", - "is-glob": "^4.0.1", - "normalize-path": "^3.0.0", - "readdirp": "^3.1.1" + "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.5.0" }, "engines": { - "node": ">= 8" + "node": ">= 8.10.0" }, "optionalDependencies": { - "fsevents": "^2.0.6" + "fsevents": "~2.3.1" } }, "node_modules/chownr": { @@ -3988,14 +3839,14 @@ } }, "node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dependencies": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/cli-spinners": { @@ -4021,33 +3872,45 @@ } }, "node_modules/cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "engines": { + "node": ">= 10" + } }, "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/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==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/clone": { @@ -4306,28 +4169,6 @@ "node": ">=8" } }, - "node_modules/configstore/node_modules/make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/configstore/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/configstore/node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -4774,6 +4615,15 @@ "node": ">=4.5.0" } }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4876,10 +4726,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enabled": { "version": "1.0.2", @@ -5175,12 +5024,6 @@ } } }, - "node_modules/eslint-plugin-jsdoc/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "node_modules/eslint-plugin-jsdoc/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5279,31 +5122,6 @@ "node": ">=10" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "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" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -5463,18 +5281,6 @@ "node": ">=10" } }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -5873,18 +5679,6 @@ "node": ">=8" } }, - "node_modules/fast-glob/node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -5954,14 +5748,17 @@ "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" }, "node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dependencies": { "escape-string-regexp": "^1.0.5" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/file-entry-cache": { @@ -6099,12 +5896,6 @@ "@google-cloud/storage": "^5.3.0" } }, - "node_modules/firebase-admin/node_modules/@types/node": { - "version": "10.17.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", - "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==", - "dev": true - }, "node_modules/firebase-functions": { "version": "3.15.7", "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.15.7.tgz", @@ -6366,10 +6157,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/fsevents": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", - "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, "optional": true, "os": [ @@ -7110,17 +6900,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/gtoken/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -7142,21 +6921,6 @@ "node": ">=6" } }, - "node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "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" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -7351,17 +7115,6 @@ "node": ">= 6" } }, - "node_modules/https-proxy-agent/node_modules/agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/https-proxy-agent/node_modules/debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -7482,26 +7235,137 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inquirer": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", - "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", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz", + "integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.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", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.2.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "engines": { - "node": ">=6.0.0" + "node": ">=8.0.0" + } + }, + "node_modules/inquirer/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==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/inquirer/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/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==" + }, + "node_modules/inquirer/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/install-artifact-from-github": { @@ -7575,6 +7439,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "devOptional": true, "engines": { "node": ">=4" } @@ -7981,9 +7846,9 @@ "integrity": "sha512-hrZ4tElT8huJUH3OwOK+d7F8PRqw09QnGM3Mm3GmqKWDyCCPCG8lGHxXOwQAj0VOxzLirOds07Kz10B5F8M8EA==" }, "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "node_modules/json-schema-compatibility": { "version": "1.1.0", @@ -8153,17 +8018,17 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "engines": [ - "node >=0.6.0" - ], + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" } }, "node_modules/just-extend": { @@ -8580,7 +8445,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "dependencies": { "semver": "^6.0.0" }, @@ -8595,7 +8459,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -8787,11 +8650,11 @@ } }, "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/mimic-response": { @@ -8890,35 +8753,35 @@ "dev": true }, "node_modules/mocha": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", - "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.14.0", + "js-yaml": "4.0.0", "log-symbols": "4.0.0", "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", + "ms": "2.1.3", + "nanoid": "3.1.20", "serialize-javascript": "5.0.1", "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", + "supports-color": "8.1.1", "which": "2.0.2", "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "bin": { @@ -8948,18 +8811,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/mocha/node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/mocha/node_modules/chalk": { "version": "4.1.0", @@ -8977,25 +8833,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "node_modules/mocha/node_modules/chalk/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, "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.5.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" + "node": ">=8" } }, "node_modules/mocha/node_modules/color-convert": { @@ -9017,10 +8864,9 @@ "dev": true }, "node_modules/mocha/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -9034,14 +8880,11 @@ } } }, - "node_modules/mocha/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", @@ -9055,21 +8898,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/mocha/node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -9100,13 +8928,12 @@ } }, "node_modules/mocha/node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -9125,35 +8952,11 @@ } }, "node_modules/mocha/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/mocha/node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/mocha/node_modules/readdirp/node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mocha/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -9167,15 +8970,18 @@ } }, "node_modules/mocha/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==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/mocha/node_modules/which": { @@ -9240,9 +9046,9 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "node_modules/mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/nan": { "version": "2.14.2", @@ -9251,15 +9057,15 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": "^10 || ^12 || >=13.7" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/nash": { @@ -9752,15 +9558,6 @@ "node": ">=8.9" } }, - "node_modules/nyc/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/nyc/node_modules/ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -9806,12 +9603,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/nyc/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==", - "dev": true - }, "node_modules/nyc/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -9901,22 +9692,10 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "dependencies": { "ansi-styles": "^4.0.0", @@ -10004,74 +9783,6 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/oas-resolver/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/oas-resolver/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/oas-resolver/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/oas-resolver/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/oas-resolver/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==", - "dev": true - }, - "node_modules/oas-resolver/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==", - "dev": true - }, - "node_modules/oas-resolver/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/oas-resolver/node_modules/reftools": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.7.tgz", @@ -10081,85 +9792,6 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/oas-resolver/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/oas-resolver/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/oas-resolver/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==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/oas-resolver/node_modules/y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/oas-resolver/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/oas-resolver/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/oas-schema-walker": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", @@ -10263,14 +9895,17 @@ "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" }, "node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dependencies": { - "mimic-fn": "^1.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=4" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/open": { @@ -10285,9 +9920,9 @@ } }, "node_modules/openapi-merge": { - "version": "1.0.23", - "resolved": "https://registry.npmjs.org/openapi-merge/-/openapi-merge-1.0.23.tgz", - "integrity": "sha512-5taciN3KUYFXGF3TrlO4LuPIxZW2oWMrzGrgTrO6OIW9RxCQe+Jj1xc6B3iwXdqwGeqfc4EvLFzde5++B36wQg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/openapi-merge/-/openapi-merge-1.3.2.tgz", + "integrity": "sha512-qRWBwPMiKIUrAcKW6lstMPKpFEWy32dBbP1UjHH9jlWgw++2BCqOVbsjO5Wa4H1Ll3c4cn+lyi4TinUy8iswzw==", "dev": true, "dependencies": { "atlassian-openapi": "^1.0.8", @@ -10341,14 +9976,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/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==", - "engines": { - "node": ">=8" - } - }, "node_modules/ora/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -10378,17 +10005,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/ora/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ora/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -10413,51 +10029,6 @@ "node": ">=8" } }, - "node_modules/ora/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==", - "engines": { - "node": ">=6" - } - }, - "node_modules/ora/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/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==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ora/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10747,11 +10318,14 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "node_modules/picomatch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", - "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "engines": { - "node": ">=8" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pkg-dir": { @@ -10942,11 +10516,6 @@ "pbts": "bin/pbts" } }, - "node_modules/protobufjs/node_modules/@types/node": { - "version": "10.17.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", - "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==" - }, "node_modules/proxy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.2.tgz", @@ -11303,15 +10872,6 @@ "node": ">=8" } }, - "node_modules/read-pkg-up/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, - "engines": { - "node": ">=8" - } - }, "node_modules/read-pkg/node_modules/type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -11343,14 +10903,14 @@ } }, "node_modules/readdirp": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz", - "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "dependencies": { - "picomatch": "^2.0.4" + "picomatch": "^2.2.1" }, "engines": { - "node": ">= 8" + "node": ">=8.10.0" } }, "node_modules/redent": { @@ -11543,15 +11103,15 @@ } }, "node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dependencies": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/retry-request": { @@ -11643,12 +11203,9 @@ } }, "node_modules/run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dependencies": { - "is-promise": "^2.1.0" - }, + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "engines": { "node": ">=0.12.0" } @@ -11674,16 +11231,18 @@ ] }, "node_modules/rxjs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", - "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.1.tgz", + "integrity": "sha512-KExVEeZWxMZnZhUZtsJcFwz8IvPvgu4G2Z2QyqjZQzUGr32KDYuSxrEYO4w3tFFNbfLozcrKUTvTPi+E9ywJkQ==", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -12282,6 +11841,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "devOptional": true, "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -12294,6 +11854,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "devOptional": true, "engines": { "node": ">=4" } @@ -12302,6 +11863,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "devOptional": true, "dependencies": { "ansi-regex": "^3.0.0" }, @@ -12310,22 +11872,22 @@ } }, "node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/strip-bom": { @@ -12533,14 +12095,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "node_modules/superstatic/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/superstatic/node_modules/path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -12702,15 +12256,6 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/swagger2openapi/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/swagger2openapi/node_modules/ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -12756,12 +12301,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/swagger2openapi/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==", - "dev": true - }, "node_modules/swagger2openapi/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -12808,15 +12347,6 @@ "node": ">=8" } }, - "node_modules/swagger2openapi/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, - "engines": { - "node": ">=8" - } - }, "node_modules/swagger2openapi/node_modules/string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -12831,18 +12361,6 @@ "node": ">=8" } }, - "node_modules/swagger2openapi/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/swagger2openapi/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -12923,27 +12441,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/table/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/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==", - "dev": true - }, - "node_modules/table/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==", - "dev": true - }, "node_modules/table/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", @@ -12959,12 +12456,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/table/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "node_modules/table/node_modules/string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -12979,18 +12470,6 @@ "node": ">=8" } }, - "node_modules/table/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tar": { "version": "4.4.18", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.18.tgz", @@ -13104,16 +12583,6 @@ "node": ">=10" } }, - "node_modules/teeny-request/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==", - "dev": true, - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/term-size": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", @@ -13331,29 +12800,11 @@ "node": ">=0.3.1" } }, - "node_modules/ts-node/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, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node/node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/tsutils": { "version": "3.19.0", @@ -13496,65 +12947,6 @@ "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==", "dev": true }, - "node_modules/typescript-json-schema/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/typescript-json-schema/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/typescript-json-schema/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/typescript-json-schema/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/typescript-json-schema/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==", - "dev": true - }, - "node_modules/typescript-json-schema/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==", - "dev": true - }, "node_modules/typescript-json-schema/node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -13575,41 +12967,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typescript-json-schema/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/typescript-json-schema/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/typescript-json-schema/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/typescript-json-schema/node_modules/typescript": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", @@ -13623,59 +12980,6 @@ "node": ">=4.2.0" } }, - "node_modules/typescript-json-schema/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==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/typescript-json-schema/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/typescript-json-schema/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/typescript-json-schema/node_modules/yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -13798,14 +13102,6 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/update-notifier/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==", - "engines": { - "node": ">=8" - } - }, "node_modules/update-notifier/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -13841,14 +13137,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/node_modules/boxen/node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dependencies": { - "string-width": "^4.1.0" - } - }, "node_modules/update-notifier/node_modules/camelcase": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", @@ -13891,11 +13179,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/update-notifier/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==" - }, "node_modules/update-notifier/node_modules/global-dirs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", @@ -13987,17 +13270,6 @@ "node": ">=8" } }, - "node_modules/update-notifier/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==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/update-notifier/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14020,26 +13292,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/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==", - "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/uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "node_modules/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dependencies": { "punycode": "^2.1.0" } @@ -14213,19 +13469,6 @@ "node": ">=8" } }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/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==" - }, "node_modules/widest-line/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", @@ -14247,17 +13490,6 @@ "node": ">=8" } }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", @@ -14312,37 +13544,76 @@ } }, "node_modules/workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", "dev": true }, "node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/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==" + }, + "node_modules/wrap-ansi/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==", + "engines": { + "node": ">=8" } }, "node_modules/wrap-ansi/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/wrappy": { @@ -14433,31 +13704,30 @@ } }, "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "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" + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" } }, "node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "engines": { + "node": ">=10" } }, "node_modules/yargs-unparser": { @@ -14508,64 +13778,36 @@ "node": ">=8" } }, - "node_modules/yargs/node_modules/find-up": { + "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/yargs/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "node_modules/yargs/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==", "dev": true, "dependencies": { - "p-limit": "^2.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "node_modules/yargs/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, "engines": { - "node": ">=6" + "node": ">=10" } }, "node_modules/yauzl": { @@ -14970,18 +14212,6 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -14991,12 +14221,6 @@ "ms": "2.1.2" } }, - "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==", - "dev": true - }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -15565,15 +14789,6 @@ "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^2.9.2" - }, - "dependencies": { - "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==", - "dev": true, - "optional": true - } } }, "@google-cloud/paginator": { @@ -15695,30 +14910,6 @@ "dev": true, "optional": true }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true, - "optional": true - }, - "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, - "optional": true - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "optional": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -15770,11 +14961,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.9.tgz", "integrity": "sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q==" }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15806,11 +14992,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "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==" - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -15918,14 +15099,6 @@ "strip-ansi": "^6.0.0" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -16087,12 +15260,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -16458,13 +15625,13 @@ } }, "@types/inquirer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.0.3.tgz", - "integrity": "sha512-lBsdZScFMaFYYIE3Y6CWX22B9VeY2NerT1kyU2heTc3u/W6a+Om6Au2q0rMzBrzynN0l4QoABhI0cbNdyz6fDg==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.1.3.tgz", + "integrity": "sha512-AayK4ZL5ssPzR1OtnOLGAwpT0Dda3Xi/h1G0l1oJDNrowp7T1423q4Zb8/emr7tzRlCy4ssEri0LWVexAqHyKQ==", "dev": true, "requires": { "@types/through": "*", - "rxjs": "^6.4.0" + "rxjs": "^7.2.0" } }, "@types/js-yaml": { @@ -17063,16 +16230,6 @@ "string-width": "^4.1.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "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==" - }, "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", @@ -17087,14 +16244,6 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } } } }, @@ -17128,9 +16277,9 @@ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, "anymatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", - "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -17363,9 +16512,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atlassian-openapi": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.8.tgz", - "integrity": "sha512-aecHFJuhu5mUNdVOKbOd17+ZrCnuTw7ZFZBGaMb/fHyqUX3FEVz5e4RRgbvn1EE1+w2vmAUA+vkB9fiOzTjhQA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.13.tgz", + "integrity": "sha512-2/CRqPWZ15BBr9s6/c48QaBKvpxbonTeFGGXKFSmcSVFqH0KfFMWkgMSnCWKyAQ7gZ8Ch9BrqCDAG7ENzFWX2A==", "dev": true, "requires": { "jsonpointer": "^4.0.1", @@ -17505,11 +16654,6 @@ "widest-line": "^3.1.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -17541,11 +16685,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "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==" - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -17566,14 +16705,6 @@ "strip-ansi": "^6.0.0" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -17789,18 +16920,18 @@ "dev": true }, "chokidar": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", - "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "requires": { - "anymatch": "^3.0.1", - "braces": "^3.0.2", - "fsevents": "^2.0.6", - "glob-parent": "^5.0.0", - "is-binary-path": "^2.1.0", - "is-glob": "^4.0.1", - "normalize-path": "^3.0.0", - "readdirp": "^3.1.1" + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" } }, "chownr": { @@ -17847,11 +16978,11 @@ } }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, "cli-spinners": { @@ -17868,30 +16999,36 @@ } }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" }, "dependencies": { + "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==", + "dev": true + }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } } } @@ -18104,19 +17241,6 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, - "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -18466,6 +17590,12 @@ "streamsearch": "0.1.2" } }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -18558,10 +17688,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "enabled": { "version": "1.0.2", @@ -18758,24 +17887,6 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -18885,15 +17996,6 @@ "lru-cache": "^6.0.0" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -18958,12 +18060,6 @@ "ms": "2.1.2" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -19311,14 +18407,6 @@ "merge2": "^1.3.0", "micromatch": "^4.0.2", "picomatch": "^2.2.1" - }, - "dependencies": { - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - } } }, "fast-json-stable-stringify": { @@ -19389,9 +18477,9 @@ "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" }, "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "requires": { "escape-string-regexp": "^1.0.5" } @@ -19539,14 +18627,6 @@ "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", "node-forge": "^0.10.0" - }, - "dependencies": { - "@types/node": { - "version": "10.17.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", - "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==", - "dev": true - } } }, "firebase-functions": { @@ -19727,9 +18807,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", - "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "optional": true }, "fstream": { @@ -20353,11 +19433,6 @@ "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } - }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" } } }, @@ -20373,19 +19448,6 @@ "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - } } }, "hard-rejection": { @@ -20546,14 +19608,6 @@ "debug": "4" }, "dependencies": { - "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", - "requires": { - "debug": "4" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -20640,23 +19694,97 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", - "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz", + "integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.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", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.2.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" + }, + "dependencies": { + "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==", + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "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==" + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "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==" + } } }, "install-artifact-from-github": { @@ -20710,7 +19838,8 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "devOptional": true }, "is-glob": { "version": "4.0.1", @@ -21027,9 +20156,9 @@ "integrity": "sha512-hrZ4tElT8huJUH3OwOK+d7F8PRqw09QnGM3Mm3GmqKWDyCCPCG8lGHxXOwQAj0VOxzLirOds07Kz10B5F8M8EA==" }, "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-schema-compatibility": { "version": "1.1.0", @@ -21165,13 +20294,13 @@ } }, "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" } }, @@ -21538,7 +20667,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "requires": { "semver": "^6.0.0" }, @@ -21546,8 +20674,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -21689,9 +20816,9 @@ } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "mimic-response": { "version": "1.0.1", @@ -21775,35 +20902,35 @@ "dev": true }, "mocha": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", - "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.14.0", + "js-yaml": "4.0.0", "log-symbols": "4.0.0", "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", + "ms": "2.1.3", + "nanoid": "3.1.20", "serialize-javascript": "5.0.1", "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", + "supports-color": "8.1.1", "which": "2.0.2", "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "dependencies": { @@ -21816,15 +20943,11 @@ "color-convert": "^2.0.1" } }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "chalk": { "version": "4.1.0", @@ -21834,22 +20957,17 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" - } - }, - "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + }, + "dependencies": { + "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, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "color-convert": { @@ -21868,33 +20986,28 @@ "dev": true }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -21916,13 +21029,12 @@ "dev": true }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "log-symbols": { @@ -21935,28 +21047,11 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - }, - "dependencies": { - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - } - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -21964,9 +21059,9 @@ "dev": true }, "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==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -22023,9 +21118,9 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "nan": { "version": "2.14.2", @@ -22034,9 +21129,9 @@ "optional": true }, "nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", "dev": true }, "nash": { @@ -22430,12 +21525,6 @@ "yargs": "^15.0.2" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -22472,12 +21561,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "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==", - "dev": true - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -22543,15 +21626,6 @@ "strip-ansi": "^6.0.0" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -22627,121 +21701,10 @@ "yargs": "^16.1.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "dev": true - }, - "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==", - "dev": true - }, - "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==", - "dev": true - }, - "reftools": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.7.tgz", - "integrity": "sha512-I+KZFkQvZjMZqVWxRezTC/kQ2kLhGRZ7C+4ARbgmb5WJbvFUlbrZ/6qlz6mb+cGcPNYib+xqL8kZlxCsSZ7Hew==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "reftools": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.7.tgz", + "integrity": "sha512-I+KZFkQvZjMZqVWxRezTC/kQ2kLhGRZ7C+4ARbgmb5WJbvFUlbrZ/6qlz6mb+cGcPNYib+xqL8kZlxCsSZ7Hew==", "dev": true } } @@ -22833,11 +21796,11 @@ "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "^2.1.0" } }, "open": { @@ -22849,9 +21812,9 @@ } }, "openapi-merge": { - "version": "1.0.23", - "resolved": "https://registry.npmjs.org/openapi-merge/-/openapi-merge-1.0.23.tgz", - "integrity": "sha512-5taciN3KUYFXGF3TrlO4LuPIxZW2oWMrzGrgTrO6OIW9RxCQe+Jj1xc6B3iwXdqwGeqfc4EvLFzde5++B36wQg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/openapi-merge/-/openapi-merge-1.3.2.tgz", + "integrity": "sha512-qRWBwPMiKIUrAcKW6lstMPKpFEWy32dBbP1UjHH9jlWgw++2BCqOVbsjO5Wa4H1Ll3c4cn+lyi4TinUy8iswzw==", "dev": true, "requires": { "atlassian-openapi": "^1.0.8", @@ -22896,11 +21859,6 @@ "wcwidth": "^1.0.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -22918,14 +21876,6 @@ "supports-color": "^7.1.0" } }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "requires": { - "restore-cursor": "^3.1.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -22944,36 +21894,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23188,9 +22108,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picomatch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", - "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pkg-dir": { "version": "4.2.0", @@ -23332,13 +22252,6 @@ "@types/long": "^4.0.0", "@types/node": "^10.1.0", "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "10.17.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", - "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==" - } } }, "proxy": { @@ -23627,12 +22540,6 @@ "requires": { "p-limit": "^2.2.0" } - }, - "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 } } }, @@ -23655,11 +22562,11 @@ } }, "readdirp": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz", - "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "requires": { - "picomatch": "^2.0.4" + "picomatch": "^2.2.1" } }, "redent": { @@ -23807,11 +22714,11 @@ } }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, @@ -23884,12 +22791,9 @@ "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" }, "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "requires": { - "is-promise": "^2.1.0" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, "run-parallel": { "version": "1.1.10", @@ -23898,11 +22802,18 @@ "dev": true }, "rxjs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", - "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.1.tgz", + "integrity": "sha512-KExVEeZWxMZnZhUZtsJcFwz8IvPvgu4G2Z2QyqjZQzUGr32KDYuSxrEYO4w3tFFNbfLozcrKUTvTPi+E9ywJkQ==", "requires": { - "tslib": "^1.9.0" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } } }, "safe-buffer": { @@ -24400,6 +23311,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "devOptional": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -24408,12 +23320,14 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "devOptional": true }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "devOptional": true, "requires": { "ansi-regex": "^3.0.0" } @@ -24421,17 +23335,17 @@ } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" } } }, @@ -24600,14 +23514,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -24731,12 +23637,6 @@ "yargs": "^15.3.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -24773,12 +23673,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "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==", - "dev": true - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -24813,12 +23707,6 @@ "p-limit": "^2.2.0" } }, - "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 - }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -24830,15 +23718,6 @@ "strip-ansi": "^6.0.0" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -24905,24 +23784,6 @@ "uri-js": "^4.2.2" } }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "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==", - "dev": true - }, - "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==", - "dev": true - }, "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", @@ -24935,12 +23796,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -24951,15 +23806,6 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } } } }, @@ -25054,15 +23900,6 @@ "node-fetch": "^2.6.1", "stream-events": "^1.0.5", "uuid": "^8.0.0" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true - } } }, "term-size": { @@ -25237,29 +24074,14 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true - }, - "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 - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } } } }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "tsutils": { "version": "3.19.0", @@ -25373,53 +24195,6 @@ "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==", "dev": true }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "dev": true - }, - "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==", - "dev": true - }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -25434,75 +24209,11 @@ "path-is-absolute": "^1.0.0" } }, - "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==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, "typescript": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", "dev": true - }, - "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==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "dev": true } } }, @@ -25615,11 +24326,6 @@ "xdg-basedir": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -25641,16 +24347,6 @@ "type-fest": "^0.20.2", "widest-line": "^3.1.0", "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "requires": { - "string-width": "^4.1.0" - } - } } }, "camelcase": { @@ -25680,11 +24376,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "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==" - }, "global-dirs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", @@ -25740,14 +24431,6 @@ "strip-ansi": "^6.0.1" } }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -25760,16 +24443,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - }, - "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==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } } } }, @@ -25917,16 +24590,6 @@ "string-width": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "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==" - }, "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", @@ -25941,14 +24604,6 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } } } }, @@ -25999,31 +24654,55 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "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==" + }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } } } @@ -26087,79 +24766,50 @@ "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "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" + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { + "is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true } } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { "version": "2.0.0", diff --git a/package.json b/package.json index 85f8fba3be4..8354ec564f1 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "fs-extra": "^5.0.0", "glob": "^7.1.2", "google-auth-library": "^7.11.0", - "inquirer": "~6.3.1", + "inquirer": "^8.2.0", "js-yaml": "^3.13.1", "JSONStream": "^1.2.1", "jsonwebtoken": "^8.5.1", @@ -160,7 +160,7 @@ "@types/express-serve-static-core": "^4.17.8", "@types/fs-extra": "^5.0.5", "@types/glob": "^7.1.1", - "@types/inquirer": "^6.0.3", + "@types/inquirer": "^8.1.3", "@types/js-yaml": "^3.12.2", "@types/jsonwebtoken": "^8.3.8", "@types/lodash": "^4.14.149", diff --git a/src/extensions/askUserForConsent.ts b/src/extensions/askUserForConsent.ts index f3c2c50b799..4e104cc2cbd 100644 --- a/src/extensions/askUserForConsent.ts +++ b/src/extensions/askUserForConsent.ts @@ -7,7 +7,7 @@ import { FirebaseError } from "../error"; import { logPrefix } from "../extensions/extensionsHelper"; import * as extensionsApi from "./extensionsApi"; import * as iam from "../gcp/iam"; -import { promptOnce, Question } from "../prompt"; +import { promptOnce } from "../prompt"; import * as utils from "../utils"; marked.setOptions({ @@ -90,7 +90,7 @@ export function displayApis(extensionName: string, projectId: string, apis: exte * Displays publisher terms of service and asks user to consent to them. * Errors if they do not consent. */ -export async function promptForPublisherTOS() { +export async function promptForPublisherTOS(): Promise { const termsOfServiceMsg = "By registering as a publisher, you confirm that you have read the Firebase Extensions Publisher Terms and Conditions (linked below) and you, on behalf of yourself and the organization you represent, agree to comply with it. Here is a brief summary of the highlights of our terms and conditions:\n" + " - You ensure extensions you publish comply with all laws and regulations; do not include any viruses, spyware, Trojan horses, or other malicious code; and do not violate any person’s rights, including intellectual property, privacy, and security rights.\n" + @@ -99,15 +99,14 @@ export async function promptForPublisherTOS() { " - If Google requests a critical security matter to be patched for your extension, you will respond to Google within 48 hours with either a resolution or a written resolution plan.\n" + " - Google may remove your extension or terminate the agreement, if you violate any terms."; utils.logLabeledBullet(logPrefix, marked(termsOfServiceMsg)); - const question: Question = { + const consented: boolean = await promptOnce({ name: "consent", type: "confirm", message: marked( "Do you accept the [Firebase Extensions Publisher Terms and Conditions](https://firebase.google.com/docs/extensions/alpha/terms-of-service) and acknowledge that your information will be used in accordance with [Google's Privacy Policy](https://policies.google.com/privacy?hl=en)?" ), default: false, - }; - const consented: boolean = await promptOnce(question); + }); if (!consented) { throw new FirebaseError("You must agree to the terms of service to register a publisher ID.", { exit: 1, diff --git a/src/management/projects.ts b/src/management/projects.ts index e3450abfb0a..d378db58eb6 100644 --- a/src/management/projects.ts +++ b/src/management/projects.ts @@ -5,8 +5,7 @@ import * as ora from "ora"; import { Client } from "../apiv2"; import { FirebaseError } from "../error"; import { pollOperation } from "../operation-poller"; -import { promptOnce } from "../prompt"; -import { Question } from "inquirer"; +import { Question, promptOnce } from "../prompt"; import * as api from "../api"; import { logger } from "../logger"; import * as utils from "../utils"; diff --git a/src/prompt.ts b/src/prompt.ts index 8cd0c313c3d..34ee29da246 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -7,7 +7,7 @@ import { FirebaseError } from "./error"; * Question type for inquirer. See * https://www.npmjs.com/package/inquirer#question */ -export type Question = inquirer.Question; +export type Question = inquirer.DistinctQuestion; type QuestionsThatReturnAString = | inquirer.RawListQuestion @@ -30,8 +30,15 @@ type Options = Record & { nonInteractive?: boolean }; * @param questions `Question`s to ask the user. * @return The answers, keyed by the `name` of the `Question`. */ -export async function prompt(options: Options, questions: Question[]): Promise { +export async function prompt( + options: Options, + // NB: If Observables are to be added here, the for loop below will need to + // be adjusted as well. + questions: ReadonlyArray +): Promise { const prompts = []; + // For each of our questions, if Options already has an answer, + // we go ahead and _skip_ that question. for (const question of questions) { if (question.name && options[question.name] === undefined) { prompts.push(question); @@ -44,7 +51,6 @@ export async function prompt(options: Options, questions: Question[]): Promise
( options?: Options ): Promise; -// This one is a bit hard to type out. Choices can be many things, including a genrator function. Even if we decided to limit +// This one is a bit hard to type out. Choices can be many things, including a generator function. Even if we decided to limit // the ListQuestion to have a choices of ReadonlyArray>, a ChoiceOption still has a `.value` of `any` export async function promptOnce( question: inquirer.ListQuestion, From a90f28aacb23618fd7101960b11ab0e421c25072 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 5 Jan 2022 17:12:17 -0600 Subject: [PATCH 0018/1699] update some dev dependencies (#3988) * upgrade eslint and friends * remove extra comment * update ts-node * update prettier, which added more support for typescript ~4.3 * move archiver types to dev deps --- .eslintrc.js | 3 +- package-lock.json | 1975 +++++++++++--------- package.json | 18 +- scripts/gen-auth-api-spec.ts | 2 +- src/apiv2.ts | 4 +- src/auth.ts | 2 +- src/commands/apps-android-sha-delete.ts | 20 +- src/commands/apps-android-sha-list.ts | 22 +- src/commands/apps-list.ts | 42 +- src/commands/ext-configure.ts | 5 +- src/commands/ext-export.ts | 4 +- src/commands/ext-install.ts | 11 +- src/commands/hosting-sites-get.ts | 32 +- src/commands/init.js | 6 +- src/commands/open.ts | 90 +- src/commands/projects-addfirebase.ts | 20 +- src/deploy/functions/backend.ts | 18 +- src/deploy/functions/release/fabricator.ts | 22 +- src/emulator/auth/server.ts | 14 +- src/emulator/auth/state.ts | 4 +- src/emulator/functionsEmulator.ts | 5 +- src/emulator/functionsEmulatorRuntime.ts | 5 +- src/emulator/storage/cloudFunctions.ts | 2 +- src/emulator/storage/metadata.ts | 11 +- src/emulator/storage/rules/runtime.ts | 4 +- src/extensions/utils.ts | 16 +- src/firestore/indexes.ts | 28 +- src/firestore/util.ts | 6 +- src/functional.ts | 8 +- src/logger.ts | 2 +- src/rulesDeploy.ts | 6 +- src/test/extensions/extensionsApi.spec.ts | 6 +- src/test/extensions/listExtensions.spec.ts | 6 +- src/test/extensions/secretUtils.spec.ts | 3 +- src/test/extensions/updateHelper.spec.ts | 9 +- src/test/functional.spec.ts | 8 +- src/test/gcp/cloudfunctions.spec.ts | 20 +- src/test/gcp/cloudfunctionsv2.spec.ts | 26 +- src/utils.ts | 23 +- 39 files changed, 1382 insertions(+), 1126 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index aedec3be694..d6f8dd98bbc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,7 +10,6 @@ module.exports = { "plugin:jsdoc/recommended", "google", "prettier", - "prettier/@typescript-eslint", ], rules: { "jsdoc/newline-after-description": "off", @@ -47,6 +46,7 @@ module.exports = { "@typescript-eslint/no-inferrable-types": "warn", // TODO(bkendall): remove, allow to error. "@typescript-eslint/no-misused-promises": "warn", // TODO(bkendall): remove, allow to error. "@typescript-eslint/no-unnecessary-type-assertion": "warn", // TODO(bkendall): remove, allow to error. + "@typescript-eslint/no-unsafe-argument": "warn", // TODO(bkendall): remove, allow to error. "@typescript-eslint/no-unsafe-assignment": "warn", // TODO(bkendall): remove, allow to error. "@typescript-eslint/no-unsafe-call": "warn", // TODO(bkendall): remove, allow to error. "@typescript-eslint/no-unsafe-member-access": "warn", // TODO(bkendall): remove, allow to error. @@ -73,6 +73,7 @@ module.exports = { "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-misused-promises": "off", "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-member-access": "off", diff --git a/package-lock.json b/package-lock.json index ae02e6debad..d007d8bdeb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.7.0", - "@types/archiver": "^5.1.0", "abort-controller": "^3.0.0", "ajv": "^6.12.6", "archiver": "^5.0.0", @@ -74,6 +73,7 @@ "devDependencies": { "@google/events": "^5.1.1", "@manifoldco/swagger-to-ts": "^2.0.0", + "@types/archiver": "^5.1.0", "@types/body-parser": "^1.17.0", "@types/chai": "^4.2.12", "@types/chai-as-promised": "^7.1.3", @@ -113,15 +113,15 @@ "@types/uuid": "^8.3.1", "@types/winston": "^2.4.4", "@types/ws": "^7.2.3", - "@typescript-eslint/eslint-plugin": "^4.12.0", - "@typescript-eslint/parser": "^4.12.0", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "eslint": "^7.17.0", + "eslint": "^8.6.0", "eslint-config-google": "^0.14.0", - "eslint-config-prettier": "^7.1.0", - "eslint-plugin-jsdoc": "^30.7.13", - "eslint-plugin-prettier": "^3.3.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-jsdoc": "^37.5.1", + "eslint-plugin-prettier": "^4.0.0", "firebase": "^7.24.0", "firebase-admin": "^9.4.2", "firebase-functions": "^3.15.0", @@ -130,7 +130,7 @@ "nock": "^13.0.5", "nyc": "^15.1.0", "openapi-merge": "^1.0.23", - "prettier": "^2.2.1", + "prettier": "^2.5.1", "proxy": "^1.0.2", "puppeteer": "^9.0.0", "sinon": "^9.2.3", @@ -138,7 +138,7 @@ "source-map-support": "^0.5.9", "supertest": "^3.3.0", "swagger2openapi": "^6.0.3", - "ts-node": "^9.1.1", + "ts-node": "^10.4.0", "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1" }, @@ -480,31 +480,71 @@ "to-fast-properties": "^2.0.0" } }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.14.2.tgz", + "integrity": "sha512-812igKXDcLEdkwUbJvnhzMy88dBBiDeaf3mMF1jnQwclIObu5UQB8ow1KAvDRN1FQqpB+IsZnpmRA0jZ6KGt3g==", + "dev": true, + "dependencies": { + "comment-parser": "1.3.0", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "2.0.2" + }, + "engines": { + "node": "^12 || ^14 || ^16 || ^17" + } + }, "node_modules/@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", + "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", + "debug": "^4.3.2", + "espree": "^9.2.0", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "js-yaml": "^4.1.0", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -519,12 +559,12 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "dependencies": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" }, "engines": { "node": ">=8" @@ -542,6 +582,18 @@ "node": ">= 4" } }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@eslint/eslintrc/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -560,6 +612,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@exodus/schemasafe": { "version": "1.0.0-rc.2", "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.2.tgz", @@ -1583,6 +1647,49 @@ "node": ">=6" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "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", @@ -1750,12 +1857,12 @@ } }, "node_modules/@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "2.0.4", + "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" }, "engines": { @@ -1763,21 +1870,21 @@ } }, "node_modules/@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.4", + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" }, "engines": { @@ -1982,10 +2089,35 @@ "node": ">= 6" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "node_modules/@types/archiver": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.0.tgz", "integrity": "sha512-baFOhanb/hxmcOd1Uey2TfFg43kTSmM6py1Eo7Rjbv/ivcl7PXLhY0QgXGf50Hx/eskGCFqPfhs/7IZLb15C5g==", + "dev": true, "dependencies": { "@types/glob": "*" } @@ -2100,7 +2232,8 @@ "node_modules/@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true }, "node_modules/@types/express": { "version": "4.17.0", @@ -2137,6 +2270,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, "dependencies": { "@types/events": "*", "@types/minimatch": "*", @@ -2160,9 +2294,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, "node_modules/@types/jsonwebtoken": { @@ -2200,7 +2334,8 @@ "node_modules/@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true }, "node_modules/@types/minimist": { "version": "1.2.0", @@ -2481,29 +2616,31 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.12.0.tgz", - "integrity": "sha512-wHKj6q8s70sO5i39H2g1gtpCXCvjVszzj6FFygneNFyIAxRvNSVz9GML7XpqrB9t7hNutXw+MHnLN/Ih6uyB8Q==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.0.tgz", + "integrity": "sha512-qT4lr2jysDQBQOPsCCvpPUZHjbABoTJW8V9ZzIYKHMfppJtpdtzszDYsldwhFxlhvrp7aCHeXD1Lb9M1zhwWwQ==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "4.12.0", - "@typescript-eslint/scope-manager": "4.12.0", - "debug": "^4.1.1", + "@typescript-eslint/experimental-utils": "5.9.0", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/type-utils": "5.9.0", + "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -2512,9 +2649,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2535,9 +2672,9 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2550,49 +2687,49 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.12.0.tgz", - "integrity": "sha512-MpXZXUAvHt99c9ScXijx7i061o5HEjXltO+sbYfZAAHxv3XankQkPaNi5myy0Yh0Tyea3Hdq1pi7Vsh0GJb0fA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.0.tgz", + "integrity": "sha512-ZnLVjBrf26dn7ElyaSKa6uDhqwvAi4jBBmHK1VxuFGPRAxhdi18ubQYSGA7SRiFiES3q9JiBOBHEBStOFkwD2g==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.12.0", - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/typescript-estree": "4.12.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/typescript-estree": "5.9.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.12.0.tgz", - "integrity": "sha512-9XxVADAo9vlfjfoxnjboBTxYOiNY93/QuvcPgsiKvHxW6tOZx1W4TvkIQ2jB3k5M0pbFP5FlXihLK49TjZXhuQ==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.9.0.tgz", + "integrity": "sha512-/6pOPz8yAxEt4PLzgbFRDpZmHnXCeZgPDrh/1DaVKOjvn/UPMlWhbx/gA96xRi2JxY1kBl2AmwVbyROUqys5xQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "4.12.0", - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/typescript-estree": "4.12.0", - "debug": "^4.1.1" + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/typescript-estree": "5.9.0", + "debug": "^4.3.2" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -2601,9 +2738,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2624,29 +2761,78 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.12.0.tgz", - "integrity": "sha512-QVf9oCSVLte/8jvOsxmgBdOaoe2J0wtEmBr13Yz0rkBNkl5D8bfnf6G4Vhox9qqMIoG7QQoVwd2eG9DM/ge4Qg==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz", + "integrity": "sha512-DKtdIL49Qxk2a8icF6whRk7uThuVz4A6TCXfjdJSwOsf+9ree7vgQWcx0KOyCdk0i9ETX666p4aMhrRhxhUkyg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.0.tgz", + "integrity": "sha512-uVCb9dJXpBrK1071ri5aEW7ZHdDHAiqEjYznF3HSSvAJXyrkxGOw2Ejibz/q6BXdT8lea8CMI0CzKNFTNI6TEQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/visitor-keys": "4.12.0" + "@typescript-eslint/experimental-utils": "5.9.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/@typescript-eslint/types": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.12.0.tgz", - "integrity": "sha512-N2RhGeheVLGtyy+CxRmxdsniB7sMSCfsnbh8K/+RUIXYYq3Ub5+sukRCjVE80QerrUBvuEvs4fDhz5AW/pcL6g==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz", + "integrity": "sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg==", "dev": true, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -2654,22 +2840,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.12.0.tgz", - "integrity": "sha512-gZkFcmmp/CnzqD2RKMich2/FjBTsYopjiwJCroxqHZIY11IIoN0l5lKqcgoAPKHt33H2mAkSfvzj8i44Jm7F4w==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz", + "integrity": "sha512-kxo3xL2mB7XmiVZcECbaDwYCt3qFXz99tBSuVJR4L/sR7CJ+UNAPrYILILktGj1ppfZ/jNt/cWYbziJUlHl1Pw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/visitor-keys": "4.12.0", - "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -2682,9 +2867,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2705,9 +2890,9 @@ "dev": true }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2720,16 +2905,16 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.12.0.tgz", - "integrity": "sha512-hVpsLARbDh4B9TKYz5cLbcdMIOAoBYgFPCSP9FFS/liSF+b33gVNq8JHY3QGhHNVz85hObvL7BEYLlgx553WCw==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz", + "integrity": "sha512-6zq0mb7LV0ThExKlecvpfepiB+XEtFv/bzx7/jKSgyXTFD7qjmSu1FoiS0x3OZaiS+UIXpH2vd9O89f02RCtgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.12.0", - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.9.0", + "eslint-visitor-keys": "^3.0.0" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -2772,9 +2957,9 @@ } }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2784,14 +2969,23 @@ } }, "node_modules/acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -3203,15 +3397,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -4030,12 +4215,12 @@ } }, "node_modules/comment-parser": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.6.tgz", - "integrity": "sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.0.tgz", + "integrity": "sha512-hRpmWIKgzd81vn0ydoWoyPoALEOnF4wt8yKD35Ib1D6XC2siLiYaiqfGkYrunuKdsXGwpBpHU3+9r+RVw2NZfA==", "dev": true, "engines": { - "node": ">= 6.0.0" + "node": ">= 12.0.0" } }, "node_modules/commondir": { @@ -4909,46 +5094,47 @@ } }, "node_modules/eslint": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.17.0.tgz", - "integrity": "sha512-zJk08MiBgwuGoxes5sSQhOtibZ75pz0J35XTRlZOk9xMffhpA9BTbQZxoXZzOl5zMbleShbGwtw+1kGferfFwQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.6.0.tgz", + "integrity": "sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^1.0.5", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.2.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.0", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.1.0", + "espree": "^9.3.0", + "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^6.0.0", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^3.1.0", + "regexpp": "^3.2.0", "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.4", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -4956,7 +5142,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4975,9 +5161,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", - "integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -4987,30 +5173,32 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "30.7.13", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.13.tgz", - "integrity": "sha512-YM4WIsmurrp0rHX6XiXQppqKB8Ne5ATiZLJe2+/fkp9l9ExXFr43BbAbjZaVrpCT+tuPYOZ8k1MICARHnURUNQ==", - "dev": true, - "dependencies": { - "comment-parser": "^0.7.6", - "debug": "^4.3.1", - "jsdoctypeparser": "^9.0.0", - "lodash": "^4.17.20", - "regextras": "^0.7.1", - "semver": "^7.3.4", + "version": "37.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-37.5.1.tgz", + "integrity": "sha512-WMv/Na5QdpMQao1MR3SgYpGFi2PSrhh/JljlErQru9ZYXf1j9oQVIVCELQV7jcyqKQ/svPqCyU8eMhet9dzP+w==", + "dev": true, + "dependencies": { + "@es-joy/jsdoccomment": "0.14.2", + "comment-parser": "1.3.0", + "debug": "^4.3.3", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "^2.0.2", + "regextras": "^0.8.0", + "semver": "^7.3.5", "spdx-expression-parse": "^3.0.1" }, "engines": { - "node": ">=10" + "node": "^12 || ^14 || ^16 || ^17" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/eslint-plugin-jsdoc/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -5024,6 +5212,18 @@ } } }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-jsdoc/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5031,9 +5231,9 @@ "dev": true }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5056,9 +5256,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz", - "integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0" @@ -5067,8 +5267,8 @@ "node": ">=6.0.0" }, "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" }, "peerDependenciesMeta": { "eslint-config-prettier": { @@ -5090,36 +5290,39 @@ } }, "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">=6" + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" } }, "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "engines": { - "node": ">=4" + "node": ">=10" } }, "node_modules/eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", "dev": true, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/eslint/node_modules/ansi-styles": { @@ -5137,6 +5340,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -5172,9 +5381,9 @@ "dev": true }, "node_modules/eslint/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -5188,23 +5397,69 @@ } } }, - "node_modules/eslint/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", + "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, @@ -5221,6 +5476,18 @@ "node": ">= 4" } }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/eslint/node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5317,27 +5584,30 @@ "node": ">= 0.8.0" } }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/espree": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", + "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", "dev": true, + "dependencies": { + "acorn": "^8.7.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.1.0" + }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/esprima": { @@ -5353,9 +5623,9 @@ } }, "node_modules/esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -5365,9 +5635,9 @@ } }, "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -5386,9 +5656,9 @@ } }, "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -5663,17 +5933,16 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" }, "engines": { "node": ">=8" @@ -5713,9 +5982,9 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "node_modules/fastq": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", - "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -5762,9 +6031,9 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", - "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { "flat-cache": "^3.0.4" @@ -6030,25 +6299,10 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", + "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, "node_modules/foreground-child": { @@ -6638,9 +6892,9 @@ } }, "node_modules/globby": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", - "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", "dev": true, "dependencies": { "array-union": "^2.1.0", @@ -7166,9 +7420,9 @@ ] }, "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, "engines": { "node": ">= 4" @@ -7445,9 +7699,9 @@ } }, "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dependencies": { "is-extglob": "^2.1.1" }, @@ -7797,16 +8051,13 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, - "node_modules/jsdoctypeparser": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", - "integrity": "sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==", + "node_modules/jsdoc-type-pratt-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.0.2.tgz", + "integrity": "sha512-gXN5CxeaI9WtYQYzpOO/CtTRfZppQlKxXRTIm73JuAX6kOGTQ7iZ0e+YB+b2m7Fk+gTYYxRtE1nqje7H6dqv8w==", "dev": true, - "bin": { - "jsdoctypeparser": "bin/jsdoctypeparser" - }, "engines": { - "node": ">=10" + "node": ">=12.0.0" } }, "node_modules/jsesc": { @@ -8271,6 +8522,12 @@ "lodash.isobject": "~2.4.1" } }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -8601,16 +8858,16 @@ } }, "node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "dependencies": { "braces": "^3.0.1", - "picomatch": "^2.0.5" + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" + "node": ">=8.6" } }, "node_modules/mime": { @@ -9357,21 +9614,6 @@ "uuid": "bin/uuid" } }, - "node_modules/node-gyp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/node-gyp/node_modules/semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -10412,9 +10654,9 @@ } }, "node_modules/prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -10714,21 +10956,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/puppeteer/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -10737,6 +10964,26 @@ "node": ">=0.6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -10950,9 +11197,9 @@ "dev": true }, "node_modules/regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "engines": { "node": ">=8" @@ -10962,9 +11209,9 @@ } }, "node_modules/regextras": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz", - "integrity": "sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz", + "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", "dev": true, "engines": { "node": ">=0.1.14" @@ -11157,14 +11404,17 @@ } }, "node_modules/rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/router": { @@ -11211,9 +11461,9 @@ } }, "node_modules/run-parallel": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", - "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -11228,7 +11478,10 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } }, "node_modules/rxjs": { "version": "7.5.1", @@ -11515,65 +11768,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/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==", - "dev": true - }, - "node_modules/slice-ansi/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -12410,66 +12604,6 @@ "node": ">=6" } }, - "node_modules/table": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", - "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", - "dev": true, - "dependencies": { - "ajv": "^7.0.2", - "lodash": "^4.17.20", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", - "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/table/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tar": { "version": "4.4.18", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.18.tgz", @@ -12766,29 +12900,44 @@ "dev": true }, "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.17", "yn": "3.1.1" }, "bin": { "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" }, - "engines": { - "node": ">=10.0.0" - }, "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, "node_modules/ts-node/node_modules/diff": { @@ -12807,9 +12956,9 @@ "dev": true }, "node_modules/tsutils": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.0.tgz", - "integrity": "sha512-A7BaLUPvcQ1cxVu72YfD+UMI3SQPTDv/w4ol6TOwLyI0hwfG9EC+cYlhdflJTmtYTgZ3KqdPSe/otxU4K3kArg==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "dependencies": { "tslib": "^1.8.1" @@ -12935,18 +13084,21 @@ "typescript-json-schema": "bin/typescript-json-schema" } }, - "node_modules/typescript-json-schema/node_modules/@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, "node_modules/typescript-json-schema/node_modules/@types/node": { "version": "14.17.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz", "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==", "dev": true }, + "node_modules/typescript-json-schema/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/typescript-json-schema/node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -12967,6 +13119,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/typescript-json-schema/node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, "node_modules/typescript-json-schema/node_modules/typescript": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", @@ -14194,40 +14372,71 @@ "to-fast-properties": "^2.0.0" } }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@es-joy/jsdoccomment": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.14.2.tgz", + "integrity": "sha512-812igKXDcLEdkwUbJvnhzMy88dBBiDeaf3mMF1jnQwclIObu5UQB8ow1KAvDRN1FQqpB+IsZnpmRA0jZ6KGt3g==", + "dev": true, + "requires": { + "comment-parser": "1.3.0", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "2.0.2" + } + }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", + "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", "dev": true, "requires": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", + "debug": "^4.3.2", + "espree": "^9.2.0", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "js-yaml": "^4.1.0", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" } }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" } }, "ignore": { @@ -14236,6 +14445,15 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -14247,6 +14465,12 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, @@ -15147,6 +15371,40 @@ "protobufjs": "^6.8.6" } }, + "@humanwhocodes/config-array": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -15272,28 +15530,28 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.4", + "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.4", + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, @@ -15463,10 +15721,35 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "@types/archiver": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.0.tgz", "integrity": "sha512-baFOhanb/hxmcOd1Uey2TfFg43kTSmM6py1Eo7Rjbv/ivcl7PXLhY0QgXGf50Hx/eskGCFqPfhs/7IZLb15C5g==", + "dev": true, "requires": { "@types/glob": "*" } @@ -15581,7 +15864,8 @@ "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true }, "@types/express": { "version": "4.17.0", @@ -15618,6 +15902,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, "requires": { "@types/events": "*", "@types/minimatch": "*", @@ -15641,9 +15926,9 @@ "dev": true }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, "@types/jsonwebtoken": { @@ -15681,7 +15966,8 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true }, "@types/minimist": { "version": "1.2.0", @@ -15959,24 +16245,26 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.12.0.tgz", - "integrity": "sha512-wHKj6q8s70sO5i39H2g1gtpCXCvjVszzj6FFygneNFyIAxRvNSVz9GML7XpqrB9t7hNutXw+MHnLN/Ih6uyB8Q==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.0.tgz", + "integrity": "sha512-qT4lr2jysDQBQOPsCCvpPUZHjbABoTJW8V9ZzIYKHMfppJtpdtzszDYsldwhFxlhvrp7aCHeXD1Lb9M1zhwWwQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.12.0", - "@typescript-eslint/scope-manager": "4.12.0", - "debug": "^4.1.1", + "@typescript-eslint/experimental-utils": "5.9.0", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/type-utils": "5.9.0", + "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -15989,9 +16277,9 @@ "dev": true }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -16000,35 +16288,73 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.12.0.tgz", - "integrity": "sha512-MpXZXUAvHt99c9ScXijx7i061o5HEjXltO+sbYfZAAHxv3XankQkPaNi5myy0Yh0Tyea3Hdq1pi7Vsh0GJb0fA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.0.tgz", + "integrity": "sha512-ZnLVjBrf26dn7ElyaSKa6uDhqwvAi4jBBmHK1VxuFGPRAxhdi18ubQYSGA7SRiFiES3q9JiBOBHEBStOFkwD2g==", "dev": true, "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.12.0", - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/typescript-estree": "4.12.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/typescript-estree": "5.9.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" } }, "@typescript-eslint/parser": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.12.0.tgz", - "integrity": "sha512-9XxVADAo9vlfjfoxnjboBTxYOiNY93/QuvcPgsiKvHxW6tOZx1W4TvkIQ2jB3k5M0pbFP5FlXihLK49TjZXhuQ==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.9.0.tgz", + "integrity": "sha512-/6pOPz8yAxEt4PLzgbFRDpZmHnXCeZgPDrh/1DaVKOjvn/UPMlWhbx/gA96xRi2JxY1kBl2AmwVbyROUqys5xQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.12.0", - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/typescript-estree": "4.12.0", - "debug": "^4.1.1" + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/typescript-estree": "5.9.0", + "debug": "^4.3.2" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz", + "integrity": "sha512-DKtdIL49Qxk2a8icF6whRk7uThuVz4A6TCXfjdJSwOsf+9ree7vgQWcx0KOyCdk0i9ETX666p4aMhrRhxhUkyg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.0.tgz", + "integrity": "sha512-uVCb9dJXpBrK1071ri5aEW7ZHdDHAiqEjYznF3HSSvAJXyrkxGOw2Ejibz/q6BXdT8lea8CMI0CzKNFTNI6TEQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "5.9.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -16042,42 +16368,31 @@ } } }, - "@typescript-eslint/scope-manager": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.12.0.tgz", - "integrity": "sha512-QVf9oCSVLte/8jvOsxmgBdOaoe2J0wtEmBr13Yz0rkBNkl5D8bfnf6G4Vhox9qqMIoG7QQoVwd2eG9DM/ge4Qg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/visitor-keys": "4.12.0" - } - }, "@typescript-eslint/types": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.12.0.tgz", - "integrity": "sha512-N2RhGeheVLGtyy+CxRmxdsniB7sMSCfsnbh8K/+RUIXYYq3Ub5+sukRCjVE80QerrUBvuEvs4fDhz5AW/pcL6g==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz", + "integrity": "sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.12.0.tgz", - "integrity": "sha512-gZkFcmmp/CnzqD2RKMich2/FjBTsYopjiwJCroxqHZIY11IIoN0l5lKqcgoAPKHt33H2mAkSfvzj8i44Jm7F4w==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz", + "integrity": "sha512-kxo3xL2mB7XmiVZcECbaDwYCt3qFXz99tBSuVJR4L/sR7CJ+UNAPrYILILktGj1ppfZ/jNt/cWYbziJUlHl1Pw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.12.0", - "@typescript-eslint/visitor-keys": "4.12.0", - "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -16090,9 +16405,9 @@ "dev": true }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -16101,13 +16416,13 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.12.0.tgz", - "integrity": "sha512-hVpsLARbDh4B9TKYz5cLbcdMIOAoBYgFPCSP9FFS/liSF+b33gVNq8JHY3QGhHNVz85hObvL7BEYLlgx553WCw==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz", + "integrity": "sha512-6zq0mb7LV0ThExKlecvpfepiB+XEtFv/bzx7/jKSgyXTFD7qjmSu1FoiS0x3OZaiS+UIXpH2vd9O89f02RCtgw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.12.0", - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.9.0", + "eslint-visitor-keys": "^3.0.0" } }, "@ungap/promise-all-settled": { @@ -16140,18 +16455,24 @@ } }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true }, "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -16492,12 +16813,6 @@ } } }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -17128,9 +17443,9 @@ "integrity": "sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA==" }, "comment-parser": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.6.tgz", - "integrity": "sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.0.tgz", + "integrity": "sha512-hRpmWIKgzd81vn0ydoWoyPoALEOnF4wt8yKD35Ib1D6XC2siLiYaiqfGkYrunuKdsXGwpBpHU3+9r+RVw2NZfA==", "dev": true }, "commondir": { @@ -17843,46 +18158,47 @@ } }, "eslint": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.17.0.tgz", - "integrity": "sha512-zJk08MiBgwuGoxes5sSQhOtibZ75pz0J35XTRlZOk9xMffhpA9BTbQZxoXZzOl5zMbleShbGwtw+1kGferfFwQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.6.0.tgz", + "integrity": "sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^1.0.5", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.2.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.0", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.1.0", + "espree": "^9.3.0", + "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^6.0.0", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^3.1.0", + "regexpp": "^3.2.0", "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.4", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -17896,6 +18212,12 @@ "color-convert": "^2.0.1" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -17922,21 +18244,52 @@ "dev": true }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" } }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", + "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" } }, "has-flag": { @@ -17951,6 +18304,15 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -18019,6 +18381,12 @@ "requires": { "prelude-ls": "^1.2.1" } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, @@ -18030,36 +18398,44 @@ "requires": {} }, "eslint-config-prettier": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", - "integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", "dev": true, "requires": {} }, "eslint-plugin-jsdoc": { - "version": "30.7.13", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.13.tgz", - "integrity": "sha512-YM4WIsmurrp0rHX6XiXQppqKB8Ne5ATiZLJe2+/fkp9l9ExXFr43BbAbjZaVrpCT+tuPYOZ8k1MICARHnURUNQ==", - "dev": true, - "requires": { - "comment-parser": "^0.7.6", - "debug": "^4.3.1", - "jsdoctypeparser": "^9.0.0", - "lodash": "^4.17.20", - "regextras": "^0.7.1", - "semver": "^7.3.4", + "version": "37.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-37.5.1.tgz", + "integrity": "sha512-WMv/Na5QdpMQao1MR3SgYpGFi2PSrhh/JljlErQru9ZYXf1j9oQVIVCELQV7jcyqKQ/svPqCyU8eMhet9dzP+w==", + "dev": true, + "requires": { + "@es-joy/jsdoccomment": "0.14.2", + "comment-parser": "1.3.0", + "debug": "^4.3.3", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "^2.0.2", + "regextras": "^0.8.0", + "semver": "^7.3.5", "spdx-expression-parse": "^3.0.1" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" } }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -18067,9 +18443,9 @@ "dev": true }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -18088,9 +18464,9 @@ } }, "eslint-plugin-prettier": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz", - "integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" @@ -18107,45 +18483,37 @@ } }, "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true } } }, "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", "dev": true }, "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", + "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", "dev": true, "requires": { - "acorn": "^7.4.0", + "acorn": "^8.7.0", "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "eslint-visitor-keys": "^3.1.0" } }, "esprima": { @@ -18154,18 +18522,18 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -18180,9 +18548,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -18396,17 +18764,16 @@ "dev": true }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" } }, "fast-json-stable-stringify": { @@ -18445,9 +18812,9 @@ } }, "fastq": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", - "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -18485,9 +18852,9 @@ } }, "file-entry-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", - "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { "flat-cache": "^3.0.4" @@ -18712,23 +19079,12 @@ "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } } }, "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", + "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, "foreground-child": { @@ -19202,9 +19558,9 @@ "dev": true }, "globby": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", - "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", "dev": true, "requires": { "array-union": "^2.1.0", @@ -19643,9 +19999,9 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, "import-fresh": { @@ -19842,9 +20198,9 @@ "devOptional": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "requires": { "is-extglob": "^2.1.1" } @@ -20119,10 +20475,10 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, - "jsdoctypeparser": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", - "integrity": "sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==", + "jsdoc-type-pratt-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.0.2.tgz", + "integrity": "sha512-gXN5CxeaI9WtYQYzpOO/CtTRfZppQlKxXRTIm73JuAX6kOGTQ7iZ0e+YB+b2m7Fk+gTYYxRtE1nqje7H6dqv8w==", "dev": true }, "jsesc": { @@ -20525,6 +20881,12 @@ "lodash.isobject": "~2.4.1" } }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -20782,13 +21144,13 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "requires": { "braces": "^3.0.1", - "picomatch": "^2.0.5" + "picomatch": "^2.2.3" } }, "mime": { @@ -21370,15 +21732,6 @@ } } }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -22179,9 +22532,9 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" }, "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true }, "prettier-linter-helpers": { @@ -22412,15 +22765,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } } } }, @@ -22429,6 +22773,12 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -22600,15 +22950,15 @@ "dev": true }, "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "regextras": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz", - "integrity": "sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz", + "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", "dev": true }, "registry-auth-token": { @@ -22752,9 +23102,9 @@ "dev": true }, "rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { "glob": "^7.1.3" } @@ -22796,10 +23146,13 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, "run-parallel": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", - "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } }, "rxjs": { "version": "7.5.1", @@ -23050,49 +23403,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "dev": true - }, - "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==", - "dev": true - } - } - }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -23760,55 +24070,6 @@ } } }, - "table": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", - "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", - "dev": true, - "requires": { - "ajv": "^7.0.2", - "lodash": "^4.17.20", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0" - }, - "dependencies": { - "ajv": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", - "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "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==", - "dev": true - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - } - } - }, "tar": { "version": "4.4.18", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.18.tgz", @@ -24056,16 +24317,22 @@ "dev": true }, "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.17", "yn": "3.1.1" }, "dependencies": { @@ -24084,9 +24351,9 @@ "dev": true }, "tsutils": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.19.0.tgz", - "integrity": "sha512-A7BaLUPvcQ1cxVu72YfD+UMI3SQPTDv/w4ol6TOwLyI0hwfG9EC+cYlhdflJTmtYTgZ3KqdPSe/otxU4K3kArg==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -24183,18 +24450,18 @@ "yargs": "^16.2.0" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, "@types/node": { "version": "14.17.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz", "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -24209,6 +24476,20 @@ "path-is-absolute": "^1.0.0" } }, + "ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, "typescript": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", diff --git a/package.json b/package.json index 8354ec564f1..a6094beb32b 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ }, "dependencies": { "@google-cloud/pubsub": "^2.7.0", - "@types/archiver": "^5.1.0", "abort-controller": "^3.0.0", "ajv": "^6.12.6", "archiver": "^5.0.0", @@ -146,6 +145,7 @@ "devDependencies": { "@google/events": "^5.1.1", "@manifoldco/swagger-to-ts": "^2.0.0", + "@types/archiver": "^5.1.0", "@types/body-parser": "^1.17.0", "@types/chai": "^4.2.12", "@types/chai-as-promised": "^7.1.3", @@ -185,15 +185,15 @@ "@types/uuid": "^8.3.1", "@types/winston": "^2.4.4", "@types/ws": "^7.2.3", - "@typescript-eslint/eslint-plugin": "^4.12.0", - "@typescript-eslint/parser": "^4.12.0", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "eslint": "^7.17.0", + "eslint": "^8.6.0", "eslint-config-google": "^0.14.0", - "eslint-config-prettier": "^7.1.0", - "eslint-plugin-jsdoc": "^30.7.13", - "eslint-plugin-prettier": "^3.3.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-jsdoc": "^37.5.1", + "eslint-plugin-prettier": "^4.0.0", "firebase": "^7.24.0", "firebase-admin": "^9.4.2", "firebase-functions": "^3.15.0", @@ -202,7 +202,7 @@ "nock": "^13.0.5", "nyc": "^15.1.0", "openapi-merge": "^1.0.23", - "prettier": "^2.2.1", + "prettier": "^2.5.1", "proxy": "^1.0.2", "puppeteer": "^9.0.0", "sinon": "^9.2.3", @@ -210,7 +210,7 @@ "source-map-support": "^0.5.9", "supertest": "^3.3.0", "swagger2openapi": "^6.0.3", - "ts-node": "^9.1.1", + "ts-node": "^10.4.0", "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1" } diff --git a/scripts/gen-auth-api-spec.ts b/scripts/gen-auth-api-spec.ts index 32c87f4d62f..458528daebf 100644 --- a/scripts/gen-auth-api-spec.ts +++ b/scripts/gen-auth-api-spec.ts @@ -643,7 +643,7 @@ function sortKeys(obj: T): T { return obj; } if (Array.isArray(obj)) { - return (obj.map(sortKeys) as unknown) as T; + return obj.map(sortKeys) as unknown as T; } const sortedObj: T = {} as T; (Object.keys(obj) as [keyof T]).sort().forEach((key) => { diff --git a/src/apiv2.ts b/src/apiv2.ts index 2f3c850c135..3112ed3367f 100644 --- a/src/apiv2.ts +++ b/src/apiv2.ts @@ -368,12 +368,12 @@ export class Client { // any content. We can't just rely on response code (202 may have conent) // and unfortuantely res.length is unreliable (many requests return zero). if (!text.length) { - body = (undefined as unknown) as ResT; + body = undefined as unknown as ResT; } else { body = JSON.parse(text) as ResT; } } else if (options.responseType === "stream") { - body = (res.body as unknown) as ResT; + body = res.body as unknown as ResT; } else { throw new FirebaseError(`Unable to interpret response. Please set responseType.`, { exit: 2, diff --git a/src/auth.ts b/src/auth.ts index d90da2c19a3..036ce6f3c72 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -74,7 +74,7 @@ interface GitHubAuthResponse { // Typescript emulates modules, which have constant exports. We can // overcome this by casting to any // TODO fix after https://github.com/http-party/node-portfinder/pull/115 -((portfinder as unknown) as { basePort: number }).basePort = 9005; +(portfinder as unknown as { basePort: number }).basePort = 9005; /** * Get the global default account. Before multi-auth was implemented diff --git a/src/commands/apps-android-sha-delete.ts b/src/commands/apps-android-sha-delete.ts index 94197053691..06e7a686403 100644 --- a/src/commands/apps-android-sha-delete.ts +++ b/src/commands/apps-android-sha-delete.ts @@ -9,15 +9,13 @@ import { promiseWithSpinner } from "../utils"; module.exports = new Command("apps:android:sha:delete ") .description("delete a SHA certificate hash for a given app id.") .before(requireAuth) - .action( - async (appId: string = "", shaId: string = "", options: any): Promise => { - const projectId = needProjectId(options); + .action(async (appId: string = "", shaId: string = "", options: any): Promise => { + const projectId = needProjectId(options); - await promiseWithSpinner( - async () => await deleteAppAndroidSha(projectId, appId, shaId), - `Deleting Android SHA certificate hash with SHA id ${clc.bold( - shaId - )} and Android app Id ${clc.bold(appId)}` - ); - } - ); + await promiseWithSpinner( + async () => await deleteAppAndroidSha(projectId, appId, shaId), + `Deleting Android SHA certificate hash with SHA id ${clc.bold( + shaId + )} and Android app Id ${clc.bold(appId)}` + ); + }); diff --git a/src/commands/apps-android-sha-list.ts b/src/commands/apps-android-sha-list.ts index 763ca784724..044cc5a0e08 100644 --- a/src/commands/apps-android-sha-list.ts +++ b/src/commands/apps-android-sha-list.ts @@ -39,17 +39,15 @@ function logCertificatesCount(count: number = 0): void { module.exports = new Command("apps:android:sha:list ") .description("list the SHA certificate hashes for a given app id. ") .before(requireAuth) - .action( - async (appId: string = "", options: any): Promise => { - const projectId = needProjectId(options); + .action(async (appId: string = "", options: any): Promise => { + const projectId = needProjectId(options); - const shaCertificates = await promiseWithSpinner( - async () => await listAppAndroidSha(projectId, appId), - "Preparing the list of your Firebase Android app SHA certificate hashes" - ); + const shaCertificates = await promiseWithSpinner( + async () => await listAppAndroidSha(projectId, appId), + "Preparing the list of your Firebase Android app SHA certificate hashes" + ); - logCertificatesList(shaCertificates); - logCertificatesCount(shaCertificates.length); - return shaCertificates; - } - ); + logCertificatesList(shaCertificates); + logCertificatesCount(shaCertificates.length); + return shaCertificates; + }); diff --git a/src/commands/apps-list.ts b/src/commands/apps-list.ts index 6148cc1cea0..25da1d3213b 100644 --- a/src/commands/apps-list.ts +++ b/src/commands/apps-list.ts @@ -38,26 +38,24 @@ module.exports = new Command("apps:list [platform]") "Optionally filter apps by [platform]: IOS, ANDROID or WEB (case insensitive)" ) .before(requireAuth) - .action( - async (platform: string | undefined, options: any): Promise => { - const projectId = needProjectId(options); - const appPlatform = getAppPlatform(platform || ""); - - let apps; - const spinner = ora( - "Preparing the list of your Firebase " + - `${appPlatform === AppPlatform.ANY ? "" : appPlatform + " "}apps` - ).start(); - try { - apps = await listFirebaseApps(projectId, appPlatform); - } catch (err: any) { - spinner.fail(); - throw err; - } - - spinner.succeed(); - logAppsList(apps); - logAppCount(apps.length); - return apps; + .action(async (platform: string | undefined, options: any): Promise => { + const projectId = needProjectId(options); + const appPlatform = getAppPlatform(platform || ""); + + let apps; + const spinner = ora( + "Preparing the list of your Firebase " + + `${appPlatform === AppPlatform.ANY ? "" : appPlatform + " "}apps` + ).start(); + try { + apps = await listFirebaseApps(projectId, appPlatform); + } catch (err: any) { + spinner.fail(); + throw err; } - ); + + spinner.succeed(); + logAppsList(apps); + logAppCount(apps.length); + return apps; + }); diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index d22660bcbf3..5e14d1b9348 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -51,9 +51,8 @@ export default new Command("ext:configure ") } throw err; } - const paramSpecWithNewDefaults = paramHelper.getParamsWithCurrentValuesAsDefaults( - existingInstance - ); + const paramSpecWithNewDefaults = + paramHelper.getParamsWithCurrentValuesAsDefaults(existingInstance); const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => { return param.immutable || param.param === "LOCATION"; // TODO: Stop special casing "LOCATION" once all official extensions make it immutable diff --git a/src/commands/ext-export.ts b/src/commands/ext-export.ts index 6f76041e434..34b23a10bb5 100644 --- a/src/commands/ext-export.ts +++ b/src/commands/ext-export.ts @@ -30,7 +30,9 @@ module.exports = new Command("ext:export") // set any secrets to latest version, // and strip project IDs from the param values. const have = await Promise.all( - (await planner.have(projectId)).map(async (i) => { + ( + await planner.have(projectId) + ).map(async (i) => { const subbed = await setSecretParamsToLatest(i); return parameterizeProject(projectId, projectNumber, subbed); }) diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index f037acf9624..85b07fc6946 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -53,15 +53,8 @@ interface InstallExtensionOptions { } async function installExtension(options: InstallExtensionOptions): Promise { - const { - projectId, - extensionName, - source, - extVersion, - paramsEnvPath, - nonInteractive, - force, - } = options; + const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } = + options; const spec = source?.spec || extVersion?.spec; if (!spec) { throw new FirebaseError( diff --git a/src/commands/hosting-sites-get.ts b/src/commands/hosting-sites-get.ts index 8b82f01e765..df7c47a8288 100644 --- a/src/commands/hosting-sites-get.ts +++ b/src/commands/hosting-sites-get.ts @@ -10,22 +10,20 @@ import { FirebaseError } from "../error"; export default new Command("hosting:sites:get ") .description("print info about a Firebase Hosting site") .before(requirePermissions, ["firebasehosting.sites.get"]) - .action( - async (siteId: string, options): Promise => { - const projectId = needProjectId(options); - if (!siteId) { - throw new FirebaseError(" must be specified"); - } - const site = await getSite(projectId, siteId); - const table = new Table(); - table.push(["Site ID:", site.name.split("/").pop()]); - table.push(["Default URL:", site.defaultUrl]); - table.push(["App ID:", site.appId || ""]); - // table.push(["Labels:", JSON.stringify(site.labels)]); + .action(async (siteId: string, options): Promise => { + const projectId = needProjectId(options); + if (!siteId) { + throw new FirebaseError(" must be specified"); + } + const site = await getSite(projectId, siteId); + const table = new Table(); + table.push(["Site ID:", site.name.split("/").pop()]); + table.push(["Default URL:", site.defaultUrl]); + table.push(["App ID:", site.appId || ""]); + // table.push(["Labels:", JSON.stringify(site.labels)]); - logger.info(); - logger.info(table.toString()); + logger.info(); + logger.info(table.toString()); - return site; - } - ); + return site; + }); diff --git a/src/commands/init.js b/src/commands/init.js index b558ef38411..19d2beb765a 100644 --- a/src/commands/init.js +++ b/src/commands/init.js @@ -26,8 +26,7 @@ var _isOutside = function (from, to) { const choices = [ { value: "database", - name: - "Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance", + name: "Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance", checked: false, }, { @@ -42,8 +41,7 @@ const choices = [ }, { value: "hosting", - name: - "Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys", + name: "Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys", checked: false, }, { diff --git a/src/commands/open.ts b/src/commands/open.ts index db270f4596d..f4c904ddac8 100644 --- a/src/commands/open.ts +++ b/src/commands/open.ts @@ -55,53 +55,51 @@ export default new Command("open [link]") .description("quickly open a browser to relevant project resources") .before(requirePermissions) .before(requireDatabaseInstance) - .action( - async (linkName: string, options: any): Promise => { - let link = _.find(LINKS, { arg: linkName }); - if (linkName && !link) { - throw new FirebaseError( - "Unrecognized link name. Valid links are:\n\n" + _.map(LINKS, "arg").join("\n") - ); - } - - if (!link) { - const name = await promptOnce({ - type: "list", - message: "What link would you like to open?", - choices: CHOICES, - }); - link = _.find(LINKS, { name }); - } - if (!link) { - throw new FirebaseError( - "Unrecognized link name. Valid links are:\n\n" + _.map(LINKS, "arg").join("\n") - ); - } + .action(async (linkName: string, options: any): Promise => { + let link = _.find(LINKS, { arg: linkName }); + if (linkName && !link) { + throw new FirebaseError( + "Unrecognized link name. Valid links are:\n\n" + _.map(LINKS, "arg").join("\n") + ); + } - let url; - if (link.consolePath) { - url = utils.consoleUrl(options.project, link.consolePath); - } else if (link.url) { - url = link.url; - } else if (link.arg === "hosting:site") { - url = utils.addSubdomain(api.hostingOrigin, options.instance); - } else if (link.arg === "functions:log") { - url = `https://console.developers.google.com/logs/viewer?resource=cloudfunctions.googleapis.com&project=${options.project}`; - } else { - throw new FirebaseError(`Unable to determine URL for link: ${link}`); - } + if (!link) { + const name = await promptOnce({ + type: "list", + message: "What link would you like to open?", + choices: CHOICES, + }); + link = _.find(LINKS, { name }); + } + if (!link) { + throw new FirebaseError( + "Unrecognized link name. Valid links are:\n\n" + _.map(LINKS, "arg").join("\n") + ); + } - if (link.arg !== linkName) { - logger.info( - `${clc.bold.cyan("Tip:")} You can also run ${clc.bold.underline( - `firebase open ${link.arg}` - )}` - ); - logger.info(); - } - logger.info(`Opening ${clc.bold(link.name)} link in your default browser:`); - logger.info(clc.bold.underline(url)); + let url; + if (link.consolePath) { + url = utils.consoleUrl(options.project, link.consolePath); + } else if (link.url) { + url = link.url; + } else if (link.arg === "hosting:site") { + url = utils.addSubdomain(api.hostingOrigin, options.instance); + } else if (link.arg === "functions:log") { + url = `https://console.developers.google.com/logs/viewer?resource=cloudfunctions.googleapis.com&project=${options.project}`; + } else { + throw new FirebaseError(`Unable to determine URL for link: ${link}`); + } - open(url); + if (link.arg !== linkName) { + logger.info( + `${clc.bold.cyan("Tip:")} You can also run ${clc.bold.underline( + `firebase open ${link.arg}` + )}` + ); + logger.info(); } - ); + logger.info(`Opening ${clc.bold(link.name)} link in your default browser:`); + logger.info(clc.bold.underline(url)); + + open(url); + }); diff --git a/src/commands/projects-addfirebase.ts b/src/commands/projects-addfirebase.ts index eca069ec4b4..568c82f28ef 100644 --- a/src/commands/projects-addfirebase.ts +++ b/src/commands/projects-addfirebase.ts @@ -10,16 +10,14 @@ import { requireAuth } from "../requireAuth"; module.exports = new Command("projects:addfirebase [projectId]") .description("add Firebase resources to a Google Cloud Platform project") .before(requireAuth) - .action( - async (projectId: string | undefined, options: any): Promise => { - if (!options.nonInteractive && !projectId) { - projectId = await promptAvailableProjectId(); - } - - if (!projectId) { - throw new FirebaseError("Project ID cannot be empty"); - } + .action(async (projectId: string | undefined, options: any): Promise => { + if (!options.nonInteractive && !projectId) { + projectId = await promptAvailableProjectId(); + } - return addFirebaseToCloudProjectAndLog(projectId); + if (!projectId) { + throw new FirebaseError("Project ID cannot be empty"); } - ); + + return addFirebaseToCloudProjectAndLog(projectId); + }); diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 71845f7de53..d9e9f2c5b8d 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -492,14 +492,20 @@ export function regionalEndpoints(backend: Backend, region: string): Endpoint[] } /** A curried function used for filters, returns a matcher for functions in a backend. */ -export const hasEndpoint = (backend: Backend) => (endpoint: Endpoint): boolean => { - return !!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id]; -}; +export const hasEndpoint = + (backend: Backend) => + (endpoint: Endpoint): boolean => { + return ( + !!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id] + ); + }; /** A curried function that is the opposite of hasEndpoint */ -export const missingEndpoint = (backend: Backend) => (endpoint: Endpoint): boolean => { - return !hasEndpoint(backend)(endpoint); -}; +export const missingEndpoint = + (backend: Backend) => + (endpoint: Endpoint): boolean => { + return !hasEndpoint(backend)(endpoint); + }; /** A standard method for sorting endpoints for display. * Future versions might consider sorting region by pricing tier before diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 0f8504fe40a..d61ec0b0827 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -51,11 +51,11 @@ export interface FabricatorArgs { storage?: Record; } -const rethrowAs = (endpoint: backend.Endpoint, op: reporter.OperationType) => ( - err: unknown -): T => { - throw new reporter.DeploymentError(endpoint, op, err); -}; +const rethrowAs = + (endpoint: backend.Endpoint, op: reporter.OperationType) => + (err: unknown): T => { + throw new reporter.DeploymentError(endpoint, op, err); + }; /** Fabricators make a customer's backend match a spec by applying a plan. */ export class Fabricator { @@ -79,13 +79,11 @@ export class Fabricator { totalTime: 0, results: [], }; - const deployRegions = Object.values(plan).map( - async (changes): Promise => { - const results = await this.applyRegionalChanges(changes); - summary.results.push(...results); - return; - } - ); + const deployRegions = Object.values(plan).map(async (changes): Promise => { + const results = await this.applyRegionalChanges(changes); + summary.results.push(...results); + return; + }); const promiseResults = await utils.allSettled(deployRegions); const errs = promiseResults diff --git a/src/emulator/auth/server.ts b/src/emulator/auth/server.ts index 6e386928886..11af742acd0 100644 --- a/src/emulator/auth/server.ts +++ b/src/emulator/auth/server.ts @@ -513,12 +513,14 @@ function toExegesisController( function wrapValidateBody(pluginContext: ExegesisPluginContext): void { // Apply fixes to body for Google REST API mapping compatibility. - const op = ((pluginContext as unknown) as { - _operation: { - validateBody?: ValidatorFunction; - _authEmulatorValidateBodyWrapped?: true; - }; - })._operation; + const op = ( + pluginContext as unknown as { + _operation: { + validateBody?: ValidatorFunction; + _authEmulatorValidateBodyWrapped?: true; + }; + } + )._operation; if (op.validateBody && !op._authEmulatorValidateBodyWrapped) { const validateBody = op.validateBody.bind(op); op.validateBody = (body) => { diff --git a/src/emulator/auth/state.ts b/src/emulator/auth/state.ts index c1e01cb2cb5..194586c99e3 100644 --- a/src/emulator/auth/state.ts +++ b/src/emulator/auth/state.ts @@ -414,9 +414,7 @@ export abstract class ProjectState { return refreshToken; } - validateRefreshToken( - refreshToken: string - ): + validateRefreshToken(refreshToken: string): | { user: UserInfo; provider: string; diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 2c0b2a772e4..519bab1866f 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -462,9 +462,8 @@ export class FunctionsEmulator implements EmulatorInstance { const parsedDefinitions = triggerParseEvent.data .triggerDefinitions as ParsedTriggerDefinition[]; - const triggerDefinitions: EmulatedTriggerDefinition[] = emulatedFunctionsByRegion( - parsedDefinitions - ); + const triggerDefinitions: EmulatedTriggerDefinition[] = + emulatedFunctionsByRegion(parsedDefinitions); // When force is true we set up all triggers, otherwise we only set up // triggers which have a unique function name diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 5de8a03a638..abb6e592254 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -1045,9 +1045,8 @@ async function initializeRuntime( require("../deploy/functions/runtimes/node/extractTriggers")(triggerModule, parsedDefinitions); } - const triggerDefinitions: EmulatedTriggerDefinition[] = emulatedFunctionsByRegion( - parsedDefinitions - ); + const triggerDefinitions: EmulatedTriggerDefinition[] = + emulatedFunctionsByRegion(parsedDefinitions); const triggers = getEmulatedTriggersFromDefinitions(triggerDefinitions, triggerModule); diff --git a/src/emulator/storage/cloudFunctions.ts b/src/emulator/storage/cloudFunctions.ts index 620d8e56e9b..527510ace7a 100644 --- a/src/emulator/storage/cloudFunctions.ts +++ b/src/emulator/storage/cloudFunctions.ts @@ -108,7 +108,7 @@ export class StorageCloudFunctions { if (!ceAction) { throw new Error("Action is not defined as a CloudEvents action"); } - const data = (objectMetadataPayload as unknown) as StorageObjectData; + const data = objectMetadataPayload as unknown as StorageObjectData; let time = new Date().toISOString(); if (data.updated) { time = typeof data.updated === "string" ? data.updated : data.updated.toISOString(); diff --git a/src/emulator/storage/metadata.ts b/src/emulator/storage/metadata.ts index 50a7015f15e..24e04c560da 100644 --- a/src/emulator/storage/metadata.ts +++ b/src/emulator/storage/metadata.ts @@ -455,16 +455,17 @@ export class CloudStorageObjectMetadata { * @return the formatted date. */ export function toSerializedDate(d: Date): string { - const day = `${d.getFullYear()}-${(d.getMonth() + 1) + const day = `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d + .getDate() .toString() - .padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")}`; + .padStart(2, "0")}`; const time = `${d.getHours().toString().padStart(2, "0")}:${d .getMinutes() .toString() - .padStart(2, "0")}:${d - .getSeconds() + .padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}.${d + .getMilliseconds() .toString() - .padStart(2, "0")}.${d.getMilliseconds().toString().padStart(3, "0")}`; + .padStart(3, "0")}`; return `${day}T${time}Z`; } diff --git a/src/emulator/storage/rules/runtime.ts b/src/emulator/storage/rules/runtime.ts index 091ac56d79f..f8f00da3aa2 100644 --- a/src/emulator/storage/rules/runtime.ts +++ b/src/emulator/storage/rules/runtime.ts @@ -229,9 +229,7 @@ export class StorageRulesRuntime { }); } - async loadRuleset( - source: Source - ): Promise<{ + async loadRuleset(source: Source): Promise<{ ruleset?: StorageRulesetInstance; issues: StorageRulesIssues; }> { diff --git a/src/extensions/utils.ts b/src/extensions/utils.ts index cb6ffaa4594..705e285ba51 100644 --- a/src/extensions/utils.ts +++ b/src/extensions/utils.ts @@ -20,15 +20,13 @@ interface ListItem { // Convert extension option to Inquirer-friendly list for the prompt, with all items unchecked. export function convertExtensionOptionToLabeledList(options: ParamOption[]): ListItem[] { - return options.map( - (option: ParamOption): ListItem => { - return { - checked: false, - name: option.label, - value: option.value, - }; - } - ); + return options.map((option: ParamOption): ListItem => { + return { + checked: false, + name: option.label, + value: option.value, + }; + }); } // Convert map of RegistryEntry into Inquirer-friendly list for prompt, with all items unchecked. diff --git a/src/firestore/indexes.ts b/src/firestore/indexes.ts index 9068391aa1c..209c70d715b 100644 --- a/src/firestore/indexes.ts +++ b/src/firestore/indexes.ts @@ -175,22 +175,20 @@ export class FirestoreIndexes { return []; } - return indexes.map( - (index: any): API.Index => { - // Ignore any fields that point at the document ID, as those are implied - // in all indexes. - const fields = index.fields.filter((field: API.IndexField) => { - return field.fieldPath !== "__name__"; - }); + return indexes.map((index: any): API.Index => { + // Ignore any fields that point at the document ID, as those are implied + // in all indexes. + const fields = index.fields.filter((field: API.IndexField) => { + return field.fieldPath !== "__name__"; + }); - return { - name: index.name, - state: index.state, - queryScope: index.queryScope, - fields, - }; - } - ); + return { + name: index.name, + state: index.state, + queryScope: index.queryScope, + fields, + }; + }); } /** diff --git a/src/firestore/util.ts b/src/firestore/util.ts index 8368ed37e97..669eb166307 100644 --- a/src/firestore/util.ts +++ b/src/firestore/util.ts @@ -13,10 +13,12 @@ interface FieldName { } // projects/$PROJECT_ID/databases/(default)/collectionGroups/$COLLECTION_GROUP_ID/indexes/$INDEX_ID -const INDEX_NAME_REGEX = /projects\/([^\/]+?)\/databases\/\(default\)\/collectionGroups\/([^\/]+?)\/indexes\/([^\/]*)/; +const INDEX_NAME_REGEX = + /projects\/([^\/]+?)\/databases\/\(default\)\/collectionGroups\/([^\/]+?)\/indexes\/([^\/]*)/; // projects/$PROJECT_ID/databases/(default)/collectionGroups/$COLLECTION_GROUP_ID/fields/$FIELD_ID -const FIELD_NAME_REGEX = /projects\/([^\/]+?)\/databases\/\(default\)\/collectionGroups\/([^\/]+?)\/fields\/([^\/]*)/; +const FIELD_NAME_REGEX = + /projects\/([^\/]+?)\/databases\/\(default\)\/collectionGroups\/([^\/]+?)\/fields\/([^\/]*)/; /** * Parse an Index name into useful pieces. diff --git a/src/functional.ts b/src/functional.ts index db0135f0fc3..acc9762ee9c 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -79,9 +79,11 @@ export function* zip(left: T[], right: V[]): Generator<[T, V]> { * Utility to zip in another array from map. * [1, 2].map(zipIn(['a', 'b'])) = [[1, 'a'], [2, 'b']] */ -export const zipIn = (other: V[]) => (elem: T, ndx: number): [T, V] => { - return [elem, other[ndx]]; -}; +export const zipIn = + (other: V[]) => + (elem: T, ndx: number): [T, V] => { + return [elem, other[ndx]]; + }; /** Used with type guards to guarantee that all cases have been covered. */ export function assertExhaustive(val: never): never { diff --git a/src/logger.ts b/src/logger.ts index 96ef299e4b4..c8189fcef46 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -91,4 +91,4 @@ rawLogger.exitOnError = false; // allow error parameters. // Casting looks super dodgy, but it should be safe because we know the underlying code // handles all parameter types we care about. -export const logger = (annotateDebugLines(expandErrors(rawLogger)) as unknown) as Logger; +export const logger = annotateDebugLines(expandErrors(rawLogger)) as unknown as Logger; diff --git a/src/rulesDeploy.ts b/src/rulesDeploy.ts index 846bce49bee..322cb3397e5 100644 --- a/src/rulesDeploy.ts +++ b/src/rulesDeploy.ts @@ -113,10 +113,8 @@ export class RulesDeploy { async createRulesets(service: RulesetServiceType): Promise { const createdRulesetNames: string[] = []; - const { - latestName: latestRulesetName, - latestContent: latestRulesetContent, - } = await this.getCurrentRules(service); + const { latestName: latestRulesetName, latestContent: latestRulesetContent } = + await this.getCurrentRules(service); // TODO: Make this into a more useful helper method. // Gather the files to be uploaded. diff --git a/src/test/extensions/extensionsApi.spec.ts b/src/test/extensions/extensionsApi.spec.ts index dc0c2a99e58..9a9da1c64fa 100644 --- a/src/test/extensions/extensionsApi.spec.ts +++ b/src/test/extensions/extensionsApi.spec.ts @@ -80,8 +80,7 @@ const TEST_INSTANCE_1 = { updateTime: "2019-06-19T00:21:06.722782Z", state: "ACTIVE", config: { - name: - "projects/invader-zim/instances/image-resizer-1/configurations/5b1fb749-764d-4bd1-af60-bb7f22d27860", + name: "projects/invader-zim/instances/image-resizer-1/configurations/5b1fb749-764d-4bd1-af60-bb7f22d27860", createTime: "2019-06-19T00:21:06.722782Z", }, }; @@ -92,8 +91,7 @@ const TEST_INSTANCE_2 = { updateTime: "2019-05-19T00:20:10.416947Z", state: "ACTIVE", config: { - name: - "projects/invader-zim/instances/image-resizer/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", + name: "projects/invader-zim/instances/image-resizer/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", createTime: "2019-05-19T00:20:10.416947Z", }, }; diff --git a/src/test/extensions/listExtensions.spec.ts b/src/test/extensions/listExtensions.spec.ts index bbfadf4f4fb..a22588cac44 100644 --- a/src/test/extensions/listExtensions.spec.ts +++ b/src/test/extensions/listExtensions.spec.ts @@ -12,8 +12,7 @@ const MOCK_INSTANCES = [ state: "ACTIVE", config: { extensionRef: "firebase/image-resizer", - name: - "projects/my-test-proj/instances/image-resizer/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", + name: "projects/my-test-proj/instances/image-resizer/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", createTime: "2019-05-19T00:20:10.416947Z", source: { state: "ACTIVE", @@ -34,8 +33,7 @@ const MOCK_INSTANCES = [ state: "ACTIVE", config: { extensionRef: "firebase/image-resizer", - name: - "projects/my-test-proj/instances/image-resizer-1/configurations/5b1fb749-764d-4bd1-af60-bb7f22d27860", + name: "projects/my-test-proj/instances/image-resizer-1/configurations/5b1fb749-764d-4bd1-af60-bb7f22d27860", createTime: "2019-06-19T00:21:06.722782Z", source: { spec: { diff --git a/src/test/extensions/secretUtils.spec.ts b/src/test/extensions/secretUtils.spec.ts index b5907735708..10b44fceffa 100644 --- a/src/test/extensions/secretUtils.spec.ts +++ b/src/test/extensions/secretUtils.spec.ts @@ -13,8 +13,7 @@ const TEST_INSTANCE: extensionsApi.ExtensionInstance = { state: "ACTIVE", serviceAccountEmail: "service@account.com", config: { - name: - "projects/invader-zim/instances/image-resizer/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", + name: "projects/invader-zim/instances/image-resizer/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", createTime: "2019-05-19T00:20:10.416947Z", source: { name: "", diff --git a/src/test/extensions/updateHelper.spec.ts b/src/test/extensions/updateHelper.spec.ts index 2c110b7389f..1c3606e523e 100644 --- a/src/test/extensions/updateHelper.spec.ts +++ b/src/test/extensions/updateHelper.spec.ts @@ -108,8 +108,7 @@ const INSTANCE = { updateTime: "2019-05-19T00:20:10.416947Z", state: "ACTIVE", config: { - name: - "projects/invader-zim/instances/instance-of-official-ext/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", + name: "projects/invader-zim/instances/instance-of-official-ext/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", createTime: "2019-05-19T00:20:10.416947Z", sourceId: "fake-official-source", sourceName: "projects/firebasemods/sources/fake-official-source", @@ -125,8 +124,7 @@ const REGISTRY_INSTANCE = { updateTime: "2019-05-19T00:20:10.416947Z", state: "ACTIVE", config: { - name: - "projects/invader-zim/instances/instance-of-registry-ext/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", + name: "projects/invader-zim/instances/instance-of-registry-ext/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", createTime: "2019-05-19T00:20:10.416947Z", sourceId: "fake-registry-source", sourceName: "projects/firebasemods/sources/fake-registry-source", @@ -143,8 +141,7 @@ const LOCAL_INSTANCE = { updateTime: "2019-05-19T00:20:10.416947Z", state: "ACTIVE", config: { - name: - "projects/invader-zim/instances/instance-of-local-ext/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", + name: "projects/invader-zim/instances/instance-of-local-ext/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", createTime: "2019-05-19T00:20:10.416947Z", sourceId: "fake-registry-source", sourceName: "projects/firebasemods/sources/fake-local-source", diff --git a/src/test/functional.spec.ts b/src/test/functional.spec.ts index 976a7d14e5a..357835b365f 100644 --- a/src/test/functional.spec.ts +++ b/src/test/functional.spec.ts @@ -125,18 +125,14 @@ describe("functional", () => { describe("partition", () => { it("should split an array into true and false", () => { const arr = ["T1", "F1", "T2", "F2"]; - expect( - f.partition(arr, (s: string) => s.startsWith("T")) - ).to.deep.equal([ + expect(f.partition(arr, (s: string) => s.startsWith("T"))).to.deep.equal([ ["T1", "T2"], ["F1", "F2"], ]); }); it("can handle an empty array", () => { - expect( - f.partition([], (s: string) => s.startsWith("T")) - ).to.deep.equal([[], []]); + expect(f.partition([], (s: string) => s.startsWith("T"))).to.deep.equal([[], []]); }); }); }); diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index e491c52781d..d29d0c190a1 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -154,17 +154,15 @@ describe("cloudfunctions", () => { ...ENDPOINT, taskQueueTrigger: {}, }; - const taskQueueFunction: Omit< - cloudfunctions.CloudFunction, - cloudfunctions.OutputOnlyFields - > = { - ...CLOUD_FUNCTION, - sourceUploadUrl: UPLOAD_URL, - httpsTrigger: {}, - labels: { - "deployment-taskqueue": "true", - }, - }; + const taskQueueFunction: Omit = + { + ...CLOUD_FUNCTION, + sourceUploadUrl: UPLOAD_URL, + httpsTrigger: {}, + labels: { + "deployment-taskqueue": "true", + }, + }; expect(cloudfunctions.functionFromEndpoint(taskEndpoint, UPLOAD_URL)).to.deep.equal( taskQueueFunction diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 7fbffa2ee5c..83160ea87d8 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -24,21 +24,19 @@ describe("cloudfunctionsv2", () => { generation: 42, }; - const CLOUD_FUNCTION_V2: Omit< - cloudfunctionsv2.CloudFunction, - cloudfunctionsv2.OutputOnlyFields - > = { - name: "projects/project/locations/region/functions/id", - buildConfig: { - entryPoint: "function", - runtime: "nodejs16", - source: { - storageSource: CLOUD_FUNCTION_V2_SOURCE, + const CLOUD_FUNCTION_V2: Omit = + { + name: "projects/project/locations/region/functions/id", + buildConfig: { + entryPoint: "function", + runtime: "nodejs16", + source: { + storageSource: CLOUD_FUNCTION_V2_SOURCE, + }, + environmentVariables: {}, }, - environmentVariables: {}, - }, - serviceConfig: {}, - }; + serviceConfig: {}, + }; const RUN_URI = "https://id-nonce-region-project.run.app"; const HAVE_CLOUD_FUNCTION_V2: cloudfunctionsv2.CloudFunction = { diff --git a/src/utils.ts b/src/utils.ts index 0853b31e22d..732157dcb76 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -217,9 +217,7 @@ export type PromiseResult = PromiseFulfilledResult | PromiseRejectedResult * TODO: delete once min Node version is 12.9.0 or greater */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function allSettled( - promises: Array> -): Promise>> { +export function allSettled(promises: Array>): Promise>> { if (!promises.length) { return Promise.resolve([]); } @@ -446,6 +444,9 @@ export function tryParse(value: any) { } } +/** + * + */ export function setupLoggers() { if (process.env.DEBUG) { logger.add( @@ -494,7 +495,7 @@ export async function promiseWithSpinner(action: () => Promise, message: s * * Inspired by https://github.com/isaacs/server-destroy/blob/master/index.js * - * @returns a function that destroys all connections and closes the server + * @return a function that destroys all connections and closes the server */ export function createDestroyer(server: http.Server): () => Promise { const connections = new Set(); @@ -526,9 +527,10 @@ export function createDestroyer(server: http.Server): () => Promise { * @return the formatted date. */ export function datetimeString(d: Date): string { - const day = `${d.getFullYear()}-${(d.getMonth() + 1) + const day = `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d + .getDate() .toString() - .padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")}`; + .padStart(2, "0")}`; const time = `${d.getHours().toString().padStart(2, "0")}:${d .getMinutes() .toString() @@ -570,6 +572,9 @@ export function assertDefined(val: T, message?: string): asserts val is NonNu } } +/** + * + */ export function assertIsString(val: any, message?: string): asserts val is string { if (typeof val !== "string") { throw new AssertionError({ @@ -578,6 +583,9 @@ export function assertIsString(val: any, message?: string): asserts val is strin } } +/** + * + */ export function assertIsNumber(val: any, message?: string): asserts val is number { if (typeof val !== "number") { throw new AssertionError({ @@ -586,6 +594,9 @@ export function assertIsNumber(val: any, message?: string): asserts val is numbe } } +/** + * + */ export function assertIsStringOrUndefined( val: any, message?: string From dfaa859f2ad8c264e8a3bc540d1a407b326eda8f Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 5 Jan 2022 16:05:51 -0800 Subject: [PATCH 0019/1699] Add support for emulating functions from multiple sources at once (#3937) * Add support for emulating functions from multiple sources at once * formats * fixing emulator tests * actually fixing emualtor tests now * formats * pr fixes * fixing tests --- .../emulator-tests/functionsEmulator.spec.ts | 124 ++++++----- .../functionsEmulatorRuntime.spec.ts | 10 +- src/emulator/controller.ts | 32 ++- src/emulator/functionsEmulator.ts | 195 +++++++++++------- src/emulator/functionsEmulatorShell.ts | 12 +- src/functionsShellCommandAction.ts | 2 +- src/options.ts | 1 + src/serve/functions.ts | 23 ++- 8 files changed, 248 insertions(+), 151 deletions(-) diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index 026411cdad6..b565634cfcf 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -4,7 +4,11 @@ import * as sinon from "sinon"; import * as supertest from "supertest"; import { SignatureType } from "../../src/emulator/functionsEmulatorShared"; -import { FunctionsEmulator, InvokeRuntimeOpts } from "../../src/emulator/functionsEmulator"; +import { + EmulatableBackend, + FunctionsEmulator, + InvokeRuntimeOpts, +} from "../../src/emulator/functionsEmulator"; import { Emulators } from "../../src/emulator/types"; import { RuntimeWorker } from "../../src/emulator/functionsRuntimeWorker"; import { TIMEOUT_LONG, TIMEOUT_MED, MODULE_ROOT } from "./fixtures"; @@ -28,62 +32,73 @@ if ((process.env.DEBUG || "").toLowerCase().includes("spec")) { const functionsEmulator = new FunctionsEmulator({ projectId: "fake-project-id", - functionsDir: MODULE_ROOT, + emulatableBackends: [ + { + functionsDir: MODULE_ROOT, + env: {}, + }, + ], quiet: true, }); -// This is normally discovered in FunctionsEmulator#start() -functionsEmulator.nodeBinary = process.execPath; - -functionsEmulator.setTriggersForTesting([ - { - platform: "gcfv1", - name: "function_id", - id: "us-central1-function_id", - region: "us-central1", - entryPoint: "function_id", - httpsTrigger: {}, - labels: {}, - }, - { - platform: "gcfv1", - name: "function_id", - id: "europe-west2-function_id", - region: "europe-west2", - entryPoint: "function_id", - httpsTrigger: {}, - labels: {}, - }, - { - platform: "gcfv1", - name: "function_id", - id: "europe-west3-function_id", - region: "europe-west3", - entryPoint: "function_id", - httpsTrigger: {}, - labels: {}, - }, - { - platform: "gcfv1", - name: "callable_function_id", - id: "us-central1-callable_function_id", - region: "us-central1", - entryPoint: "callable_function_id", - httpsTrigger: {}, - labels: { - "deployment-callable": "true", +const testBackend = { + functionsDir: MODULE_ROOT, + env: {}, + nodeBinary: process.execPath, +}; + +functionsEmulator.setTriggersForTesting( + [ + { + platform: "gcfv1", + name: "function_id", + id: "us-central1-function_id", + region: "us-central1", + entryPoint: "function_id", + httpsTrigger: {}, + labels: {}, + }, + { + platform: "gcfv1", + name: "function_id", + id: "europe-west2-function_id", + region: "europe-west2", + entryPoint: "function_id", + httpsTrigger: {}, + labels: {}, + }, + { + platform: "gcfv1", + name: "function_id", + id: "europe-west3-function_id", + region: "europe-west3", + entryPoint: "function_id", + httpsTrigger: {}, + labels: {}, + }, + { + platform: "gcfv1", + name: "callable_function_id", + id: "us-central1-callable_function_id", + region: "us-central1", + entryPoint: "callable_function_id", + httpsTrigger: {}, + labels: { + "deployment-callable": "true", + }, + }, + { + platform: "gcfv1", + name: "nested-function_id", + id: "us-central1-nested-function_id", + region: "us-central1", + entryPoint: "nested.function_id", + httpsTrigger: {}, + labels: {}, }, - }, - { - platform: "gcfv1", - name: "nested-function_id", - id: "us-central1-nested-function_id", - region: "us-central1", - entryPoint: "nested.function_id", - httpsTrigger: {}, - labels: {}, - }, -]); + ], + testBackend +); // TODO(samstern): This is an ugly way to just override the InvokeRuntimeOpts on each call const startFunctionRuntime = functionsEmulator.startFunctionRuntime.bind(functionsEmulator); @@ -92,13 +107,14 @@ function useFunctions(triggers: () => {}): void { // eslint-disable-next-line @typescript-eslint/unbound-method functionsEmulator.startFunctionRuntime = ( + backend: EmulatableBackend, triggerId: string, targetName: string, triggerType: SignatureType, proto?: any, runtimeOpts?: InvokeRuntimeOpts ): RuntimeWorker => { - return startFunctionRuntime(triggerId, targetName, triggerType, proto, { + return startFunctionRuntime(testBackend, triggerId, targetName, triggerType, proto, { nodeBinary: process.execPath, serializedTriggers, }); diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index 662340333c9..e93fbe0ccbf 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -19,12 +19,17 @@ const DO_NOTHING = () => { // do nothing. }; +const testBackend = { + functionsDir: MODULE_ROOT, + env: {}, + nodeBinary: process.execPath, +}; + const functionsEmulator = new FunctionsEmulator({ projectId: "fake-project-id", - functionsDir: MODULE_ROOT, + emulatableBackends: [testBackend], }); (functionsEmulator as any).adminSdkConfig = FunctionRuntimeBundles.onRequest.adminSdkConfig; -functionsEmulator.nodeBinary = process.execPath; async function countLogEntries(worker: RuntimeWorker): Promise<{ [key: string]: number }> { const runtime = worker.runtime; @@ -51,6 +56,7 @@ function startRuntimeWithFunctions( opts.serializedTriggers = serializedTriggers; return functionsEmulator.startFunctionRuntime( + testBackend, frb.triggerId!, frb.targetName!, signatureType, diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 6695e2d6d35..86b6b973539 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -17,7 +17,7 @@ import { isEmulator, } from "./types"; import { Constants, FIND_AVAILBLE_PORT_BY_DEFAULT } from "./constants"; -import { FunctionsEmulator } from "./functionsEmulator"; +import { EmulatableBackend, FunctionsEmulator } from "./functionsEmulator"; import { parseRuntimeVersion } from "./functionsEmulatorUtils"; import { AuthEmulator } from "./auth"; import { DatabaseEmulator, DatabaseEmulatorArgs } from "./databaseEmulator"; @@ -305,7 +305,11 @@ function findExportMetadata(importPath: string): ExportMetadata | undefined { } } -export async function startAll(options: Options, showUI: boolean = true): Promise { +interface EmulatorOptions extends Options { + extensionEnv?: Record; +} + +export async function startAll(options: EmulatorOptions, showUI: boolean = true): Promise { // Emulators config is specified in firebase.json as: // "emulators": { // "firestore": { @@ -441,21 +445,27 @@ export async function startAll(options: Options, showUI: boolean = true): Promis ); } - const account = getProjectDefaultAccount(options.projectRoot as string | null); + const account = getProjectDefaultAccount(options.projectRoot); + // TODO: Go read firebase.json for extensions and add them to emualtableBackends. + const emulatableBackends: EmulatableBackend[] = [ + { + functionsDir, + env: { + ...options.extensionEnv, + }, + predefinedTriggers: options.extensionTriggers as ParsedTriggerDefinition[] | undefined, + nodeMajorVersion: parseRuntimeVersion( + options.extensionNodeVersion || options.config.get("functions.runtime") + ), + }, + ]; const functionsEmulator = new FunctionsEmulator({ projectId, - functionsDir, + emulatableBackends, account, host: functionsAddr.host, port: functionsAddr.port, debugPort: inspectFunctions, - env: { - ...(options.extensionEnv as Record | undefined), - }, - predefinedTriggers: options.extensionTriggers as ParsedTriggerDefinition[] | undefined, - nodeMajorVersion: parseRuntimeVersion( - options.extensionNodeVersion || options.config.get("functions.runtime") - ), }); await startEmulator(functionsEmulator); } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 519bab1866f..e0fc7a23cd4 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -71,19 +71,28 @@ const EVENT_INVOKE = "functions:invoke"; */ const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$"); +/** + * EmulatableBackend represents a group of functions to be emulated. + * This can be a CF3 module, or an Extension. + */ +export interface EmulatableBackend { + functionsDir: string; + env: Record; + predefinedTriggers?: ParsedTriggerDefinition[]; + nodeMajorVersion?: number; + nodeBinary?: string; +} + export interface FunctionsEmulatorArgs { projectId: string; - functionsDir: string; + emulatableBackends: EmulatableBackend[]; account?: Account; port?: number; host?: string; quiet?: boolean; disabledRuntimeFeatures?: FunctionsRuntimeFeatures; debugPort?: number; - env?: Record; remoteEmulators?: { [key: string]: EmulatorInfo }; - predefinedTriggers?: ParsedTriggerDefinition[]; - nodeMajorVersion?: number; // Lets us specify the node version when emulating extensions. } // FunctionsRuntimeInstance is the handler for a running function invocation @@ -115,9 +124,11 @@ interface RequestWithRawBody extends express.Request { } interface EmulatedTriggerRecord { + backend: EmulatableBackend; def: EmulatedTriggerDefinition; enabled: boolean; ignored: boolean; + url?: string; } @@ -132,7 +143,6 @@ export class FunctionsEmulator implements EmulatorInstance { return `http://${host}:${port}/${projectId}/${region}/${name}`; } - nodeBinary = ""; private destroyServer?: () => Promise; private triggers: { [triggerName: string]: EmulatedTriggerRecord } = {}; @@ -197,7 +207,7 @@ export class FunctionsEmulator implements EmulatorInstance { createHubServer(): express.Application { // TODO(samstern): Should not need this here but some tests are directly calling this method - // because FunctionsEmulator.start() is not test-safe due to askInstallNodeVersion. + // because FunctionsEmulator.start() used to not be test safe. this.workQueue.start(); const hub = express(); @@ -315,13 +325,14 @@ export class FunctionsEmulator implements EmulatorInstance { } startFunctionRuntime( + backend: EmulatableBackend, triggerId: string, targetName: string, signatureType: SignatureType, proto?: any, runtimeOpts?: InvokeRuntimeOpts ): RuntimeWorker { - const bundleTemplate = this.getBaseBundle(); + const bundleTemplate = this.getBaseBundle(backend); const runtimeBundle: FunctionsRuntimeBundle = { ...bundleTemplate, emulators: { @@ -331,34 +342,34 @@ export class FunctionsEmulator implements EmulatorInstance { auth: this.getEmulatorInfo(Emulators.AUTH), storage: this.getEmulatorInfo(Emulators.STORAGE), }, - nodeMajorVersion: this.args.nodeMajorVersion, + nodeMajorVersion: backend.nodeMajorVersion, proto, triggerId, targetName, }; + if (!backend.nodeBinary) { + throw new FirebaseError(`No node binary for ${triggerId}. This should never happen.`); + } const opts = runtimeOpts || { - nodeBinary: this.nodeBinary, - extensionTriggers: this.args.predefinedTriggers, + nodeBinary: backend.nodeBinary, + extensionTriggers: backend.predefinedTriggers, }; const worker = this.invokeRuntime( runtimeBundle, opts, - this.getRuntimeEnvs({ targetName, signatureType }) + this.getRuntimeEnvs(backend, { targetName, signatureType }) ); return worker; } async start(): Promise { - this.nodeBinary = this.askInstallNodeVersion( - this.args.functionsDir, - this.args.nodeMajorVersion - ); - + for (const backend of this.args.emulatableBackends) { + backend.nodeBinary = this.getNodeBinary(backend); + } const credentialEnv = await this.getCredentialsEnvironment(); - this.args.env = { - ...credentialEnv, - ...this.args.env, - }; + for (const e of this.args.emulatableBackends) { + e.env = { ...credentialEnv, ...e.env }; + } const adminSdkConfig = await getProjectAdminSdkConfigOrCached(this.args.projectId); if (adminSdkConfig) { @@ -380,28 +391,33 @@ export class FunctionsEmulator implements EmulatorInstance { } async connect(): Promise { - this.logger.logLabeled( - "BULLET", - "functions", - `Watching "${this.args.functionsDir}" for Cloud Functions...` - ); + const loadTriggerPromises: Promise[] = []; + for (const backend of this.args.emulatableBackends) { + this.logger.logLabeled( + "BULLET", + "functions", + `Watching "${backend.functionsDir}" for Cloud Functions...` + ); - const watcher = chokidar.watch(this.args.functionsDir, { - ignored: [ - /.+?[\\\/]node_modules[\\\/].+?/, // Ignore node_modules - /(^|[\/\\])\../, // Ignore files which begin the a period - /.+\.log/, // Ignore files which have a .log extension - ], - persistent: true, - }); + const watcher = chokidar.watch(backend.functionsDir, { + ignored: [ + /.+?[\\\/]node_modules[\\\/].+?/, // Ignore node_modules + /(^|[\/\\])\../, // Ignore files which begin the a period + /.+\.log/, // Ignore files which have a .log extension + ], + persistent: true, + }); - const debouncedLoadTriggers = _.debounce(() => this.loadTriggers(), 1000); - watcher.on("change", (filePath) => { - this.logger.log("DEBUG", `File ${filePath} changed, reloading triggers`); - return debouncedLoadTriggers(); - }); + const debouncedLoadTriggers = _.debounce(() => this.loadTriggers(backend), 1000); + watcher.on("change", (filePath) => { + this.logger.log("DEBUG", `File ${filePath} changed, reloading triggers`); + return debouncedLoadTriggers(); + }); - return this.loadTriggers(/* force= */ true); + loadTriggerPromises.push(this.loadTriggers(backend, /* force= */ true)); + } + await Promise.all(loadTriggerPromises); + return; } async stop(): Promise { @@ -433,23 +449,28 @@ export class FunctionsEmulator implements EmulatorInstance { * * TODO(abehaskins): Gracefully handle removal of deleted function definitions */ - async loadTriggers(force = false): Promise { + async loadTriggers(emulatableBackend: EmulatableBackend, force = false): Promise { // Before loading any triggers we need to make sure there are no 'stale' workers // in the pool that would cause us to run old code. this.workerPool.refresh(); + if (!emulatableBackend.nodeBinary) { + throw new FirebaseError( + `No node binary for ${emulatableBackend.functionsDir}. This should never happen.` + ); + } const worker = this.invokeRuntime( - this.getBaseBundle(), + this.getBaseBundle(emulatableBackend), { - nodeBinary: this.nodeBinary, - extensionTriggers: this.args.predefinedTriggers, + nodeBinary: emulatableBackend.nodeBinary, + extensionTriggers: emulatableBackend.predefinedTriggers, }, // Don't include user envs when parsing triggers. { ...this.getSystemEnvs(), ...this.getEmulatorEnvs(), FIREBASE_CONFIG: this.getFirebaseConfig(), - ...this.args.env, + ...emulatableBackend.env, } ); @@ -568,7 +589,7 @@ export class FunctionsEmulator implements EmulatorInstance { } const ignored = !added; - this.addTriggerRecord(definition, { ignored, url }); + this.addTriggerRecord(definition, { backend: emulatableBackend, ignored, url }); const type = definition.httpsTrigger ? "http" @@ -754,14 +775,14 @@ export class FunctionsEmulator implements EmulatorInstance { return Object.values(this.triggers).map((record) => record.def); } - getTriggerDefinitionByKey(triggerKey: string): EmulatedTriggerDefinition { + getTriggerRecordByKey(triggerKey: string): EmulatedTriggerRecord { const record = this.triggers[triggerKey]; if (!record) { logger.debug(`Could not find key=${triggerKey} in ${JSON.stringify(this.triggers)}`); throw new FirebaseError(`No trigger with key ${triggerKey}`); } - return record.def; + return record; } getTriggerKey(def: EmulatedTriggerDefinition): string { @@ -769,24 +790,35 @@ export class FunctionsEmulator implements EmulatorInstance { return def.eventTrigger ? `${def.id}-${this.triggerGeneration}` : def.id; } + getBackends(): EmulatableBackend[] { + return this.args.emulatableBackends; + } + addTriggerRecord( def: EmulatedTriggerDefinition, opts: { ignored: boolean; + backend: EmulatableBackend; url?: string; } ): void { const key = this.getTriggerKey(def); - this.triggers[key] = { def, enabled: true, ignored: opts.ignored, url: opts.url }; + this.triggers[key] = { + def, + enabled: true, + backend: opts.backend, + ignored: opts.ignored, + url: opts.url, + }; } - setTriggersForTesting(triggers: EmulatedTriggerDefinition[]) { - triggers.forEach((def) => this.addTriggerRecord(def, { ignored: false })); + setTriggersForTesting(triggers: EmulatedTriggerDefinition[], backend: EmulatableBackend) { + triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false })); } - getBaseBundle(): FunctionsRuntimeBundle { + getBaseBundle(backend: EmulatableBackend): FunctionsRuntimeBundle { return { - cwd: this.args.functionsDir, + cwd: backend.functionsDir, projectId: this.args.projectId, triggerId: "", targetName: "", @@ -820,24 +852,24 @@ export class FunctionsEmulator implements EmulatorInstance { * specified in extension.yaml. This will ALWAYS be populated when emulating extensions, even if they * are using the default version. */ - askInstallNodeVersion(cwd: string, nodeMajorVersion?: number): string { - const pkg = require(path.join(cwd, "package.json")); + getNodeBinary(backend: EmulatableBackend): string { + const pkg = require(path.join(backend.functionsDir, "package.json")); // If the developer hasn't specified a Node to use, inform them that it's an option and use default - if ((!pkg.engines || !pkg.engines.node) && !nodeMajorVersion) { + if ((!pkg.engines || !pkg.engines.node) && !backend.nodeMajorVersion) { this.logger.log( "WARN", - "Your functions directory does not specify a Node version.\n " + + `Your functions directory ${backend.functionsDir} does not specify a Node version.\n ` + "- Learn more at https://firebase.google.com/docs/functions/manage-functions#set_runtime_options" ); return process.execPath; } const hostMajorVersion = process.versions.node.split(".")[0]; - const requestedMajorVersion: string = nodeMajorVersion - ? `${nodeMajorVersion}` + const requestedMajorVersion: string = backend.nodeMajorVersion + ? `${backend.nodeMajorVersion}` : pkg.engines.node; let localMajorVersion = "0"; - const localNodePath = path.join(cwd, "node_modules/.bin/node"); + const localNodePath = path.join(backend.functionsDir, "node_modules/.bin/node"); // Next check if we have a Node install in the node_modules folder try { @@ -864,10 +896,9 @@ export class FunctionsEmulator implements EmulatorInstance { "functions", `Using node@${requestedMajorVersion} from host.` ); - return process.execPath; } - // Otherwise we'll begin the conversational flow to install the correct version locally + // Otherwise we'll warn and use the version that is currently running this process. this.logger.log( "WARN", `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}"` @@ -876,9 +907,9 @@ export class FunctionsEmulator implements EmulatorInstance { return process.execPath; } - getUserEnvs(): Record { + getUserEnvs(backend: EmulatableBackend): Record { const projectInfo = { - functionsSource: this.args.functionsDir, + functionsSource: backend.functionsDir, projectId: this.args.projectId, isEmulator: true, }; @@ -982,16 +1013,19 @@ export class FunctionsEmulator implements EmulatorInstance { }); } - getRuntimeEnvs(triggerDef?: { - targetName: string; - signatureType: SignatureType; - }): Record { + getRuntimeEnvs( + backend: EmulatableBackend, + triggerDef?: { + targetName: string; + signatureType: SignatureType; + } + ): Record { return { - ...this.getUserEnvs(), + ...this.getUserEnvs(backend), ...this.getSystemEnvs(triggerDef), ...this.getEmulatorEnvs(), FIREBASE_CONFIG: this.getFirebaseConfig(), - ...this.args.env, + ...backend.env, }; } @@ -1109,19 +1143,23 @@ export class FunctionsEmulator implements EmulatorInstance { async reloadTriggers() { this.triggerGeneration++; - return this.loadTriggers(); + const loadTriggerPromises = []; + for (const backend of this.args.emulatableBackends) { + loadTriggerPromises.push(this.loadTriggers(backend)); + } + return Promise.all(loadTriggerPromises); } private async handleBackgroundTrigger(projectId: string, triggerKey: string, proto: any) { // If background triggers are disabled, exit early - const record = this.triggers[triggerKey]; + const record = this.getTriggerRecordByKey(triggerKey); if (record && !record.enabled) { return Promise.reject({ code: 204, body: "Background triggers are curently disabled." }); } - - const trigger = this.getTriggerDefinitionByKey(triggerKey); + const trigger = record.def; const service = getFunctionService(trigger); const worker = this.startFunctionRuntime( + record.backend, trigger.id, trigger.name, getSignatureType(trigger), @@ -1237,7 +1275,8 @@ export class FunctionsEmulator implements EmulatorInstance { return; } - const trigger = this.getTriggerDefinitionByKey(triggerId); + const record = this.getTriggerRecordByKey(triggerId); + const trigger = record.def; logger.debug(`Accepted request ${method} ${req.url} --> ${triggerId}`); const reqBody = (req as RequestWithRawBody).rawBody; @@ -1263,7 +1302,13 @@ export class FunctionsEmulator implements EmulatorInstance { ); } } - const worker = this.startFunctionRuntime(trigger.id, trigger.name, "http", undefined); + const worker = this.startFunctionRuntime( + record.backend, + trigger.id, + trigger.name, + "http", + undefined + ); worker.onLogs((el: EmulatorLog) => { if (el.level === "FATAL") { diff --git a/src/emulator/functionsEmulatorShell.ts b/src/emulator/functionsEmulatorShell.ts index 054b4e3ed48..d5b743b7028 100644 --- a/src/emulator/functionsEmulatorShell.ts +++ b/src/emulator/functionsEmulatorShell.ts @@ -1,5 +1,5 @@ import * as uuid from "uuid"; -import { FunctionsEmulator } from "./functionsEmulator"; +import { EmulatableBackend, FunctionsEmulator } from "./functionsEmulator"; import { EmulatedTriggerDefinition, getSignatureType, @@ -19,7 +19,7 @@ export class FunctionsEmulatorShell implements FunctionsShellController { emulatedFunctions: string[]; urls: { [name: string]: string } = {}; - constructor(private emu: FunctionsEmulator) { + constructor(private emu: FunctionsEmulator, private backend: EmulatableBackend) { this.triggers = emu.getTriggerDefinitions(); this.emulatedFunctions = this.triggers.map((t) => t.id); @@ -68,7 +68,13 @@ export class FunctionsEmulatorShell implements FunctionsShellController { data, }; - this.emu.startFunctionRuntime(trigger.id, trigger.name, getSignatureType(trigger), proto); + this.emu.startFunctionRuntime( + this.backend, + trigger.id, + trigger.name, + getSignatureType(trigger), + proto + ); } private getTrigger(name: string): EmulatedTriggerDefinition { diff --git a/src/functionsShellCommandAction.ts b/src/functionsShellCommandAction.ts index 7f7dd859857..d0c88167f56 100644 --- a/src/functionsShellCommandAction.ts +++ b/src/functionsShellCommandAction.ts @@ -74,7 +74,7 @@ export const actionFunction = async (options: Options) => { }) .then(() => { const instance = serveFunctions.get(); - const emulator = new shell.FunctionsEmulatorShell(instance); + const emulator = new shell.FunctionsEmulatorShell(instance, serveFunctions.getBackend()); if (emulator.emulatedFunctions && emulator.emulatedFunctions.length === 0) { logger.info("No functions emulated."); diff --git a/src/options.ts b/src/options.ts index dfd50a29eb1..0b972c7564c 100644 --- a/src/options.ts +++ b/src/options.ts @@ -17,6 +17,7 @@ export interface Options { projectAlias?: string; projectId?: string; projectNumber?: string; + projectRoot?: string; account?: string; json: boolean; nonInteractive: boolean; diff --git a/src/serve/functions.ts b/src/serve/functions.ts index f8c2d77792c..c05579f4dd6 100644 --- a/src/serve/functions.ts +++ b/src/serve/functions.ts @@ -1,5 +1,9 @@ import * as path from "path"; -import { FunctionsEmulator, FunctionsEmulatorArgs } from "../emulator/functionsEmulator"; +import { + EmulatableBackend, + FunctionsEmulator, + FunctionsEmulatorArgs, +} from "../emulator/functionsEmulator"; import { EmulatorServer } from "../emulator/emulatorServer"; import { parseRuntimeVersion } from "../emulator/functionsEmulatorUtils"; import { needProjectId } from "../projectUtils"; @@ -12,9 +16,10 @@ import * as utils from "../utils"; // but we don't have the "options" object until start() is called. export class FunctionsServer { emulatorServer: EmulatorServer | undefined = undefined; + backend: EmulatableBackend | undefined = undefined; private assertServer() { - if (!this.emulatorServer) { + if (!this.emulatorServer || !this.backend) { throw new Error("Must call start() before calling any other operation!"); } } @@ -30,15 +35,18 @@ export class FunctionsServer { const functionsDir = path.join(options.config.projectDir, options.config.src.functions.source); const account = getProjectDefaultAccount(options.config.projectDir); const nodeMajorVersion = parseRuntimeVersion(options.config.get("functions.runtime")); - + this.backend = { + functionsDir, + env: {}, + nodeMajorVersion, + }; // Normally, these two fields are included in args (and typed as such). // However, some poorly-typed tests may not have them and we need to provide // default values for those tests to work properly. const args: FunctionsEmulatorArgs = { projectId, - functionsDir, + emulatableBackends: [this.backend], account, - nodeMajorVersion, ...partialArgs, }; @@ -76,6 +84,11 @@ export class FunctionsServer { await this.emulatorServer!.stop(); } + getBackend(): EmulatableBackend { + this.assertServer(); + return this.backend!; + } + get(): FunctionsEmulator { this.assertServer(); return this.emulatorServer!.get() as FunctionsEmulator; From 9ae4fefdff1062720e97c3b08b1d81df5025c4cf Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 10 Jan 2022 16:03:26 -0800 Subject: [PATCH 0020/1699] shrinkwrap CLI dependencies (#4004) * create shrinkwrap file * update tests to check for shrinkwrap * add changelog --- .github/workflows/node-test.yml | 8 ++++---- CHANGELOG.md | 1 + package-lock.json => npm-shrinkwrap.json | 0 3 files changed, 5 insertions(+), 4 deletions(-) rename package-lock.json => npm-shrinkwrap.json (100%) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 3856b201cfd..f99a3cbab31 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -27,7 +27,7 @@ jobs: uses: actions/cache@v2 with: path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/npm-shrinkwrap.json') }} - run: npm i -g npm@8 - run: npm ci @@ -51,7 +51,7 @@ jobs: uses: actions/cache@v2 with: path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/npm-shrinkwrap.json') }} - run: npm i -g npm@8 - run: npm ci @@ -92,7 +92,7 @@ jobs: uses: actions/cache@v2 with: path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/npm-shrinkwrap.json') }} - name: Cache firebase emulators uses: actions/cache@v2 @@ -127,7 +127,7 @@ jobs: - run: npm i -g npm@8 # --ignore-scripts prevents the `prepare` script from being run. - run: npm install --package-lock-only --ignore-scripts - - run: "git diff --exit-code -- package-lock.json || (echo 'Error: package-lock.json is changed during npm install! Please make sure to use npm >= 8 and commit package-lock.json.' && false)" + - run: "git diff --exit-code -- npm-shrinkwrap.json || (echo 'Error: npm-shrinkwrap.json is changed during npm install! Please make sure to use npm >= 8 and commit npm-shrinkwrap.json.' && false)" check-json-schema: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac8662bedd..ebcf66428e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ +- Now publishes npm-shrinkwrap.json which pins dependencies for the CLI. - Preserve empty vpc connector setting on function deploy. (#3973) - Upgrades google-auth-library to 7.x.x, enabling support for workload identity federation diff --git a/package-lock.json b/npm-shrinkwrap.json similarity index 100% rename from package-lock.json rename to npm-shrinkwrap.json From 8e14e832ef6a88c169416da9643344834e155fae Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 11 Jan 2022 07:50:06 -0800 Subject: [PATCH 0021/1699] update mocha, nyc, chai (#3991) --- npm-shrinkwrap.json | 500 +++++++++++--------------------------------- package.json | 10 +- 2 files changed, 132 insertions(+), 378 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d007d8bdeb0..616aaf1ea90 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -75,8 +75,8 @@ "@manifoldco/swagger-to-ts": "^2.0.0", "@types/archiver": "^5.1.0", "@types/body-parser": "^1.17.0", - "@types/chai": "^4.2.12", - "@types/chai-as-promised": "^7.1.3", + "@types/chai": "^4.3.0", + "@types/chai-as-promised": "^7.1.4", "@types/cjson": "^0.5.0", "@types/cli-color": "^0.3.29", "@types/cli-table": "^0.3.0", @@ -93,7 +93,7 @@ "@types/jsonwebtoken": "^8.3.8", "@types/lodash": "^4.14.149", "@types/marked": "^0.6.5", - "@types/mocha": "^8.2.0", + "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", "@types/node": "^10.17.50", "@types/node-fetch": "^2.5.7", @@ -115,7 +115,7 @@ "@types/ws": "^7.2.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", - "chai": "^4.2.0", + "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "eslint": "^8.6.0", "eslint-config-google": "^0.14.0", @@ -126,7 +126,7 @@ "firebase-admin": "^9.4.2", "firebase-functions": "^3.15.0", "google-discovery-to-swagger": "^2.1.0", - "mocha": "^8.2.1", + "mocha": "^9.1.3", "nock": "^13.0.5", "nyc": "^15.1.0", "openapi-merge": "^1.0.23", @@ -2139,15 +2139,15 @@ "dev": true }, "node_modules/@types/chai": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", - "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", "dev": true }, "node_modules/@types/chai-as-promised": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz", - "integrity": "sha512-FQnh1ohPXJELpKhzjuDkPLR2BZCAqed+a6xV4MI/T3XzHfd2FlarfUGUdZYgqYe8oxkYn0fchHEeHfHqdZ96sg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.4.tgz", + "integrity": "sha512-1y3L1cHePcIm5vXkh1DSGf/zQq5n5xDKG1fpCvf18+uOkpce0Z1ozNFPkyWsVswK7ntN1sZBw3oU6gmN+pDUcA==", "dev": true, "dependencies": { "@types/chai": "*" @@ -2353,9 +2353,9 @@ } }, "node_modules/@types/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", "dev": true }, "node_modules/@types/multer": { @@ -3882,16 +3882,16 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "node_modules/chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", - "pathval": "^1.1.0", + "pathval": "^1.1.1", "type-detect": "^4.0.5" }, "engines": { @@ -3949,23 +3949,23 @@ } }, "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", "dependencies": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "glob-parent": "~5.1.0", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" }, "engines": { "node": ">= 8.10.0" }, "optionalDependencies": { - "fsevents": "~2.3.1" + "fsevents": "~2.3.2" } }, "node_modules/chownr": { @@ -6830,9 +6830,9 @@ } }, "node_modules/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6843,6 +6843,9 @@ }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { @@ -7693,7 +7696,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "devOptional": true, + "optional": true, "engines": { "node": ">=4" } @@ -9010,33 +9013,32 @@ "dev": true }, "node_modules/mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", + "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", + "chokidar": "3.5.2", + "debug": "4.3.2", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.6", + "glob": "7.1.7", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", + "nanoid": "3.1.25", + "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", + "workerpool": "6.1.5", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -9046,84 +9048,23 @@ "mocha": "bin/mocha" }, "engines": { - "node": ">= 10.12.0" + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mochajs" } }, - "node_modules/mocha/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/mocha/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/mocha/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": 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/mocha/node_modules/chalk/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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/mocha/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==", - "dev": true - }, "node_modules/mocha/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -9155,26 +9096,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "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" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/mocha/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -9185,9 +9106,9 @@ } }, "node_modules/mocha/node_modules/js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { "argparse": "^2.0.1" @@ -9196,18 +9117,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/mocha/node_modules/log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9256,15 +9165,6 @@ "node": ">= 8" } }, - "node_modules/mocha/node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -9314,9 +9214,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -9858,26 +9758,6 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "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" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/nyc/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", @@ -10540,9 +10420,9 @@ } }, "node_modules/pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, "engines": { "node": "*" @@ -11150,9 +11030,9 @@ } }, "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dependencies": { "picomatch": "^2.2.1" }, @@ -11573,9 +11453,9 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -12035,7 +11915,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "devOptional": true, + "optional": true, "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -12048,7 +11928,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "devOptional": true, + "optional": true, "engines": { "node": ">=4" } @@ -12057,7 +11937,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "devOptional": true, + "optional": true, "dependencies": { "ansi-regex": "^3.0.0" }, @@ -13099,26 +12979,6 @@ "node": ">=0.3.1" } }, - "node_modules/typescript-json-schema/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "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" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/typescript-json-schema/node_modules/ts-node": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", @@ -13722,9 +13582,9 @@ } }, "node_modules/workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", "dev": true }, "node_modules/wrap-ansi": { @@ -15771,15 +15631,15 @@ "dev": true }, "@types/chai": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", - "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", "dev": true }, "@types/chai-as-promised": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz", - "integrity": "sha512-FQnh1ohPXJELpKhzjuDkPLR2BZCAqed+a6xV4MI/T3XzHfd2FlarfUGUdZYgqYe8oxkYn0fchHEeHfHqdZ96sg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.4.tgz", + "integrity": "sha512-1y3L1cHePcIm5vXkh1DSGf/zQq5n5xDKG1fpCvf18+uOkpce0Z1ozNFPkyWsVswK7ntN1sZBw3oU6gmN+pDUcA==", "dev": true, "requires": { "@types/chai": "*" @@ -15985,9 +15845,9 @@ } }, "@types/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", "dev": true }, "@types/multer": { @@ -17183,16 +17043,16 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", - "pathval": "^1.1.0", + "pathval": "^1.1.1", "type-detect": "^4.0.5" } }, @@ -17235,18 +17095,18 @@ "dev": true }, "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" } }, "chownr": { @@ -19508,9 +19368,9 @@ } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -20195,7 +20055,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "devOptional": true + "optional": true }, "is-glob": { "version": "4.0.3", @@ -21264,93 +21124,47 @@ "dev": true }, "mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", + "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", + "chokidar": "3.5.2", + "debug": "4.3.2", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.6", + "glob": "7.1.7", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", + "nanoid": "3.1.25", + "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", + "workerpool": "6.1.5", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "dev": true - }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -21370,20 +21184,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "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" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -21391,23 +21191,14 @@ "dev": true }, "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { "argparse": "^2.0.1" } }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -21437,15 +21228,6 @@ "requires": { "isexe": "^2.0.0" } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } } } }, @@ -21491,9 +21273,9 @@ "optional": true }, "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", "dev": true }, "nash": { @@ -21924,20 +21706,6 @@ "path-exists": "^4.0.0" } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "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" - } - }, "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", @@ -22444,9 +22212,9 @@ "dev": true }, "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "pend": { @@ -22912,9 +22680,9 @@ } }, "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "requires": { "picomatch": "^2.2.1" } @@ -23232,9 +23000,9 @@ } }, "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -23621,7 +23389,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "devOptional": true, + "optional": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -23631,13 +23399,13 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "devOptional": true + "optional": true }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "devOptional": true, + "optional": true, "requires": { "ansi-regex": "^3.0.0" } @@ -24462,20 +24230,6 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "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" - } - }, "ts-node": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", @@ -24935,9 +24689,9 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", "dev": true }, "wrap-ansi": { diff --git a/package.json b/package.json index a6094beb32b..1cc4bf7104a 100644 --- a/package.json +++ b/package.json @@ -147,8 +147,8 @@ "@manifoldco/swagger-to-ts": "^2.0.0", "@types/archiver": "^5.1.0", "@types/body-parser": "^1.17.0", - "@types/chai": "^4.2.12", - "@types/chai-as-promised": "^7.1.3", + "@types/chai": "^4.3.0", + "@types/chai-as-promised": "^7.1.4", "@types/cjson": "^0.5.0", "@types/cli-color": "^0.3.29", "@types/cli-table": "^0.3.0", @@ -165,7 +165,7 @@ "@types/jsonwebtoken": "^8.3.8", "@types/lodash": "^4.14.149", "@types/marked": "^0.6.5", - "@types/mocha": "^8.2.0", + "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", "@types/node": "^10.17.50", "@types/node-fetch": "^2.5.7", @@ -187,7 +187,7 @@ "@types/ws": "^7.2.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", - "chai": "^4.2.0", + "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "eslint": "^8.6.0", "eslint-config-google": "^0.14.0", @@ -198,7 +198,7 @@ "firebase-admin": "^9.4.2", "firebase-functions": "^3.15.0", "google-discovery-to-swagger": "^2.1.0", - "mocha": "^8.2.1", + "mocha": "^9.1.3", "nock": "^13.0.5", "nyc": "^15.1.0", "openapi-merge": "^1.0.23", From fc2c57fea2d28f3a5f4ad546c340a2162c08d815 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 11 Jan 2022 12:53:08 -0800 Subject: [PATCH 0022/1699] use built-in cache with setup-node (#4006) --- .github/workflows/node-test.yml | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index f99a3cbab31..99e28b3be35 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -22,12 +22,8 @@ jobs: - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - - - name: Cache npm - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/npm-shrinkwrap.json') }} + cache: npm + cache-dependency-path: npm-shrinkwrap.json - run: npm i -g npm@8 - run: npm ci @@ -46,12 +42,8 @@ jobs: - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - - - name: Cache npm - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/npm-shrinkwrap.json') }} + cache: npm + cache-dependency-path: npm-shrinkwrap.json - run: npm i -g npm@8 - run: npm ci @@ -87,12 +79,8 @@ jobs: - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - - - name: Cache npm - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/npm-shrinkwrap.json') }} + cache: npm + cache-dependency-path: npm-shrinkwrap.json - name: Cache firebase emulators uses: actions/cache@v2 From 9df5e52256d4e9f069fd89c15938b36ac8afed03 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 12 Jan 2022 19:49:57 +0000 Subject: [PATCH 0023/1699] 10.1.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 616aaf1ea90..cbe2c75afcc 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.0.1", + "version": "10.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.0.1", + "version": "10.1.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.7.0", diff --git a/package.json b/package.json index 1cc4bf7104a..38c7edbdd59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.0.1", + "version": "10.1.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 960ed8e8a9fd7b3e01131f9beb74fe7e0f8fc4ac Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 12 Jan 2022 19:50:23 +0000 Subject: [PATCH 0024/1699] [firebase-release] Removed change log and reset repo after 10.1.0 release --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebcf66428e8..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +0,0 @@ -- Now publishes npm-shrinkwrap.json which pins dependencies for the CLI. -- Preserve empty vpc connector setting on function deploy. (#3973) -- Upgrades google-auth-library to 7.x.x, enabling support for workload identity federation From 2158872ffa9cba2ce51ba2650f73030271a559c2 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 12 Jan 2022 12:35:11 -0800 Subject: [PATCH 0025/1699] publishing updates (#4011) * remove types from js file * use a bigger machine * use the newer, official gcloud container --- scripts/publish/cloudbuild.yaml | 12 ++++++++---- scripts/publish/run.sh | 1 + standalone/firepit.js | 8 ++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/scripts/publish/cloudbuild.yaml b/scripts/publish/cloudbuild.yaml index 4113b5875f6..cc8cc227e45 100644 --- a/scripts/publish/cloudbuild.yaml +++ b/scripts/publish/cloudbuild.yaml @@ -1,8 +1,9 @@ steps: # Decrypt the SSH key. - - name: "gcr.io/cloud-builders/gcloud" + - name: "gcr.io/google.com/cloudsdktool/cloud-sdk:slim" args: [ + "gcloud", "kms", "decrypt", "--ciphertext-file=deploy_key.enc", @@ -13,9 +14,10 @@ steps: ] # Decrypt the Twitter credentials. - - name: "gcr.io/cloud-builders/gcloud" + - name: "gcr.io/google.com/cloudsdktool/cloud-sdk:slim" args: [ + "gcloud", "kms", "decrypt", "--ciphertext-file=twitter.json.enc", @@ -26,9 +28,10 @@ steps: ] # Decrypt the npm credentials. - - name: "gcr.io/cloud-builders/gcloud" + - name: "gcr.io/google.com/cloudsdktool/cloud-sdk:slim" args: [ + "gcloud", "kms", "decrypt", "--ciphertext-file=npmrc.enc", @@ -39,9 +42,10 @@ steps: ] # Decrypt the hub (GitHub) credentials. - - name: "gcr.io/cloud-builders/gcloud" + - name: "gcr.io/google.com/cloudsdktool/cloud-sdk:slim" args: [ + "gcloud", "kms", "decrypt", "--ciphertext-file=hub.enc", diff --git a/scripts/publish/run.sh b/scripts/publish/run.sh index 4adb35b7206..f96b0d8a100 100755 --- a/scripts/publish/run.sh +++ b/scripts/publish/run.sh @@ -24,5 +24,6 @@ cd "$THIS_DIR" gcloud --project fir-tools-builds \ builds \ submit \ + --machine-type=e2-highcpu-8 \ --substitutions=_VERSION=$VERSION \ . \ No newline at end of file diff --git a/standalone/firepit.js b/standalone/firepit.js index 926757b7b90..137895d4ff7 100644 --- a/standalone/firepit.js +++ b/standalone/firepit.js @@ -145,7 +145,7 @@ const runtime = require("./runtime"); let config; try { config = require("./config"); -} catch (err: any) { +} catch (err) { console.warn("Invalid Firepit configuration, this may be a broken build."); process.exit(2); } @@ -684,7 +684,7 @@ node "${FindTool("npm/bin/npm-cli")[0]}" ${npmArgs.join(" ")} %*`, try { shell.mkdir("-p", runtimeBinsPath); - } catch (err: any) { + } catch (err) { debug(err); } @@ -693,7 +693,7 @@ node "${FindTool("npm/bin/npm-cli")[0]}" ${npmArgs.join(" ")} %*`, const runtimeBinPath = path.join(runtimeBinsPath, filename); try { shell.rm("-rf", runtimeBinPath); - } catch (err: any) { + } catch (err) { debug(err); } fs.writeFileSync(runtimeBinPath, runtimeBins[filename]); @@ -840,7 +840,7 @@ function uninstallLegacyFirepit() { installedFirebaseToolsPackage = JSON.parse( shell.cat(installedFirebaseToolsPackagePath) ); - } catch (err: any) { + } catch (err) { debug("No existing firebase-tools install found."); } From f35dedd11981d0664b3574c819f837869a4d265e Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 12 Jan 2022 13:23:56 -0800 Subject: [PATCH 0026/1699] Refactor Functions runtime delegation process to no longer depend on cli options and context (#3994) Code for parsing CF3 triggers from function source relies on CLI options/context object to retrieve active project id, functions source directory, project root, etc. The refactoring here pulls out all references to CLI options and context object from the runtime delegate caller. This is done in preparation for having another caller for the runtime delegate (the Functions Emulator) in the near future. --- src/deploy/functions/prepare.ts | 32 ++++++++++------- src/deploy/functions/runtimes/golang/index.ts | 15 +++----- src/deploy/functions/runtimes/index.ts | 36 +++++++++---------- src/deploy/functions/runtimes/node/index.ts | 19 ++++------ .../functions/runtimes/node/validate.ts | 6 ++-- src/deploy/functions/validate.ts | 17 ++++----- src/test/deploy/functions/validate.spec.ts | 4 +-- 7 files changed, 55 insertions(+), 74 deletions(-) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 345a01bae97..f01cbf6bd86 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -20,7 +20,8 @@ import * as utils from "../../utils"; import { logger } from "../../logger"; import { ensureTriggerRegions } from "./triggerRegionHelper"; import { ensureServiceAgentRoles } from "./checkIam"; -import e from "express"; +import { DelegateContext } from "./runtimes"; +import { FirebaseError } from "../../error"; function hasUserConfig(config: Record): boolean { // "firebase" key is always going to exist in runtime config. @@ -53,18 +54,28 @@ export async function prepare( options: Options, payload: args.Payload ): Promise { - if (!options.config.src.functions) { - return; + const projectId = needProjectId(options); + + const sourceDirName = options.config.get("functions.source") as string; + if (!sourceDirName) { + throw new FirebaseError( + `No functions code detected at default location (./functions), and no functions.source defined in firebase.json` + ); } + const sourceDir = options.config.path(sourceDirName); - const runtimeDelegate = await runtimes.getRuntimeDelegate(context, options); + const delegateContext: DelegateContext = { + projectId, + sourceDir, + projectDir: options.config.projectDir, + runtime: (options.config.get("functions.runtime") as string) || "", + }; + const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext); logger.debug(`Validating ${runtimeDelegate.name} source`); await runtimeDelegate.validate(); logger.debug(`Building ${runtimeDelegate.name} source`); await runtimeDelegate.build(); - const projectId = needProjectId(options); - // Check that all necessary APIs are enabled. const checkAPIsEnabled = await Promise.all([ ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"), @@ -85,14 +96,9 @@ export async function prepare( context.firebaseConfig = firebaseConfig; const runtimeConfig = await getFunctionsConfig(context); - utils.assertDefined( - options.config.src.functions.source, - "Error: 'functions.source' is not defined" - ); - const source = options.config.src.functions.source; const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); const userEnvOpt = { - functionsSource: options.config.path(source), + functionsSource: sourceDir, projectId: projectId, projectAlias: options.projectAlias, }; @@ -133,7 +139,7 @@ export async function prepare( logBullet( clc.cyan.bold("functions:") + " preparing " + - clc.bold(options.config.src.functions.source) + + clc.bold(sourceDirName) + " directory for uploading..." ); } diff --git a/src/deploy/functions/runtimes/golang/index.ts b/src/deploy/functions/runtimes/golang/index.ts index 938570aa47e..968dc881be0 100644 --- a/src/deploy/functions/runtimes/golang/index.ts +++ b/src/deploy/functions/runtimes/golang/index.ts @@ -6,12 +6,9 @@ import * as portfinder from "portfinder"; import * as spawn from "cross-spawn"; import { FirebaseError } from "../../../../error"; -import { Options } from "../../../../options"; import { logger } from "../../../../logger"; -import * as args from "../../args"; import * as backend from "../../backend"; import * as discovery from "../discovery"; -import { needProjectId } from "../../../../projectUtils"; import * as gomod from "./gomod"; import * as runtimes from ".."; @@ -27,13 +24,9 @@ export const FUNCTIONS_CODEGEN = FUNCTIONS_SDK + "/support/codegen"; export const FUNCTIONS_RUNTIME = FUNCTIONS_SDK + "/support/runtime"; export async function tryCreateDelegate( - context: args.Context, - options: Options + context: runtimes.DelegateContext ): Promise { - const relativeSourceDir = options.config.get("functions.source") as string; - const sourceDir = options.config.path(relativeSourceDir); - const goModPath = path.join(sourceDir, "go.mod"); - const projectId = needProjectId(options); + const goModPath = path.join(context.sourceDir, "go.mod"); let module: gomod.Module; try { @@ -44,7 +37,7 @@ export async function tryCreateDelegate( return; } - let runtime = options.config.get("functions.runtime"); + let runtime = context.runtime; if (!runtime) { if (!module.version) { throw new FirebaseError("Could not detect Golang version from go.mod"); @@ -61,7 +54,7 @@ export async function tryCreateDelegate( runtime = VERSION_TO_RUNTIME[module.version]; } - return new Delegate(projectId, sourceDir, runtime, module); + return new Delegate(context.projectId, context.sourceDir, runtime, module); } export class Delegate { diff --git a/src/deploy/functions/runtimes/index.ts b/src/deploy/functions/runtimes/index.ts index d5147760ed5..d098b1dd45c 100644 --- a/src/deploy/functions/runtimes/index.ts +++ b/src/deploy/functions/runtimes/index.ts @@ -1,6 +1,4 @@ -import { Options } from "../../../options"; import * as backend from "../backend"; -import * as args from "../args"; import * as golang from "./golang"; import * as node from "./node"; import * as validate from "../validate"; @@ -100,36 +98,34 @@ export interface RuntimeDelegate { ): Promise; } -type Factory = (context: args.Context, options: Options) => Promise; +export interface DelegateContext { + projectId: string; + // Absolute path of the Firebase project directory. + projectDir: string; + // Absolute path of the source directory. + sourceDir: string; + runtime: string; +} + +type Factory = (context: DelegateContext) => Promise; const factories: Factory[] = [node.tryCreateDelegate, golang.tryCreateDelegate]; -export async function getRuntimeDelegate( - context: args.Context, - options: Options -): Promise { - const sourceDirName = options.config.get("functions.source") as string; - if (!sourceDirName) { - throw new FirebaseError( - `No functions code detected at default location (./functions), and no functions.source defined in firebase.json` - ); - } - validate.functionsDirectoryExists(options, sourceDirName); +export async function getRuntimeDelegate(context: DelegateContext): Promise { + const { projectDir, sourceDir, runtime } = context; + validate.functionsDirectoryExists(sourceDir, projectDir); // There isn't currently an easy way to map from runtime name to a delegate, but we can at least guarantee // that any explicit runtime from firebase.json is valid - const runtime = options.config.get("functions.runtime"); if (runtime && !isValidRuntime(runtime)) { - throw new FirebaseError("Cannot deploy function with runtime " + runtime); + throw new FirebaseError(`Cannot deploy function with runtime ${runtime}`); } for (const factory of factories) { - const delegate = await factory(context, options); + const delegate = await factory(context); if (delegate) { return delegate; } } - throw new FirebaseError( - `Could not detect language for functions at ${options.config.get("functions.source")}` - ); + throw new FirebaseError(`Could not detect language for functions at ${sourceDir}`); } diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index e7d96c308b4..3d27bb266d0 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -3,24 +3,18 @@ import * as fs from "fs"; import * as path from "path"; import { FirebaseError } from "../../../../error"; -import { Options } from "../../../../options"; import { getRuntimeChoice } from "./parseRuntimeAndValidateSDK"; -import * as args from "../../args"; +import { logger } from "../../../../logger"; import * as backend from "../../backend"; -import { needProjectId } from "../../../../projectUtils"; import * as runtimes from ".."; import * as validate from "./validate"; -import { logger } from "../../../../logger"; import * as versioning from "./versioning"; import * as parseTriggers from "./parseTriggers"; export async function tryCreateDelegate( - context: args.Context, - options: Options + context: runtimes.DelegateContext ): Promise { - const projectRelativeSourceDir = options.config.get("functions.source") as string; - const sourceDir = options.config.path(projectRelativeSourceDir); - const packageJsonPath = path.join(sourceDir, "package.json"); + const packageJsonPath = path.join(context.sourceDir, "package.json"); if (!(await promisify(fs.exists)(packageJsonPath))) { logger.debug("Customer code is not Node"); @@ -28,10 +22,9 @@ export async function tryCreateDelegate( } // Check what runtime to use, first in firebase.json, then in 'engines' field. - let runtime = (options.config.get("functions.runtime") as runtimes.Runtime) || ""; // TODO: This method loads the Functions SDK version which is then manually loaded elsewhere. // We should find a way to refactor this code so we're not repeatedly invoking node. - runtime = getRuntimeChoice(sourceDir, runtime); + const runtime = getRuntimeChoice(context.sourceDir, context.runtime); if (!runtime.startsWith("nodejs")) { logger.debug( @@ -40,7 +33,7 @@ export async function tryCreateDelegate( throw new FirebaseError(`Unexpected runtime ${runtime}`); } - return new Delegate(needProjectId(options), options.config.projectDir, sourceDir, runtime); + return new Delegate(context.projectId, context.projectDir, context.sourceDir, runtime); } // TODO(inlined): Consider moving contents in parseRuntimeAndValidateSDK and validate around. @@ -60,7 +53,7 @@ export class Delegate { // Using a caching interface because we (may/will) eventually depend on the SDK version // to decide whether to use the JS export method of discovery or the HTTP container contract // method of discovery. - _sdkVersion: string = ""; + _sdkVersion = ""; get sdkVersion() { if (!this._sdkVersion) { this._sdkVersion = versioning.getFunctionsSDKVersion(this.sourceDir) || ""; diff --git a/src/deploy/functions/runtimes/node/validate.ts b/src/deploy/functions/runtimes/node/validate.ts index 512aea0a2a1..b56c9932b40 100644 --- a/src/deploy/functions/runtimes/node/validate.ts +++ b/src/deploy/functions/runtimes/node/validate.ts @@ -18,10 +18,8 @@ const cjson = require("cjson"); function assertFunctionsSourcePresent(data: any, sourceDir: string, projectDir: string): void { const indexJsFile = path.join(sourceDir, data.main || "index.js"); if (!fsutils.fileExistsSync(indexJsFile)) { - const msg = `${path.relative( - projectDir, - indexJsFile - )} does not exist, can't deploy Cloud Functions`; + const relativeMainPath = path.relative(projectDir, indexJsFile); + const msg = `${relativeMainPath} does not exist, can't deploy Cloud Functions`; throw new FirebaseError(msg); } } diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 659a44df0a0..def1fc2e99c 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -1,23 +1,18 @@ +import * as path from "path"; import * as clc from "cli-color"; import { FirebaseError } from "../../error"; -import { getFunctionLabel } from "./functionsDeployHelper"; -import * as backend from "./backend"; import * as fsutils from "../../fsutils"; -import * as projectPath from "../../projectPath"; /** * Check that functions directory exists. - * @param options options object. In prod is an Options; in tests can just be {cwd: string} - * @param sourceDirName Relative path to source directory. + * @param sourceDir Absolute path to source directory. + * @param projectDir Absolute path to project directory. * @throws { FirebaseError } Functions directory must exist. */ -export function functionsDirectoryExists( - options: { cwd: string; configPath?: string }, - sourceDirName: string -): void { - // Note(inlined): What's the difference between this and options.config.path(sourceDirName)? - if (!fsutils.dirExistsSync(projectPath.resolveProjectPath(options, sourceDirName))) { +export function functionsDirectoryExists(sourceDir: string, projectDir: string): void { + if (!fsutils.dirExistsSync(sourceDir)) { + const sourceDirName = path.relative(projectDir, sourceDir); const msg = `could not deploy functions because the ${clc.bold('"' + sourceDirName + '"')} ` + `directory was not found. Please create it or specify a different source directory in firebase.json`; diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index 5a2d3e6a1b7..395ab96568c 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -26,7 +26,7 @@ describe("validate", () => { dirExistsStub.returns(true); expect(() => { - validate.functionsDirectoryExists({ cwd: "cwd" }, "sourceDirName"); + validate.functionsDirectoryExists("/cwd/sourceDirName", "/cwd"); }).to.not.throw(); }); @@ -35,7 +35,7 @@ describe("validate", () => { dirExistsStub.returns(false); expect(() => { - validate.functionsDirectoryExists({ cwd: "cwd" }, "sourceDirName"); + validate.functionsDirectoryExists("/cwd/sourceDirName", "/cwd"); }).to.throw(FirebaseError); }); }); From a8ccc7afec817389a7ab5565a1bbf59f24bd68bd Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 12 Jan 2022 15:44:03 -0800 Subject: [PATCH 0027/1699] audit fix (#4013) --- npm-shrinkwrap.json | 1583 +++++++++++++++++++++---------------------- 1 file changed, 773 insertions(+), 810 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index cbe2c75afcc..e2493e45060 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -654,16 +654,6 @@ "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==", "dev": true }, - "node_modules/@firebase/analytics/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/analytics/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -688,22 +678,77 @@ "xmlhttprequest": "1.8.0" } }, + "node_modules/@firebase/app-compat": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.13.tgz", + "integrity": "sha512-K5eFU0bIbGTTRPihZEc1BtuOTwEtiKhu2tm4e+g9+c5cMSpJvr+GIQaN8A8SgDeqt13DP9lKqTic2NiG+6EQCw==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/app": "0.7.12", + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/app": { + "version": "0.7.12", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.12.tgz", + "integrity": "sha512-eniX/KcMA/iTuRqdYvMuRaPj3DGxWdXa5r2tsmtLbx8HvdY/Wzq3H0p7fyapBRPsg0rO+t3xzWDVZ3Blq2xfCA==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/component": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", + "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/util": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", + "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true, + "peer": true + }, "node_modules/@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", "dev": true }, - "node_modules/@firebase/app/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/app/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -746,39 +791,160 @@ } }, "node_modules/@firebase/component": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", - "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/component/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", "dev": true, "dependencies": { - "@firebase/util": "0.3.4", "tslib": "^1.11.1" } }, "node_modules/@firebase/database": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.1.tgz", - "integrity": "sha512-/1HhR4ejpqUaM9Cn3KSeNdQvdlehWIhdfTVWFxS73ZlLYf7ayk9jITwH10H3ZOIm5yNzxF67p/U7Z/0IPhgWaQ==", + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", "dev": true, "dependencies": { "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.21", - "@firebase/database-types": "0.6.1", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.4", + "@firebase/util": "0.3.2", "faye-websocket": "0.11.3", "tslib": "^1.11.1" } }, + "node_modules/@firebase/database-compat": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.5.tgz", + "integrity": "sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==", + "dev": true, + "dependencies": { + "@firebase/component": "0.5.10", + "@firebase/database": "0.12.5", + "@firebase/database-types": "0.9.4", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==", + "dev": true + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/component": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", + "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "dev": true, + "dependencies": { + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/database": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.5.tgz", + "integrity": "sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==", + "dev": true, + "dependencies": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/database-types": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.4.tgz", + "integrity": "sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==", + "dev": true, + "dependencies": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.4.3" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/util": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", + "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/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==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/@firebase/database-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz", - "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", "dev": true, "dependencies": { "@firebase/app-types": "0.6.1" } }, + "node_modules/@firebase/database/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/firestore": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.18.0.tgz", @@ -812,16 +978,6 @@ "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/firestore/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/firestore/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -854,25 +1010,6 @@ "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==", "dev": true }, - "node_modules/@firebase/functions/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "node_modules/@firebase/functions/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/installations": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.17.tgz", @@ -899,16 +1036,6 @@ "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/installations/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/installations/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -951,16 +1078,6 @@ "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/messaging/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/messaging/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -994,16 +1111,6 @@ "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==", "dev": true }, - "node_modules/@firebase/performance/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/performance/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1048,16 +1155,6 @@ "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==", "dev": true }, - "node_modules/@firebase/remote-config/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/remote-config/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1093,16 +1190,6 @@ "@firebase/util": "0.x" } }, - "node_modules/@firebase/storage/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/storage/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1117,6 +1204,7 @@ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", "dev": true, + "peer": true, "dependencies": { "tslib": "^1.11.1" } @@ -1489,14 +1577,6 @@ "node": ">=10" } }, - "node_modules/@grpc/grpc-js/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==", - "engines": { - "node": ">=8" - } - }, "node_modules/@grpc/grpc-js/node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -1576,19 +1656,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@grpc/grpc-js/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@grpc/grpc-js/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -1973,6 +2040,15 @@ "node": ">=8.0.0" } }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2246,6 +2322,16 @@ "@types/serve-static": "*" } }, + "node_modules/@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "node_modules/@types/express-serve-static-core": { "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", @@ -2257,6 +2343,15 @@ "@types/range-parser": "*" } }, + "node_modules/@types/express-unless": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.2.tgz", + "integrity": "sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/fs-extra": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", @@ -3090,27 +3185,6 @@ "string-width": "^4.1.0" } }, - "node_modules/ansi-align/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -3411,15 +3485,24 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/atlassian-openapi": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.13.tgz", - "integrity": "sha512-2/CRqPWZ15BBr9s6/c48QaBKvpxbonTeFGGXKFSmcSVFqH0KfFMWkgMSnCWKyAQ7gZ8Ch9BrqCDAG7ENzFWX2A==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.15.tgz", + "integrity": "sha512-HzgdBHJ/9jZWZfass5DRJNG4vLxoFl6Zcl3B+8Cp2VSpEH7t0laBGnGtcthvj2h73hq8dzjKtVlG30agBZ4OPw==", "dev": true, "dependencies": { - "jsonpointer": "^4.0.1", + "jsonpointer": "^5.0.0", "urijs": "^1.18.10" } }, + "node_modules/atlassian-openapi/node_modules/jsonpointer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", + "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3631,27 +3714,6 @@ "node": ">=8" } }, - "node_modules/boxen/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/boxen/node_modules/supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -4075,29 +4137,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/cliui/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/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==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -6145,30 +6184,52 @@ } }, "node_modules/firebase-admin": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.4.2.tgz", - "integrity": "sha512-mRnBJbW6BAz6DJkZ0GOUTkmnmCrwVzMreMc6O+RXWukFydOzi5Xr6TKSiPKxoOQw41r9IluP2AZ3Qzvlx2SR+g==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", + "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", "dev": true, "dependencies": { - "@firebase/database": "^0.8.1", - "@firebase/database-types": "^0.6.1", - "@types/node": "^10.10.0", + "@firebase/database-compat": "^0.1.1", + "@firebase/database-types": "^0.7.2", + "@types/node": ">=12.12.47", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", "node-forge": "^0.10.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=10.13.0" }, "optionalDependencies": { "@google-cloud/firestore": "^4.5.0", "@google-cloud/storage": "^5.3.0" } }, + "node_modules/firebase-admin/node_modules/@firebase/app-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", + "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==", + "dev": true + }, + "node_modules/firebase-admin/node_modules/@firebase/database-types": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", + "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "dev": true, + "dependencies": { + "@firebase/app-types": "0.6.3" + } + }, + "node_modules/firebase-admin/node_modules/@types/node": { + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", + "dev": true + }, "node_modules/firebase-functions": { - "version": "3.15.7", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.15.7.tgz", - "integrity": "sha512-ZD7r8eoWWebgs+mTqfH8NLUT2C0f7/cyAvIA1RSUdBVQZN7MBBt3oSlN/rL3e+m6tdlJz6YbQ3hrOKOGjOVYvQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.16.0.tgz", + "integrity": "sha512-6ISOn0JckMtpA3aJ/+wCCGhThUhBUrpZD+tSkUeolx0Vr+NoYFXA0+2YzJZa/A2MDU8gotPzUtnauLSEQvfClQ==", "dev": true, "dependencies": { "@types/cors": "^2.8.5", @@ -6181,7 +6242,7 @@ "node": "^8.13.0 || >=10.10.0" }, "peerDependencies": { - "firebase-admin": "^8.0.0 || ^9.0.0" + "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, "node_modules/firebase-functions/node_modules/@types/express": { @@ -6195,40 +6256,6 @@ "@types/serve-static": "*" } }, - "node_modules/firebase/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "node_modules/firebase/node_modules/@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", - "dev": true, - "dependencies": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "faye-websocket": "0.11.3", - "tslib": "^1.11.1" - } - }, - "node_modules/firebase/node_modules/@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", - "dev": true, - "dependencies": { - "@firebase/app-types": "0.6.1" - } - }, "node_modules/firebase/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -7070,11 +7097,11 @@ "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "node_modules/google-p12-pem": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", - "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", "dependencies": { - "node-forge": "^0.10.0" + "node-forge": "^1.0.0" }, "bin": { "gp12-pem": "build/src/bin/gp12-pem.js" @@ -7083,6 +7110,14 @@ "node": ">=10" } }, + "node_modules/google-p12-pem/node_modules/node-forge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", + "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -7582,27 +7617,6 @@ "node": ">=8" } }, - "node_modules/inquirer/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/inquirer/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7693,12 +7707,11 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "optional": true, + "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==", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/is-glob": { @@ -8031,6 +8044,21 @@ "valid-url": "^1" } }, + "node_modules/jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "dev": true, + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "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", @@ -8301,6 +8329,45 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwks-rsa": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", + "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", + "dev": true, + "dependencies": { + "@types/express-jwt": "0.0.42", + "debug": "^4.3.2", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=10 < 13 || >=14" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -8391,6 +8458,12 @@ "node": ">= 0.8.0" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -8445,6 +8518,12 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -8693,6 +8772,32 @@ "node": ">=10" } }, + "node_modules/lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dev": true, + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, "node_modules/lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -9371,6 +9476,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "dev": true, "engines": { "node": ">= 6.0.0" } @@ -9758,15 +9864,6 @@ "node": ">=8" } }, - "node_modules/nyc/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/nyc/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -9800,20 +9897,6 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nyc/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -10744,6 +10827,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "node_modules/psl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", @@ -11912,37 +12001,16 @@ } }, "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "optional": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { - "ansi-regex": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/strip-ansi": { @@ -12388,15 +12456,6 @@ "node": ">=8" } }, - "node_modules/swagger2openapi/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/swagger2openapi/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -12421,20 +12480,6 @@ "node": ">=8" } }, - "node_modules/swagger2openapi/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/swagger2openapi/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -13247,14 +13292,6 @@ "node": ">=10" } }, - "node_modules/update-notifier/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==", - "engines": { - "node": ">=8" - } - }, "node_modules/update-notifier/node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -13295,19 +13332,6 @@ "node": ">=10" } }, - "node_modules/update-notifier/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/update-notifier/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13507,27 +13531,6 @@ "node": ">=8" } }, - "node_modules/widest-line/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", @@ -13633,27 +13636,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/wrap-ansi/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13816,29 +13798,6 @@ "node": ">=8" } }, - "node_modules/yargs/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/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==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -14354,16 +14313,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14396,24 +14345,81 @@ "xmlhttprequest": "1.8.0" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", "dev": true, "requires": { - "@firebase/util": "0.3.2", "tslib": "^1.11.1" } + } + } + }, + "@firebase/app-compat": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.13.tgz", + "integrity": "sha512-K5eFU0bIbGTTRPihZEc1BtuOTwEtiKhu2tm4e+g9+c5cMSpJvr+GIQaN8A8SgDeqt13DP9lKqTic2NiG+6EQCw==", + "dev": true, + "peer": true, + "requires": { + "@firebase/app": "0.7.12", + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/app": { + "version": "0.7.12", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.12.tgz", + "integrity": "sha512-eniX/KcMA/iTuRqdYvMuRaPj3DGxWdXa5r2tsmtLbx8HvdY/Wzq3H0p7fyapBRPsg0rO+t3xzWDVZ3Blq2xfCA==", + "dev": true, + "peer": true, + "requires": { + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } }, - "@firebase/util": { + "@firebase/component": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", + "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "dev": true, + "peer": true, + "requires": { + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "@firebase/logger": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", "dev": true, + "peer": true, "requires": { - "tslib": "^1.11.1" + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", + "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.1.0" } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true, + "peer": true } } }, @@ -14447,34 +14453,152 @@ "requires": {} }, "@firebase/component": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", - "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", "dev": true, "requires": { - "@firebase/util": "0.3.4", + "@firebase/util": "0.3.2", "tslib": "^1.11.1" + }, + "dependencies": { + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + } + } } }, "@firebase/database": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.1.tgz", - "integrity": "sha512-/1HhR4ejpqUaM9Cn3KSeNdQvdlehWIhdfTVWFxS73ZlLYf7ayk9jITwH10H3ZOIm5yNzxF67p/U7Z/0IPhgWaQ==", + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", "dev": true, "requires": { "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.21", - "@firebase/database-types": "0.6.1", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.4", + "@firebase/util": "0.3.2", "faye-websocket": "0.11.3", "tslib": "^1.11.1" + }, + "dependencies": { + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + } + } + } + }, + "@firebase/database-compat": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.5.tgz", + "integrity": "sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==", + "dev": true, + "requires": { + "@firebase/component": "0.5.10", + "@firebase/database": "0.12.5", + "@firebase/database-types": "0.9.4", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==", + "dev": true + }, + "@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "dev": true, + "requires": {} + }, + "@firebase/component": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", + "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "dev": true, + "requires": { + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.5.tgz", + "integrity": "sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==", + "dev": true, + "requires": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "@firebase/database-types": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.4.tgz", + "integrity": "sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==", + "dev": true, + "requires": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.4.3" + } + }, + "@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", + "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } } }, "@firebase/database-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz", - "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", "dev": true, "requires": { "@firebase/app-types": "0.6.1" @@ -14497,16 +14621,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14536,27 +14650,6 @@ "@firebase/messaging-types": "0.5.0", "node-fetch": "2.6.1", "tslib": "^1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - } } }, "@firebase/functions-types": { @@ -14578,16 +14671,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14624,18 +14707,8 @@ "@firebase/util": "0.3.2", "idb": "3.0.2", "tslib": "^1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, + }, + "dependencies": { "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14668,16 +14741,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14720,16 +14783,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14759,16 +14812,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14792,6 +14835,7 @@ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", "dev": true, + "peer": true, "requires": { "tslib": "^1.11.1" } @@ -15101,11 +15145,6 @@ "lru-cache": "^6.0.0" } }, - "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==" - }, "jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -15173,16 +15212,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -15474,6 +15503,12 @@ "@opentelemetry/semantic-conventions": "^0.11.0" } }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "dev": true + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -15738,6 +15773,16 @@ "@types/serve-static": "*" } }, + "@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "@types/express-serve-static-core": { "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", @@ -15749,6 +15794,15 @@ "@types/range-parser": "*" } }, + "@types/express-unless": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.2.tgz", + "integrity": "sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/fs-extra": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", @@ -16409,23 +16463,6 @@ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "requires": { "string-width": "^4.1.0" - }, - "dependencies": { - "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==" - }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } } }, "ansi-colors": { @@ -16687,13 +16724,21 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atlassian-openapi": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.13.tgz", - "integrity": "sha512-2/CRqPWZ15BBr9s6/c48QaBKvpxbonTeFGGXKFSmcSVFqH0KfFMWkgMSnCWKyAQ7gZ8Ch9BrqCDAG7ENzFWX2A==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.15.tgz", + "integrity": "sha512-HzgdBHJ/9jZWZfass5DRJNG4vLxoFl6Zcl3B+8Cp2VSpEH7t0laBGnGtcthvj2h73hq8dzjKtVlG30agBZ4OPw==", "dev": true, "requires": { - "jsonpointer": "^4.0.1", + "jsonpointer": "^5.0.0", "urijs": "^1.18.10" + }, + "dependencies": { + "jsonpointer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", + "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", + "dev": true + } } }, "aws-sign2": { @@ -16865,21 +16910,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "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==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -17187,25 +17217,6 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "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==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } } }, "clone": { @@ -18795,40 +18806,6 @@ "@firebase/util": "0.3.2" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", - "dev": true, - "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "faye-websocket": "0.11.3", - "tslib": "^1.11.1" - } - }, - "@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", - "dev": true, - "requires": { - "@firebase/app-types": "0.6.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -18841,25 +18818,49 @@ } }, "firebase-admin": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.4.2.tgz", - "integrity": "sha512-mRnBJbW6BAz6DJkZ0GOUTkmnmCrwVzMreMc6O+RXWukFydOzi5Xr6TKSiPKxoOQw41r9IluP2AZ3Qzvlx2SR+g==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", + "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", "dev": true, "requires": { - "@firebase/database": "^0.8.1", - "@firebase/database-types": "^0.6.1", + "@firebase/database-compat": "^0.1.1", + "@firebase/database-types": "^0.7.2", "@google-cloud/firestore": "^4.5.0", "@google-cloud/storage": "^5.3.0", - "@types/node": "^10.10.0", + "@types/node": ">=12.12.47", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", "node-forge": "^0.10.0" + }, + "dependencies": { + "@firebase/app-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", + "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==", + "dev": true + }, + "@firebase/database-types": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", + "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "dev": true, + "requires": { + "@firebase/app-types": "0.6.3" + } + }, + "@types/node": { + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", + "dev": true + } } }, "firebase-functions": { - "version": "3.15.7", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.15.7.tgz", - "integrity": "sha512-ZD7r8eoWWebgs+mTqfH8NLUT2C0f7/cyAvIA1RSUdBVQZN7MBBt3oSlN/rL3e+m6tdlJz6YbQ3hrOKOGjOVYvQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.16.0.tgz", + "integrity": "sha512-6ISOn0JckMtpA3aJ/+wCCGhThUhBUrpZD+tSkUeolx0Vr+NoYFXA0+2YzJZa/A2MDU8gotPzUtnauLSEQvfClQ==", "dev": true, "requires": { "@types/cors": "^2.8.5", @@ -19578,11 +19579,18 @@ } }, "google-p12-pem": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", - "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", "requires": { - "node-forge": "^0.10.0" + "node-forge": "^1.0.0" + }, + "dependencies": { + "node-forge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", + "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==" + } } }, "got": { @@ -19973,21 +19981,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "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==" - }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -20052,10 +20045,9 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "optional": true + "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==" }, "is-glob": { "version": "4.0.3", @@ -20315,6 +20307,15 @@ "valid-url": "^1" } }, + "jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "dev": true, + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -20536,6 +20537,36 @@ "safe-buffer": "^5.0.1" } }, + "jwks-rsa": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", + "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", + "dev": true, + "requires": { + "@types/express-jwt": "0.0.42", + "debug": "^4.3.2", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -20613,6 +20644,12 @@ "type-check": "~0.3.2" } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -20661,6 +20698,12 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -20877,6 +20920,34 @@ "yallist": "^4.0.0" } }, + "lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dev": true, + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dev": true, + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -21406,7 +21477,8 @@ "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "dev": true }, "node-gyp": { "version": "7.1.2", @@ -21706,12 +21778,6 @@ "path-exists": "^4.0.0" } }, - "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==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -21736,17 +21802,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -22460,6 +22515,12 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "psl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", @@ -23386,30 +23447,13 @@ } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "optional": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { @@ -23761,12 +23805,6 @@ "path-exists": "^4.0.0" } }, - "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==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -23785,17 +23823,6 @@ "p-limit": "^2.2.0" } }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -24429,11 +24456,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" }, - "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==" - }, "is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -24456,16 +24478,6 @@ "lru-cache": "^6.0.0" } }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -24623,23 +24635,6 @@ "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "requires": { "string-width": "^4.0.0" - }, - "dependencies": { - "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==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - } } }, "winston": { @@ -24724,21 +24719,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "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==" - }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } } } }, @@ -24815,23 +24795,6 @@ "yargs-parser": "^20.2.2" }, "dependencies": { - "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==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", From ab014c2c7574a570474bf71f7f28aa1f89abf1ff Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 13 Jan 2022 16:08:39 -0800 Subject: [PATCH 0028/1699] remove typing from javascript... string (#4016) * remove typing from javascript... string * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/emulator/auth/widget_ui.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..9690af852ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes issue where Auth UI did not behave correctly. diff --git a/src/emulator/auth/widget_ui.ts b/src/emulator/auth/widget_ui.ts index 96ca57f727d..32e3c4e1739 100644 --- a/src/emulator/auth/widget_ui.ts +++ b/src/emulator/auth/widget_ui.ts @@ -91,7 +91,7 @@ function sendAuthEventViaIframeRelay(authEvent, cb) { }, '*'); sent = true; } - } catch (e: any) { + } catch (e) { // The frame does not have the same origin } } From f3135530625322934aa926744ff12a9c83359a4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jan 2022 21:58:11 +0000 Subject: [PATCH 0029/1699] Bump shelljs from 0.8.3 to 0.8.5 in /scripts/firepit-builder (#4018) Bumps [shelljs](https://github.com/shelljs/shelljs) from 0.8.3 to 0.8.5. - [Release notes](https://github.com/shelljs/shelljs/releases) - [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md) - [Commits](https://github.com/shelljs/shelljs/compare/v0.8.3...v0.8.5) --- updated-dependencies: - dependency-name: shelljs dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- scripts/firepit-builder/package-lock.json | 60 +++++++++++++++++------ scripts/firepit-builder/package.json | 2 +- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/scripts/firepit-builder/package-lock.json b/scripts/firepit-builder/package-lock.json index f1858f6d1c1..4d6984bc9b2 100644 --- a/scripts/firepit-builder/package-lock.json +++ b/scripts/firepit-builder/package-lock.json @@ -18,9 +18,9 @@ } }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "brace-expansion": { "version": "1.1.11", @@ -87,15 +87,20 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "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==" }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -105,6 +110,14 @@ "path-is-absolute": "^1.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -120,9 +133,17 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "requires": { + "has": "^1.0.3" + } }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -209,11 +230,13 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", + "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", "requires": { - "path-parse": "^1.0.6" + "is-core-module": "^2.8.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "set-blocking": { @@ -222,9 +245,9 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "shelljs": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", - "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "requires": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -249,6 +272,11 @@ "ansi-regex": "^4.1.0" } }, + "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==" + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", diff --git a/scripts/firepit-builder/package.json b/scripts/firepit-builder/package.json index 6bfba1b5306..3c6d77cc8a7 100644 --- a/scripts/firepit-builder/package.json +++ b/scripts/firepit-builder/package.json @@ -11,7 +11,7 @@ "license": "MIT", "private": true, "dependencies": { - "shelljs": "^0.8.3", + "shelljs": "^0.8.5", "yargs": "^13.3.0" } } From 31684e6f2e3ae50d369cb9fd7f3b74f941d1766d Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 19 Jan 2022 00:35:32 +0000 Subject: [PATCH 0030/1699] 10.1.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e2493e45060..039f74aa530 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.1.0", + "version": "10.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.1.0", + "version": "10.1.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.7.0", diff --git a/package.json b/package.json index 38c7edbdd59..4faf3aa063f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.1.0", + "version": "10.1.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 653c838e18d25a464bf407d20869958182094d60 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 19 Jan 2022 00:35:57 +0000 Subject: [PATCH 0031/1699] [firebase-release] Removed change log and reset repo after 10.1.1 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9690af852ee..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes issue where Auth UI did not behave correctly. From 67183216e3fcb4087735b0a20a6222f096248e7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 10:05:51 -0800 Subject: [PATCH 0032/1699] Bump marked from 0.7.0 to 4.0.10 (#4017) * Bump marked from 0.7.0 to 4.0.10 Bumps [marked](https://github.com/markedjs/marked) from 0.7.0 to 4.0.10. - [Release notes](https://github.com/markedjs/marked/releases) - [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json) - [Commits](https://github.com/markedjs/marked/compare/v0.7.0...v4.0.10) --- updated-dependencies: - dependency-name: marked dependency-type: direct:production ... Signed-off-by: dependabot[bot] * fix marked imports * remove comments * fix marked-terminal types * fix weird import of marked * fix importing of marked * update marked-terminal to use the @types package Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bryan Kendall --- npm-shrinkwrap.json | 67 ++++++++++++++----- package.json | 5 +- src/bin/firebase.js | 2 +- src/commands/ext-configure.ts | 3 +- src/commands/ext-dev-init.ts | 3 +- src/commands/ext-dev-publish.ts | 3 +- src/commands/ext-dev-register.ts | 3 +- src/commands/ext-info.ts | 3 +- src/commands/ext-install.ts | 3 +- src/commands/ext-uninstall.ts | 3 +- src/commands/ext-update.ts | 3 +- src/commands/hosting-channel-create.ts | 3 +- src/commands/hosting-channel-delete.ts | 3 +- src/commands/hosting-channel-deploy.ts | 3 +- src/commands/hosting-clone.ts | 3 +- src/extensions/askUserForConsent.ts | 3 +- src/extensions/askUserForParam.ts | 3 +- src/extensions/billingMigrationHelper.ts | 3 +- src/extensions/changelog.ts | 3 +- src/extensions/displayExtensionInfo.ts | 3 +- src/extensions/extensionsApi.ts | 3 +- src/extensions/extensionsHelper.ts | 3 +- src/extensions/provisioningHelper.ts | 3 +- src/extensions/resolveSource.ts | 3 +- src/extensions/updateHelper.ts | 3 +- src/extensions/warnings.ts | 3 +- src/projectUtils.ts | 3 +- .../extensions/billingMigrationHelper.spec.ts | 18 ++--- src/types/marked-terminal/index.d.ts | 9 --- 29 files changed, 112 insertions(+), 61 deletions(-) delete mode 100644 src/types/marked-terminal/index.d.ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 039f74aa530..499a9a2d119 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -39,7 +39,7 @@ "jsonwebtoken": "^8.5.1", "leven": "^3.1.0", "lodash": "^4.17.21", - "marked": "^0.7.0", + "marked": "^4.0.10", "marked-terminal": "^3.3.0", "mime": "^2.5.2", "minimatch": "^3.0.4", @@ -92,7 +92,8 @@ "@types/js-yaml": "^3.12.2", "@types/jsonwebtoken": "^8.3.8", "@types/lodash": "^4.14.149", - "@types/marked": "^0.6.5", + "@types/marked": "^4.0.1", + "@types/marked-terminal": "^3.1.3", "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", "@types/node": "^10.17.50", @@ -2415,9 +2416,25 @@ "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" }, "node_modules/@types/marked": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.6.5.tgz", - "integrity": "sha512-6kBKf64aVfx93UJrcyEZ+OBM5nGv4RLsI6sR1Ar34bpgvGVRoyTgpxn4ZmtxOM5aDTAaaznYuYUH8bUX3Nk3YA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz", + "integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag==", + "dev": true + }, + "node_modules/@types/marked-terminal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/marked-terminal/-/marked-terminal-3.1.3.tgz", + "integrity": "sha512-dKgOLKlI5zFb2jTbRcyQqbdrHxeU74DCOkVIZtsoB2sc1ctXZ1iB2uxG2jjAuzoLdvwHP065ijN6Q8HecWdWYg==", + "dev": true, + "dependencies": { + "@types/marked": "^3", + "chalk": "^2.4.1" + } + }, + "node_modules/@types/marked-terminal/node_modules/@types/marked": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-3.0.3.tgz", + "integrity": "sha512-ZgAr847Wl68W+B0sWH7F4fDPxTzerLnRuUXjUpp1n4NjGSs8hgPAjAp7NQIXblG34MXTrf5wWkAK8PVJ2LIlVg==", "dev": true }, "node_modules/@types/mime": { @@ -8844,14 +8861,14 @@ } }, "node_modules/marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", + "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==", "bin": { - "marked": "bin/marked" + "marked": "bin/marked.js" }, "engines": { - "node": ">=0.10.0" + "node": ">= 12" } }, "node_modules/marked-terminal": { @@ -15866,11 +15883,29 @@ "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" }, "@types/marked": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.6.5.tgz", - "integrity": "sha512-6kBKf64aVfx93UJrcyEZ+OBM5nGv4RLsI6sR1Ar34bpgvGVRoyTgpxn4ZmtxOM5aDTAaaznYuYUH8bUX3Nk3YA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz", + "integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag==", "dev": true }, + "@types/marked-terminal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/marked-terminal/-/marked-terminal-3.1.3.tgz", + "integrity": "sha512-dKgOLKlI5zFb2jTbRcyQqbdrHxeU74DCOkVIZtsoB2sc1ctXZ1iB2uxG2jjAuzoLdvwHP065ijN6Q8HecWdWYg==", + "dev": true, + "requires": { + "@types/marked": "^3", + "chalk": "^2.4.1" + }, + "dependencies": { + "@types/marked": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-3.0.3.tgz", + "integrity": "sha512-ZgAr847Wl68W+B0sWH7F4fDPxTzerLnRuUXjUpp1n4NjGSs8hgPAjAp7NQIXblG34MXTrf5wWkAK8PVJ2LIlVg==", + "dev": true + } + } + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -20984,9 +21019,9 @@ "dev": true }, "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==" + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", + "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==" }, "marked-terminal": { "version": "3.3.0", diff --git a/package.json b/package.json index 4faf3aa063f..0c0e229ccfe 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "jsonwebtoken": "^8.5.1", "leven": "^3.1.0", "lodash": "^4.17.21", - "marked": "^0.7.0", + "marked": "^4.0.10", "marked-terminal": "^3.3.0", "mime": "^2.5.2", "minimatch": "^3.0.4", @@ -164,7 +164,8 @@ "@types/js-yaml": "^3.12.2", "@types/jsonwebtoken": "^8.3.8", "@types/lodash": "^4.14.149", - "@types/marked": "^0.6.5", + "@types/marked": "^4.0.1", + "@types/marked-terminal": "^3.1.3", "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", "@types/node": "^10.17.50", diff --git a/src/bin/firebase.js b/src/bin/firebase.js index 7540f3d7aa0..d833e7ef2ea 100755 --- a/src/bin/firebase.js +++ b/src/bin/firebase.js @@ -20,7 +20,7 @@ if (!semver.satisfies(nodeVersion, pkg.engines.node)) { const updateNotifier = require("update-notifier")({ pkg: pkg }); const clc = require("cli-color"); const TerminalRenderer = require("marked-terminal"); -const marked = require("marked"); +const marked = require("marked").marked; marked.setOptions({ renderer: new TerminalRenderer(), }); diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 5e14d1b9348..21bbf62a751 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -1,6 +1,7 @@ import * as _ from "lodash"; import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import * as ora from "ora"; import TerminalRenderer = require("marked-terminal"); diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index 2a30ba1f389..4d68da54af9 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -1,6 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; diff --git a/src/commands/ext-dev-publish.ts b/src/commands/ext-dev-publish.ts index 75307198177..97ba5eb6d24 100644 --- a/src/commands/ext-dev-publish.ts +++ b/src/commands/ext-dev-publish.ts @@ -1,5 +1,6 @@ import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); import { Command } from "../command"; diff --git a/src/commands/ext-dev-register.ts b/src/commands/ext-dev-register.ts index 32637fd0a89..f2f2e55c748 100644 --- a/src/commands/ext-dev-register.ts +++ b/src/commands/ext-dev-register.ts @@ -1,5 +1,6 @@ import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import { Command } from "../command"; import { registerPublisherProfile } from "../extensions/extensionsApi"; diff --git a/src/commands/ext-info.ts b/src/commands/ext-info.ts index eaeff779a06..d1a05b27d96 100644 --- a/src/commands/ext-info.ts +++ b/src/commands/ext-info.ts @@ -11,7 +11,8 @@ import { logger } from "../logger"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); const FUNCTION_TYPE_REGEX = /\..+\.function/; diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 85b07fc6946..3c620530c21 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -1,6 +1,7 @@ import * as _ from "lodash"; import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import * as ora from "ora"; import TerminalRenderer = require("marked-terminal"); diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 6e6f29d2cd2..1eea467d440 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -1,7 +1,8 @@ import * as _ from "lodash"; import * as clc from "cli-color"; import * as ora from "ora"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index e19e55f5348..e4af5c6c956 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -1,6 +1,7 @@ import * as clc from "cli-color"; import * as _ from "lodash"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import * as ora from "ora"; import TerminalRenderer = require("marked-terminal"); diff --git a/src/commands/hosting-channel-create.ts b/src/commands/hosting-channel-create.ts index 807f67e7667..9c5fb5f5682 100644 --- a/src/commands/hosting-channel-create.ts +++ b/src/commands/hosting-channel-create.ts @@ -10,7 +10,8 @@ import { requirePermissions } from "../requirePermissions"; import { needProjectId } from "../projectUtils"; import { logger } from "../logger"; import * as requireConfig from "../requireConfig"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import { requireHostingSite } from "../requireHostingSite"; const LOG_TAG = "hosting:channel"; diff --git a/src/commands/hosting-channel-delete.ts b/src/commands/hosting-channel-delete.ts index 27ef98fc0d8..d4307c35dd5 100644 --- a/src/commands/hosting-channel-delete.ts +++ b/src/commands/hosting-channel-delete.ts @@ -1,5 +1,6 @@ import { bold, underline } from "cli-color"; -import marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import { Command } from "../command"; import { consoleUrl, logLabeledSuccess, logLabeledWarning } from "../utils"; diff --git a/src/commands/hosting-channel-deploy.ts b/src/commands/hosting-channel-deploy.ts index 96def9ea970..46a6ce050bb 100644 --- a/src/commands/hosting-channel-deploy.ts +++ b/src/commands/hosting-channel-deploy.ts @@ -19,7 +19,8 @@ import { logger } from "../logger"; import * as requireConfig from "../requireConfig"; import { DEFAULT_DURATION, calculateChannelExpireTTL } from "../hosting/expireUtils"; import { logLabeledSuccess, datetimeString, logLabeledWarning, consoleUrl } from "../utils"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import { requireHostingSite } from "../requireHostingSite"; const LOG_TAG = "hosting:channel"; diff --git a/src/commands/hosting-clone.ts b/src/commands/hosting-clone.ts index 475e9b2cc47..a09a3e7f647 100644 --- a/src/commands/hosting-clone.ts +++ b/src/commands/hosting-clone.ts @@ -13,7 +13,8 @@ import { } from "../hosting/api"; import * as utils from "../utils"; import { requireAuth } from "../requireAuth"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import { logger } from "../logger"; export default new Command("hosting:clone ") diff --git a/src/extensions/askUserForConsent.ts b/src/extensions/askUserForConsent.ts index 4e104cc2cbd..64103e0cb55 100644 --- a/src/extensions/askUserForConsent.ts +++ b/src/extensions/askUserForConsent.ts @@ -1,6 +1,7 @@ import * as _ from "lodash"; import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); import { FirebaseError } from "../error"; diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index 94ba1b7d4c3..030a4f3e955 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -1,6 +1,7 @@ import * as _ from "lodash"; import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import { Param, ParamOption, ParamType } from "./extensionsApi"; import * as secretManagerApi from "../gcp/secretManager"; diff --git a/src/extensions/billingMigrationHelper.ts b/src/extensions/billingMigrationHelper.ts index 4ad6b61fe85..52a425c2d12 100644 --- a/src/extensions/billingMigrationHelper.ts +++ b/src/extensions/billingMigrationHelper.ts @@ -1,4 +1,5 @@ -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); import { FirebaseError } from "../error"; diff --git a/src/extensions/changelog.ts b/src/extensions/changelog.ts index abd5da1117e..ae2d88562b4 100644 --- a/src/extensions/changelog.ts +++ b/src/extensions/changelog.ts @@ -1,5 +1,6 @@ import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import * as path from "path"; import * as semver from "semver"; import TerminalRenderer = require("marked-terminal"); diff --git a/src/extensions/displayExtensionInfo.ts b/src/extensions/displayExtensionInfo.ts index ca7e14d82e7..483dd74de6c 100644 --- a/src/extensions/displayExtensionInfo.ts +++ b/src/extensions/displayExtensionInfo.ts @@ -1,6 +1,7 @@ import * as _ from "lodash"; import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); import * as extensionsApi from "./extensionsApi"; diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index 24794f33837..ddd1ac39c9f 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -1,7 +1,8 @@ import * as yaml from "js-yaml"; import * as _ from "lodash"; import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import * as api from "../api"; import * as apiv2 from "../apiv2"; import * as refs from "./refs"; diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index e8081223f8a..94fa6327709 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -2,7 +2,8 @@ import * as _ from "lodash"; import * as clc from "cli-color"; import * as ora from "ora"; import * as semver from "semver"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); const TerminalRenderer = require("marked-terminal"); marked.setOptions({ diff --git a/src/extensions/provisioningHelper.ts b/src/extensions/provisioningHelper.ts index 6f62ff9250e..b42ea7d66bb 100644 --- a/src/extensions/provisioningHelper.ts +++ b/src/extensions/provisioningHelper.ts @@ -1,4 +1,5 @@ -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import * as extensionsApi from "./extensionsApi"; import * as api from "../api"; diff --git a/src/extensions/resolveSource.ts b/src/extensions/resolveSource.ts index 53e89c5046e..8a1bd81c23f 100644 --- a/src/extensions/resolveSource.ts +++ b/src/extensions/resolveSource.ts @@ -1,6 +1,7 @@ import * as _ from "lodash"; import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import * as semver from "semver"; import * as api from "../api"; import { FirebaseError } from "../error"; diff --git a/src/extensions/updateHelper.ts b/src/extensions/updateHelper.ts index dae983e0922..33e7470b2c3 100644 --- a/src/extensions/updateHelper.ts +++ b/src/extensions/updateHelper.ts @@ -1,6 +1,7 @@ import * as clc from "cli-color"; import * as semver from "semver"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import { FirebaseError } from "../error"; import { logger } from "../logger"; diff --git a/src/extensions/warnings.ts b/src/extensions/warnings.ts index eae3d15dad4..f06f77a3010 100644 --- a/src/extensions/warnings.ts +++ b/src/extensions/warnings.ts @@ -1,4 +1,5 @@ -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); import * as clc from "cli-color"; import { ExtensionVersion, RegistryLaunchStage } from "./extensionsApi"; diff --git a/src/projectUtils.ts b/src/projectUtils.ts index f6973164e9f..8601b8a9e41 100644 --- a/src/projectUtils.ts +++ b/src/projectUtils.ts @@ -2,7 +2,8 @@ import { getFirebaseProject } from "./management/projects"; import { RC } from "./rc"; import * as clc from "cli-color"; -import * as marked from "marked"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires +const { marked } = require("marked"); const { FirebaseError } = require("./error"); diff --git a/src/test/extensions/billingMigrationHelper.spec.ts b/src/test/extensions/billingMigrationHelper.spec.ts index 7bd83dc951c..873547a6454 100644 --- a/src/test/extensions/billingMigrationHelper.spec.ts +++ b/src/test/extensions/billingMigrationHelper.spec.ts @@ -82,39 +82,39 @@ describe("billingMigrationHelper", () => { promptStub.restore(); }); - describe("displayCreateBillingNotice", () => { - it("should notify the user if the runtime requires nodejs10", () => { + describe("displayNode10CreateBillingNotice", () => { + it("should notify the user if the runtime requires nodejs10", async () => { promptStub.resolves(true); const newSpec = _.cloneDeep(NODE10_SPEC); - expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be + await expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be .rejected; expect(promptStub.callCount).to.equal(1); }); - it("should notify the user if the runtime does not require nodejs (explicit)", () => { + it("should notify the user if the runtime does not require nodejs (explicit)", async () => { promptStub.resolves(true); const newSpec = _.cloneDeep(NODE8_SPEC); - expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be + await expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be .rejected; expect(promptStub.callCount).to.equal(0); }); - it("should notify the user if the runtime does not require nodejs (implicit)", () => { + it("should notify the user if the runtime does not require nodejs (implicit)", async () => { promptStub.resolves(true); const newSpec = _.cloneDeep(NO_RUNTIME_SPEC); - expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be + await expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be .rejected; expect(promptStub.callCount).to.equal(0); }); - it("should error if the user doesn't give consent", () => { + it("should error if the user doesn't give consent", async () => { promptStub.resolves(false); const newSpec = _.cloneDeep(NODE10_SPEC); - expect( + await expect( nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true) ).to.be.rejectedWith(FirebaseError, "Cancelled"); }); diff --git a/src/types/marked-terminal/index.d.ts b/src/types/marked-terminal/index.d.ts deleted file mode 100644 index bf4fea68ae8..00000000000 --- a/src/types/marked-terminal/index.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare module "marked-terminal" { - import * as marked from "marked"; - - class TerminalRenderer extends marked.Renderer { - constructor(options?: marked.MarkedOptions); - } - - export = TerminalRenderer; -} From ebe7e4a221f3706a2767dd9ff8cc9b24998becba Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 19 Jan 2022 14:26:26 -0800 Subject: [PATCH 0033/1699] Rewrite a bunch of commands to Typescript (#4025) * rewriting a handful of commands to typescript * quick fixes * one more FirebaseError --- src/commands/auth-export.js | 52 -------------------- src/commands/auth-export.ts | 55 +++++++++++++++++++++ src/commands/ext-dev-publish.ts | 1 - src/commands/functions-config-clone.js | 60 ----------------------- src/commands/functions-config-clone.ts | 59 ++++++++++++++++++++++ src/commands/functions-config-export.ts | 1 - src/commands/functions-config-get.js | 37 -------------- src/commands/functions-config-get.ts | 35 +++++++++++++ src/commands/functions-config-legacy.js | 48 ------------------ src/commands/functions-config-set.js | 51 ------------------- src/commands/functions-config-set.ts | 52 ++++++++++++++++++++ src/commands/functions-config-unset.js | 50 ------------------- src/commands/functions-config-unset.ts | 47 ++++++++++++++++++ src/commands/{help.js => help.ts} | 19 +++---- src/commands/{login-ci.js => login-ci.ts} | 18 +++---- src/commands/setup-emulators-database.js | 13 ----- src/commands/setup-emulators-database.ts | 11 +++++ src/commands/setup-emulators-firestore.js | 13 ----- src/commands/setup-emulators-firestore.ts | 11 +++++ src/commands/setup-emulators-pubsub.ts | 7 +-- src/commands/setup-emulators-storage.ts | 8 +-- src/commands/setup-emulators-ui.js | 13 ----- src/commands/setup-emulators-ui.ts | 11 +++++ src/commands/target-apply.js | 35 ------------- src/commands/target-apply.ts | 31 ++++++++++++ src/commands/target-clear.js | 20 -------- src/commands/target-clear.ts | 18 +++++++ src/commands/target-remove.js | 24 --------- src/commands/target-remove.ts | 20 ++++++++ src/commands/target.js | 39 --------------- src/commands/target.ts | 40 +++++++++++++++ 31 files changed, 414 insertions(+), 485 deletions(-) delete mode 100644 src/commands/auth-export.js create mode 100644 src/commands/auth-export.ts delete mode 100644 src/commands/functions-config-clone.js create mode 100644 src/commands/functions-config-clone.ts delete mode 100644 src/commands/functions-config-get.js create mode 100644 src/commands/functions-config-get.ts delete mode 100644 src/commands/functions-config-legacy.js delete mode 100644 src/commands/functions-config-set.js create mode 100644 src/commands/functions-config-set.ts delete mode 100644 src/commands/functions-config-unset.js create mode 100644 src/commands/functions-config-unset.ts rename src/commands/{help.js => help.ts} (62%) rename src/commands/{login-ci.js => login-ci.ts} (64%) delete mode 100644 src/commands/setup-emulators-database.js create mode 100644 src/commands/setup-emulators-database.ts delete mode 100644 src/commands/setup-emulators-firestore.js create mode 100644 src/commands/setup-emulators-firestore.ts delete mode 100644 src/commands/setup-emulators-ui.js create mode 100644 src/commands/setup-emulators-ui.ts delete mode 100644 src/commands/target-apply.js create mode 100644 src/commands/target-apply.ts delete mode 100644 src/commands/target-clear.js create mode 100644 src/commands/target-clear.ts delete mode 100644 src/commands/target-remove.js create mode 100644 src/commands/target-remove.ts delete mode 100644 src/commands/target.js create mode 100644 src/commands/target.ts diff --git a/src/commands/auth-export.js b/src/commands/auth-export.js deleted file mode 100644 index 4f6c6aa5168..00000000000 --- a/src/commands/auth-export.js +++ /dev/null @@ -1,52 +0,0 @@ -"use strict"; - -var clc = require("cli-color"); -var fs = require("fs"); -var os = require("os"); - -var { Command } = require("../command"); -var accountExporter = require("../accountExporter"); -var needProjectId = require("../projectUtils").needProjectId; -const { logger } = require("../logger"); -var { requirePermissions } = require("../requirePermissions"); - -var MAX_BATCH_SIZE = 1000; - -var validateOptions = accountExporter.validateOptions; -var serialExportUsers = accountExporter.serialExportUsers; - -module.exports = new Command("auth:export [dataFile]") - .description("Export accounts from your Firebase project into a data file") - .option( - "--format ", - "Format of exported data (csv, json). Ignored if [dataFile] has format extension." - ) - .before(requirePermissions, ["firebaseauth.users.get"]) - .action(function (dataFile, options) { - var projectId = needProjectId(options); - var checkRes = validateOptions(options, dataFile); - if (!checkRes.format) { - return checkRes; - } - var exportOptions = checkRes; - var writeStream = fs.createWriteStream(dataFile); - if (exportOptions.format === "json") { - writeStream.write('{"users": [' + os.EOL); - } - exportOptions.writeStream = writeStream; - exportOptions.batchSize = MAX_BATCH_SIZE; - logger.info("Exporting accounts to " + clc.bold(dataFile)); - return serialExportUsers(projectId, exportOptions).then(function () { - if (exportOptions.format === "json") { - writeStream.write("]}"); - } - writeStream.end(); - // Ensure process ends only when all data have been flushed - // to the output file - return new Promise(function (resolve, reject) { - writeStream.on("finish", resolve); - writeStream.on("close", resolve); - writeStream.on("error", reject); - }); - }); - }); diff --git a/src/commands/auth-export.ts b/src/commands/auth-export.ts new file mode 100644 index 00000000000..4bb691eabc6 --- /dev/null +++ b/src/commands/auth-export.ts @@ -0,0 +1,55 @@ +import * as clc from "cli-color"; +import * as fs from "fs"; +import * as os from "os"; + +import { Command } from "../command"; +import { logger } from "../logger"; +import { needProjectId } from "../projectUtils"; +import { requirePermissions } from "../requirePermissions"; +import { validateOptions, serialExportUsers } from "../accountExporter"; + +const MAX_BATCH_SIZE = 1000; + +interface exportOptions { + format: string; + writeStream: fs.WriteStream; + batchSize: number; +} + +export default new Command("auth:export [dataFile]") + .description("Export accounts from your Firebase project into a data file") + .option( + "--format ", + "Format of exported data (csv, json). Ignored if has format extension." + ) + .before(requirePermissions, ["firebaseauth.users.get"]) + .action((dataFile, options) => { + const projectId = needProjectId(options); + const checkRes = validateOptions(options, dataFile); + if (!checkRes.format) { + return checkRes; + } + const writeStream = fs.createWriteStream(dataFile); + if (checkRes.format === "json") { + writeStream.write('{"users": [' + os.EOL); + } + const exportOptions: exportOptions = { + format: checkRes.format, + writeStream, + batchSize: MAX_BATCH_SIZE, + }; + logger.info("Exporting accounts to " + clc.bold(dataFile)); + return serialExportUsers(projectId, exportOptions).then(() => { + if (exportOptions.format === "json") { + writeStream.write("]}"); + } + writeStream.end(); + // Ensure process ends only when all data have been flushed + // to the output file + return new Promise((resolve, reject) => { + writeStream.on("finish", resolve); + writeStream.on("close", resolve); + writeStream.on("error", reject); + }); + }); + }); diff --git a/src/commands/ext-dev-publish.ts b/src/commands/ext-dev-publish.ts index 97ba5eb6d24..3c0f9d1c40b 100644 --- a/src/commands/ext-dev-publish.ts +++ b/src/commands/ext-dev-publish.ts @@ -11,7 +11,6 @@ import { consoleInstallLink } from "../extensions/publishHelpers"; import { requireAuth } from "../requireAuth"; import { FirebaseError } from "../error"; import * as utils from "../utils"; -import { options } from "./auth-export"; marked.setOptions({ renderer: new TerminalRenderer(), diff --git a/src/commands/functions-config-clone.js b/src/commands/functions-config-clone.js deleted file mode 100644 index 6d9cc26ebf5..00000000000 --- a/src/commands/functions-config-clone.js +++ /dev/null @@ -1,60 +0,0 @@ -"use strict"; - -var clc = require("cli-color"); -var { Command } = require("../command"); -var functionsConfig = require("../functionsConfig"); -var functionsConfigClone = require("../functionsConfigClone"); -var needProjectId = require("../projectUtils").needProjectId; -var { requirePermissions } = require("../requirePermissions"); -var utils = require("../utils"); -const { logger } = require("../logger"); - -module.exports = new Command("functions:config:clone") - .description("clone environment config from another project") - .option("--from ", "the project from which to clone configuration") - .option("--only ", "a comma-separated list of keys to clone") - .option("--except ", "a comma-separated list of keys to not clone") - .before(requirePermissions, [ - "runtimeconfig.configs.list", - "runtimeconfig.configs.create", - "runtimeconfig.configs.get", - "runtimeconfig.configs.update", - "runtimeconfig.configs.delete", - "runtimeconfig.variables.list", - "runtimeconfig.variables.create", - "runtimeconfig.variables.get", - "runtimeconfig.variables.update", - "runtimeconfig.variables.delete", - ]) - .before(functionsConfig.ensureApi) - .action(function (options) { - var projectId = needProjectId(options); - if (!options.from) { - return utils.reject( - "Must specify a source project in " + clc.bold("--from ") + " option." - ); - } else if (options.from === projectId) { - return utils.reject("From project and destination can't be the same project."); - } else if (options.only && options.except) { - return utils.reject("Cannot use both --only and --except at the same time."); - } - - var only; - var except; - if (options.only) { - only = options.only.split(","); - } else if (options.except) { - except = options.except.split(","); - } - - return functionsConfigClone(options.from, projectId, only, except).then(function () { - utils.logSuccess( - "Cloned functions config from " + clc.bold(options.from) + " into " + clc.bold(projectId) - ); - logger.info( - "\nPlease deploy your functions for the change to take effect by running " + - clc.bold("firebase deploy --only functions") + - "\n" - ); - }); - }); diff --git a/src/commands/functions-config-clone.ts b/src/commands/functions-config-clone.ts new file mode 100644 index 00000000000..1a4d41af4fa --- /dev/null +++ b/src/commands/functions-config-clone.ts @@ -0,0 +1,59 @@ +import * as clc from "cli-color"; + +import { Command } from "../command"; +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import { needProjectId } from "../projectUtils"; +import { requirePermissions } from "../requirePermissions"; +import * as functionsConfig from "../functionsConfig"; +import * as functionsConfigClone from "../functionsConfigClone"; +import * as utils from "../utils"; + +export default new Command("functions:config:clone") + .description("clone environment config from another project") + .option("--from ", "the project from which to clone configuration") + .option("--only ", "a comma-separated list of keys to clone") + .option("--except ", "a comma-separated list of keys to not clone") + .before(requirePermissions, [ + "runtimeconfig.configs.list", + "runtimeconfig.configs.create", + "runtimeconfig.configs.get", + "runtimeconfig.configs.update", + "runtimeconfig.configs.delete", + "runtimeconfig.variables.list", + "runtimeconfig.variables.create", + "runtimeconfig.variables.get", + "runtimeconfig.variables.update", + "runtimeconfig.variables.delete", + ]) + .before(functionsConfig.ensureApi) + .action(async (options) => { + const projectId = needProjectId(options); + if (!options.from) { + throw new FirebaseError( + `Must specify a source project in ${clc.bold("--from ")} option.` + ); + } else if (options.from === projectId) { + throw new FirebaseError("From project and destination can't be the same project."); + } else if (options.only && options.except) { + throw new FirebaseError("Cannot use both --only and --except at the same time."); + } + + let only: string[] = []; + let except: string[] = []; + if (options.only) { + only = options.only.split(","); + } else if (options.except) { + except = options.except.split(","); + } + + await functionsConfigClone(options.from, projectId, only, except); + utils.logSuccess( + `Cloned functions config from ${clc.bold(options.from)} into ${clc.bold(projectId)}` + ); + logger.info( + `\nPlease deploy your functions for the change to take effect by running ${clc.bold( + "firebase deploy --only functions" + )}\n` + ); + }); diff --git a/src/commands/functions-config-export.ts b/src/commands/functions-config-export.ts index ef57eb9df5e..c6d45cbe2f1 100644 --- a/src/commands/functions-config-export.ts +++ b/src/commands/functions-config-export.ts @@ -7,7 +7,6 @@ import { Command } from "../command"; import { FirebaseError } from "../error"; import { testIamPermissions } from "../gcp/iam"; import { logger } from "../logger"; -import { resolveProjectPath } from "../projectPath"; import { promptOnce } from "../prompt"; import { requirePermissions } from "../requirePermissions"; import { logBullet, logWarning } from "../utils"; diff --git a/src/commands/functions-config-get.js b/src/commands/functions-config-get.js deleted file mode 100644 index ad51126b6bc..00000000000 --- a/src/commands/functions-config-get.js +++ /dev/null @@ -1,37 +0,0 @@ -"use strict"; - -var _ = require("lodash"); -var { Command } = require("../command"); -var needProjectId = require("../projectUtils").needProjectId; -const { logger } = require("../logger"); -var { requirePermissions } = require("../requirePermissions"); -var functionsConfig = require("../functionsConfig"); - -function _materialize(projectId, path) { - if (_.isUndefined(path)) { - return functionsConfig.materializeAll(projectId); - } - var parts = path.split("."); - var configId = parts[0]; - var configName = _.join(["projects", projectId, "configs", configId], "/"); - return functionsConfig.materializeConfig(configName, {}).then(function (result) { - var query = _.chain(parts).join(".").value(); - return query ? _.get(result, query) : result; - }); -} - -module.exports = new Command("functions:config:get [path]") - .description("fetch environment config stored at the given path") - .before(requirePermissions, [ - "runtimeconfig.configs.list", - "runtimeconfig.configs.get", - "runtimeconfig.variables.list", - "runtimeconfig.variables.get", - ]) - .before(functionsConfig.ensureApi) - .action(function (path, options) { - return _materialize(needProjectId(options), path).then(function (result) { - logger.info(JSON.stringify(result, null, 2)); - return result; - }); - }); diff --git a/src/commands/functions-config-get.ts b/src/commands/functions-config-get.ts new file mode 100644 index 00000000000..ae322e9d6d5 --- /dev/null +++ b/src/commands/functions-config-get.ts @@ -0,0 +1,35 @@ +import { get } from "lodash"; +import { join } from "path"; + +import { Command } from "../command"; +import { logger } from "../logger"; +import { needProjectId } from "../projectUtils"; +import { requirePermissions } from "../requirePermissions"; +import * as functionsConfig from "../functionsConfig"; + +async function materialize(projectId: string, path?: string): Promise { + if (path === undefined) { + return functionsConfig.materializeAll(projectId); + } + const parts = path.split("."); + const configId = parts[0]; + const configName = join("projects", projectId, "configs", configId); + const result = await functionsConfig.materializeConfig(configName, {}); + const query = parts.join("."); + return query ? get(result, query) : result; +} + +export default new Command("functions:config:get [path]") + .description("fetch environment config stored at the given path") + .before(requirePermissions, [ + "runtimeconfig.configs.list", + "runtimeconfig.configs.get", + "runtimeconfig.variables.list", + "runtimeconfig.variables.get", + ]) + .before(functionsConfig.ensureApi) + .action(async (path, options) => { + const result = await materialize(needProjectId(options), path); + logger.info(JSON.stringify(result, null, 2)); + return result; + }); diff --git a/src/commands/functions-config-legacy.js b/src/commands/functions-config-legacy.js deleted file mode 100644 index a8be67582ca..00000000000 --- a/src/commands/functions-config-legacy.js +++ /dev/null @@ -1,48 +0,0 @@ -"use strict"; - -var _ = require("lodash"); - -var { Command } = require("../command"); -var needProjectId = require("../projectUtils").needProjectId; -var { requirePermissions } = require("../requirePermissions"); -var runtimeconfig = require("../gcp/runtimeconfig"); -var functionsConfig = require("../functionsConfig"); -const { logger } = require("../logger"); - -module.exports = new Command("functions:config:legacy") - .description("get legacy functions config variables") - .before(requirePermissions, [ - "runtimeconfig.configs.list", - "runtimeconfig.configs.get", - "runtimeconfig.variables.list", - "runtimeconfig.variables.get", - ]) - .action(function (options) { - var projectId = needProjectId(options); - var metaPath = "projects/" + projectId + "/configs/firebase/variables/meta"; - return runtimeconfig.variables - .get(metaPath) - .then(function (result) { - var metaVal = JSON.parse(result.text); - if (!_.has(metaVal, "version")) { - logger.info("You do not have any legacy config variables."); - return null; - } - var latestVarPath = functionsConfig.idsToVarName(projectId, "firebase", metaVal.version); - return runtimeconfig.variables.get(latestVarPath); - }) - .then(function (latest) { - if (latest !== null) { - var latestVal = JSON.parse(latest.text); - logger.info(JSON.stringify(latestVal, null, 2)); - return latestVal; - } - }) - .catch(function (err) { - if (_.get(err, "context.response.statusCode") === 404) { - logger.info("You do not have any legacy config variables."); - return null; - } - return Promise.reject(err); - }); - }); diff --git a/src/commands/functions-config-set.js b/src/commands/functions-config-set.js deleted file mode 100644 index 3d8b7aa34e3..00000000000 --- a/src/commands/functions-config-set.js +++ /dev/null @@ -1,51 +0,0 @@ -"use strict"; - -var clc = require("cli-color"); - -var { Command } = require("../command"); -var needProjectId = require("../projectUtils").needProjectId; -var { requirePermissions } = require("../requirePermissions"); -const { logger } = require("../logger"); -var utils = require("../utils"); -var functionsConfig = require("../functionsConfig"); - -module.exports = new Command("functions:config:set [values...]") - .description("set environment config with key=value syntax") - .before(requirePermissions, [ - "runtimeconfig.configs.list", - "runtimeconfig.configs.create", - "runtimeconfig.configs.get", - "runtimeconfig.configs.update", - "runtimeconfig.configs.delete", - "runtimeconfig.variables.list", - "runtimeconfig.variables.create", - "runtimeconfig.variables.get", - "runtimeconfig.variables.update", - "runtimeconfig.variables.delete", - ]) - .before(functionsConfig.ensureApi) - .action(function (args, options) { - if (!args.length) { - return utils.reject( - "Must supply at least one key/value pair, e.g. " + clc.bold('app.name="My App"') - ); - } - var projectId = needProjectId(options); - var parsed = functionsConfig.parseSetArgs(args); - var promises = []; - - parsed.forEach(function (item) { - promises.push( - functionsConfig.setVariablesRecursive(projectId, item.configId, item.varId, item.val) - ); - }); - - return Promise.all(promises).then(function () { - utils.logSuccess("Functions config updated."); - logger.info( - "\nPlease deploy your functions for the change to take effect by running " + - clc.bold("firebase deploy --only functions") + - "\n" - ); - }); - }); diff --git a/src/commands/functions-config-set.ts b/src/commands/functions-config-set.ts new file mode 100644 index 00000000000..be27dcc960d --- /dev/null +++ b/src/commands/functions-config-set.ts @@ -0,0 +1,52 @@ +import * as clc from "cli-color"; + +import { Command } from "../command"; +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import { needProjectId } from "../projectUtils"; +import { requirePermissions } from "../requirePermissions"; +import * as functionsConfig from "../functionsConfig"; +import * as utils from "../utils"; + +export default new Command("functions:config:set [values...]") + .description("set environment config with key=value syntax") + .before(requirePermissions, [ + "runtimeconfig.configs.list", + "runtimeconfig.configs.create", + "runtimeconfig.configs.get", + "runtimeconfig.configs.update", + "runtimeconfig.configs.delete", + "runtimeconfig.variables.list", + "runtimeconfig.variables.create", + "runtimeconfig.variables.get", + "runtimeconfig.variables.update", + "runtimeconfig.variables.delete", + ]) + .before(functionsConfig.ensureApi) + .action(async (args, options) => { + if (!args.length) { + throw new FirebaseError( + `Must supply at least one key/value pair, e.g. ${clc.bold('app.name="My App"')}` + ); + } + const projectId = needProjectId(options); + const parsed = functionsConfig.parseSetArgs(args); + const promises: Promise[] = []; + + for (const item of parsed) { + if (item.val === undefined) { + throw new FirebaseError(`Unexpected undefined value for varId "${item.varId}`, { exit: 2 }); + } + promises.push( + functionsConfig.setVariablesRecursive(projectId, item.configId, item.varId, item.val) + ); + } + + await Promise.all(promises); + utils.logSuccess("Functions config updated."); + logger.info( + `\nPlease deploy your functions for the change to take effect by running ${clc.bold( + "firebase deploy --only functions" + )}\n` + ); + }); diff --git a/src/commands/functions-config-unset.js b/src/commands/functions-config-unset.js deleted file mode 100644 index adb262e4d41..00000000000 --- a/src/commands/functions-config-unset.js +++ /dev/null @@ -1,50 +0,0 @@ -"use strict"; - -var _ = require("lodash"); - -var clc = require("cli-color"); -var { Command } = require("../command"); -var functionsConfig = require("../functionsConfig"); -var needProjectId = require("../projectUtils").needProjectId; -const { logger } = require("../logger"); -var { requirePermissions } = require("../requirePermissions"); -var utils = require("../utils"); -var runtimeconfig = require("../gcp/runtimeconfig"); - -module.exports = new Command("functions:config:unset [keys...]") - .description("unset environment config at the specified path(s)") - .before(requirePermissions, [ - "runtimeconfig.configs.list", - "runtimeconfig.configs.create", - "runtimeconfig.configs.get", - "runtimeconfig.configs.update", - "runtimeconfig.configs.delete", - "runtimeconfig.variables.list", - "runtimeconfig.variables.create", - "runtimeconfig.variables.get", - "runtimeconfig.variables.update", - "runtimeconfig.variables.delete", - ]) - .before(functionsConfig.ensureApi) - .action(function (args, options) { - if (!args.length) { - return utils.reject("Must supply at least one key"); - } - var projectId = needProjectId(options); - var parsed = functionsConfig.parseUnsetArgs(args); - return Promise.all( - _.map(parsed, function (item) { - if (item.varId === "") { - return runtimeconfig.configs.delete(projectId, item.configId); - } - return runtimeconfig.variables.delete(projectId, item.configId, item.varId); - }) - ).then(function () { - utils.logSuccess("Environment updated."); - logger.info( - "\nPlease deploy your functions for the change to take effect by running " + - clc.bold("firebase deploy --only functions") + - "\n" - ); - }); - }); diff --git a/src/commands/functions-config-unset.ts b/src/commands/functions-config-unset.ts new file mode 100644 index 00000000000..347e86a77ef --- /dev/null +++ b/src/commands/functions-config-unset.ts @@ -0,0 +1,47 @@ +import * as clc from "cli-color"; + +import { Command } from "../command"; +import { logger } from "../logger"; +import { requirePermissions } from "../requirePermissions"; +import { needProjectId } from "../projectUtils"; +import * as functionsConfig from "../functionsConfig"; +import * as runtimeconfig from "../gcp/runtimeconfig"; +import * as utils from "../utils"; +import { FirebaseError } from "../error"; + +export default new Command("functions:config:unset [keys...]") + .description("unset environment config at the specified path(s)") + .before(requirePermissions, [ + "runtimeconfig.configs.list", + "runtimeconfig.configs.create", + "runtimeconfig.configs.get", + "runtimeconfig.configs.update", + "runtimeconfig.configs.delete", + "runtimeconfig.variables.list", + "runtimeconfig.variables.create", + "runtimeconfig.variables.get", + "runtimeconfig.variables.update", + "runtimeconfig.variables.delete", + ]) + .before(functionsConfig.ensureApi) + .action(async (args, options) => { + if (!args.length) { + throw new FirebaseError("Must supply at least one key"); + } + const projectId = needProjectId(options); + const parsed = functionsConfig.parseUnsetArgs(args); + await Promise.all( + parsed.map((item) => { + if (item.varId === "") { + return runtimeconfig.configs.delete(projectId, item.configId); + } + return runtimeconfig.variables.delete(projectId, item.configId, item.varId); + }) + ); + utils.logSuccess("Environment updated."); + logger.info( + `\nPlease deploy your functions for the change to take effect by running ${clc.bold( + "firebase deploy --only functions" + )}\n` + ); + }); diff --git a/src/commands/help.js b/src/commands/help.ts similarity index 62% rename from src/commands/help.js rename to src/commands/help.ts index 99d40201bcc..79f64047b70 100644 --- a/src/commands/help.js +++ b/src/commands/help.ts @@ -1,18 +1,17 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -"use strict"; +import * as clc from "cli-color"; -var { Command } = require("../command"); +import { Command } from "../command"; +import { logger } from "../logger"; +import * as utils from "../utils"; -var clc = require("cli-color"); -const { logger } = require("../logger"); -var utils = require("../utils"); - -module.exports = new Command("help [command]") +export default new Command("help [command]") .description("display help information") + // This must stay `function (commandName)`. .action(function (commandName) { // @ts-ignore - var client = this.client; // eslint-disable-line no-invalid-this - var cmd = client.getCommand(commandName); + const client = this.client; // eslint-disable-line @typescript-eslint/no-invalid-this + const cmd = client.getCommand(commandName); if (cmd) { cmd.outputHelp(); } else if (commandName) { @@ -30,6 +29,4 @@ module.exports = new Command("help [command]") ); logger.info(); } - - return Promise.resolve(); }); diff --git a/src/commands/login-ci.js b/src/commands/login-ci.ts similarity index 64% rename from src/commands/login-ci.js rename to src/commands/login-ci.ts index 37f08f5c243..473a6898077 100644 --- a/src/commands/login-ci.js +++ b/src/commands/login-ci.ts @@ -1,12 +1,12 @@ -"use strict"; +import * as clc from "cli-color"; -var { Command } = require("../command"); -var clc = require("cli-color"); -var utils = require("../utils"); -const { logger } = require("../logger"); -var auth = require("../auth"); +import { Command } from "../command"; +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import * as auth from "../auth"; +import * as utils from "../utils"; -module.exports = new Command("login:ci") +export default new Command("login:ci") .description("generate an access token for use in non-interactive environments") .option( "--no-localhost", @@ -14,9 +14,7 @@ module.exports = new Command("login:ci") ) .action(async (options) => { if (options.nonInteractive) { - return utils.reject("Cannot run login:ci in non-interactive mode.", { - exit: 1, - }); + throw new FirebaseError("Cannot run login:ci in non-interactive mode."); } const userCredentials = await auth.loginGoogle(options.localhost); diff --git a/src/commands/setup-emulators-database.js b/src/commands/setup-emulators-database.js deleted file mode 100644 index 41914618b10..00000000000 --- a/src/commands/setup-emulators-database.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; - -const { Command } = require("../command"); -const { Emulators } = require("../emulator/types"); -const { downloadEmulator } = require("../emulator/download"); - -const NAME = Emulators.DATABASE; - -module.exports = new Command(`setup:emulators:${NAME}`) - .description(`downloads the ${NAME} emulator`) - .action((options) => { - return downloadEmulator(NAME); - }); diff --git a/src/commands/setup-emulators-database.ts b/src/commands/setup-emulators-database.ts new file mode 100644 index 00000000000..3567342f100 --- /dev/null +++ b/src/commands/setup-emulators-database.ts @@ -0,0 +1,11 @@ +import { Command } from "../command"; +import { downloadEmulator } from "../emulator/download"; +import { Emulators } from "../emulator/types"; + +const NAME = Emulators.DATABASE; + +export default new Command(`setup:emulators:${NAME}`) + .description(`downloads the ${NAME} emulator`) + .action(() => { + return downloadEmulator(NAME); + }); diff --git a/src/commands/setup-emulators-firestore.js b/src/commands/setup-emulators-firestore.js deleted file mode 100644 index 7220676194d..00000000000 --- a/src/commands/setup-emulators-firestore.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; - -const { Command } = require("../command"); -const { Emulators } = require("../emulator/types"); -const { downloadEmulator } = require("../emulator/download"); - -const NAME = Emulators.FIRESTORE; - -module.exports = new Command(`setup:emulators:${NAME}`) - .description(`downloads the ${NAME} emulator`) - .action((options) => { - return downloadEmulator(NAME); - }); diff --git a/src/commands/setup-emulators-firestore.ts b/src/commands/setup-emulators-firestore.ts new file mode 100644 index 00000000000..23908dcc1c3 --- /dev/null +++ b/src/commands/setup-emulators-firestore.ts @@ -0,0 +1,11 @@ +import { Command } from "../command"; +import { downloadEmulator } from "../emulator/download"; +import { Emulators } from "../emulator/types"; + +const NAME = Emulators.FIRESTORE; + +export default new Command(`setup:emulators:${NAME}`) + .description(`downloads the ${NAME} emulator`) + .action(() => { + return downloadEmulator(NAME); + }); diff --git a/src/commands/setup-emulators-pubsub.ts b/src/commands/setup-emulators-pubsub.ts index e82e9306c1c..02ca4a1ef75 100644 --- a/src/commands/setup-emulators-pubsub.ts +++ b/src/commands/setup-emulators-pubsub.ts @@ -1,9 +1,10 @@ import { Command } from "../command"; -const { downloadEmulator } = require("../emulator/download"); +import { downloadEmulator } from "../emulator/download"; +import { Emulators } from "../emulator/types"; -const EMULATOR_NAME = "pubsub"; +const EMULATOR_NAME = Emulators.PUBSUB; -module.exports = new Command(`setup:emulators:${EMULATOR_NAME}`) +export default new Command(`setup:emulators:${EMULATOR_NAME}`) .description(`downloads the ${EMULATOR_NAME} emulator`) .action(() => { return downloadEmulator(EMULATOR_NAME); diff --git a/src/commands/setup-emulators-storage.ts b/src/commands/setup-emulators-storage.ts index 622307686b5..460b09fa255 100644 --- a/src/commands/setup-emulators-storage.ts +++ b/src/commands/setup-emulators-storage.ts @@ -1,11 +1,11 @@ import { Command } from "../command"; import { downloadEmulator } from "../emulator/download"; -import { DownloadableEmulators } from "../emulator/types"; +import { Emulators } from "../emulator/types"; -const EMULATOR_NAME = "storage"; +const EMULATOR_NAME = Emulators.STORAGE; -module.exports = new Command(`setup:emulators:${EMULATOR_NAME}`) +export default new Command(`setup:emulators:${EMULATOR_NAME}`) .description(`downloads the ${EMULATOR_NAME} emulator`) .action(() => { - return downloadEmulator(EMULATOR_NAME as DownloadableEmulators); + return downloadEmulator(EMULATOR_NAME); }); diff --git a/src/commands/setup-emulators-ui.js b/src/commands/setup-emulators-ui.js deleted file mode 100644 index 2e7267a059f..00000000000 --- a/src/commands/setup-emulators-ui.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; - -const { Command } = require("../command"); -const { Emulators } = require("../emulator/types"); -const { downloadEmulator } = require("../emulator/download"); - -const NAME = Emulators.UI; - -module.exports = new Command(`setup:emulators:${NAME}`) - .description(`downloads the ${NAME} emulator`) - .action((options) => { - return downloadEmulator(NAME); - }); diff --git a/src/commands/setup-emulators-ui.ts b/src/commands/setup-emulators-ui.ts new file mode 100644 index 00000000000..940c8bb34cd --- /dev/null +++ b/src/commands/setup-emulators-ui.ts @@ -0,0 +1,11 @@ +import { Command } from "../command"; +import { downloadEmulator } from "../emulator/download"; +import { Emulators } from "../emulator/types"; + +const NAME = Emulators.UI; + +export default new Command(`setup:emulators:${NAME}`) + .description(`downloads the ${NAME} emulator`) + .action(() => { + return downloadEmulator(NAME); + }); diff --git a/src/commands/target-apply.js b/src/commands/target-apply.js deleted file mode 100644 index 8171c57ce81..00000000000 --- a/src/commands/target-apply.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; - -var _ = require("lodash"); -var clc = require("cli-color"); - -var { Command } = require("../command"); -const { logger } = require("../logger"); -var requireConfig = require("../requireConfig"); -var utils = require("../utils"); - -module.exports = new Command("target:apply ") - .description("apply a deploy target to a resource") - .before(requireConfig) - .action(function (type, name, resources, options) { - if (!options.project) { - return utils.reject( - "Must have an active project to set deploy targets. Try " + clc.bold("firebase use --add") - ); - } - - var changes = options.rc.applyTarget(options.project, type, name, resources); - - utils.logSuccess( - "Applied " + type + " target " + clc.bold(name) + " to " + clc.bold(resources.join(", ")) - ); - _.forEach(changes, function (change) { - utils.logWarning( - "Previous target " + clc.bold(change.target) + " removed from " + clc.bold(change.resource) - ); - }); - logger.info(); - logger.info( - "Updated: " + name + " (" + options.rc.target(options.project, type, name).join(",") + ")" - ); - }); diff --git a/src/commands/target-apply.ts b/src/commands/target-apply.ts new file mode 100644 index 00000000000..77487c8a422 --- /dev/null +++ b/src/commands/target-apply.ts @@ -0,0 +1,31 @@ +import * as clc from "cli-color"; + +import { Command } from "../command"; +import { logger } from "../logger"; +import * as requireConfig from "../requireConfig"; +import * as utils from "../utils"; +import { FirebaseError } from "../error"; + +export default new Command("target:apply ") + .description("apply a deploy target to a resource") + .before(requireConfig) + .action((type, name, resources, options) => { + if (!options.project) { + throw new FirebaseError( + `Must have an active project to set deploy targets. Try ${clc.bold("firebase use --add")}` + ); + } + + const changes = options.rc.applyTarget(options.project, type, name, resources); + + utils.logSuccess( + `Applied ${type} target ${clc.bold(name)} to ${clc.bold(resources.join(", "))}` + ); + for (const change of changes) { + utils.logWarning( + `Previous target ${clc.bold(change.target)} removed from ${clc.bold(change.resource)}` + ); + } + logger.info(); + logger.info(`Updated: ${name} (${options.rc.target(options.project, type, name).join(",")})`); + }); diff --git a/src/commands/target-clear.js b/src/commands/target-clear.js deleted file mode 100644 index 6e924499b1e..00000000000 --- a/src/commands/target-clear.js +++ /dev/null @@ -1,20 +0,0 @@ -"use strict"; - -var clc = require("cli-color"); - -var { Command } = require("../command"); -var requireConfig = require("../requireConfig"); -var utils = require("../utils"); - -module.exports = new Command("target:clear ") - .description("clear all resources from a named resource target") - .before(requireConfig) - .action(function (type, name, options) { - var existed = options.rc.clearTarget(options.project, type, name); - if (existed) { - utils.logSuccess("Cleared " + type + " target " + clc.bold(name)); - } else { - utils.logWarning("No action taken. No " + type + " target found named " + clc.bold(name)); - } - return Promise.resolve(existed); - }); diff --git a/src/commands/target-clear.ts b/src/commands/target-clear.ts new file mode 100644 index 00000000000..f93cb879546 --- /dev/null +++ b/src/commands/target-clear.ts @@ -0,0 +1,18 @@ +import * as clc from "cli-color"; + +import { Command } from "../command"; +import * as requireConfig from "../requireConfig"; +import * as utils from "../utils"; + +export default new Command("target:clear ") + .description("clear all resources from a named resource target") + .before(requireConfig) + .action((type, name, options) => { + const existed = options.rc.clearTarget(options.project, type, name); + if (existed) { + utils.logSuccess(`Cleared ${type} target ${clc.bold(name)}`); + } else { + utils.logWarning(`No action taken. No ${type} target found named ${clc.bold(name)}`); + } + return existed; + }); diff --git a/src/commands/target-remove.js b/src/commands/target-remove.js deleted file mode 100644 index 031d454944a..00000000000 --- a/src/commands/target-remove.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; - -var clc = require("cli-color"); - -var { Command } = require("../command"); -var requireConfig = require("../requireConfig"); -var utils = require("../utils"); - -module.exports = new Command("target:remove ") - .description("remove a resource target") - .before(requireConfig) - .action(function (type, resource, options) { - var name = options.rc.removeTarget(options.project, type, resource); - if (name) { - utils.logSuccess( - "Removed " + type + " target " + clc.bold(name) + " from " + clc.bold(resource) - ); - } else { - utils.logWarning( - "No action taken. No target found for " + type + " resource " + clc.bold(resource) - ); - } - return Promise.resolve(name); - }); diff --git a/src/commands/target-remove.ts b/src/commands/target-remove.ts new file mode 100644 index 00000000000..ee6b1d8a0dd --- /dev/null +++ b/src/commands/target-remove.ts @@ -0,0 +1,20 @@ +import * as clc from "cli-color"; + +import { Command } from "../command"; +import * as requireConfig from "../requireConfig"; +import * as utils from "../utils"; + +export default new Command("target:remove ") + .description("remove a resource target") + .before(requireConfig) + .action((type, resource, options) => { + const name = options.rc.removeTarget(options.project, type, resource); + if (name) { + utils.logSuccess(`Removed ${type} target ${clc.bold(name)} from ${clc.bold(resource)}`); + } else { + utils.logWarning( + `No action taken. No target found for ${type} resource ${clc.bold(resource)}` + ); + } + return Promise.resolve(name); + }); diff --git a/src/commands/target.js b/src/commands/target.js deleted file mode 100644 index c37896307ab..00000000000 --- a/src/commands/target.js +++ /dev/null @@ -1,39 +0,0 @@ -"use strict"; - -var _ = require("lodash"); -var clc = require("cli-color"); - -var { Command } = require("../command"); -const { logger } = require("../logger"); -var requireConfig = require("../requireConfig"); -var utils = require("../utils"); - -function _logTargets(type, targets) { - logger.info(clc.cyan("[ " + type + " ]")); - _.forEach(targets, function (resources, name) { - logger.info(name, "(" + (resources || []).join(",") + ")"); - }); -} - -module.exports = new Command("target [type]") - .description("display configured deploy targets for the current project") - .before(requireConfig) - .action(function (type, options) { - if (!options.project) { - return utils.reject("No active project, cannot list deploy targets."); - } - - logger.info("Resource targets for", clc.bold(options.project) + ":"); - logger.info(); - if (type) { - var targets = options.rc.targets(options.project, type); - _logTargets(type, targets); - return Promise.resolve(targets); - } - - const allTargets = options.rc.allTargets(options.project); - for (const [targetType, targetName] of Object.entries(allTargets)) { - _logTargets(targetType, targetName); - } - return Promise.resolve(allTargets); - }); diff --git a/src/commands/target.ts b/src/commands/target.ts new file mode 100644 index 00000000000..fc129501004 --- /dev/null +++ b/src/commands/target.ts @@ -0,0 +1,40 @@ +import * as clc from "cli-color"; + +import { Command } from "../command"; +import { logger } from "../logger"; +import * as requireConfig from "../requireConfig"; +import * as utils from "../utils"; + +interface targetMap { + [target: string]: string[]; +} + +function logTargets(type: string, targets: targetMap): void { + logger.info(clc.cyan("[ " + type + " ]")); + for (const [name, resources] of Object.entries(targets)) { + logger.info(name, "(" + (resources || []).join(",") + ")"); + } +} + +export default new Command("target [type]") + .description("display configured deploy targets for the current project") + .before(requireConfig) + .action((type, options) => { + if (!options.project) { + return utils.reject("No active project, cannot list deploy targets."); + } + + logger.info("Resource targets for", clc.bold(options.project) + ":"); + logger.info(); + if (type) { + const targets = options.rc.targets(options.project, type); + logTargets(type, targets); + return targets; + } + + const allTargets: { [product: string]: targetMap } = options.rc.allTargets(options.project); + for (const [targetType, targetName] of Object.entries(allTargets)) { + logTargets(targetType, targetName); + } + return allTargets; + }); From b705926cf4a77a58998589581ec95a605d63122b Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 20 Jan 2022 11:54:23 -0600 Subject: [PATCH 0034/1699] account import update (#4027) * replace csv-streamify with csv-parse, which has type defs * refactor to remove JSONstream * fix some weird typing * and now we're back to streaming json - this library is tricky though * a bit of cleanup * a few fixes * add changelog --- CHANGELOG.md | 1 + npm-shrinkwrap.json | 214 +++++++++++++----------------------- package.json | 6 +- src/commands/auth-import.js | 138 ----------------------- src/commands/auth-import.ts | 139 +++++++++++++++++++++++ 5 files changed, 220 insertions(+), 278 deletions(-) delete mode 100644 src/commands/auth-import.js create mode 100644 src/commands/auth-import.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..fde720c8923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Updates the streaming libraries used in `auth:import`. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 499a9a2d119..861e6b5d92f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -23,7 +23,7 @@ "cors": "^2.8.5", "cross-env": "^5.1.3", "cross-spawn": "^7.0.1", - "csv-streamify": "^3.0.4", + "csv-parse": "^5.0.4", "dotenv": "^6.1.0", "exegesis": "^4.1.0", "exegesis-express": "^4.0.0", @@ -35,7 +35,6 @@ "google-auth-library": "^7.11.0", "inquirer": "^8.2.0", "js-yaml": "^3.13.1", - "JSONStream": "^1.2.1", "jsonwebtoken": "^8.5.1", "leven": "^3.1.0", "lodash": "^4.17.21", @@ -53,6 +52,8 @@ "request": "^2.87.0", "rimraf": "^3.0.0", "semver": "^5.7.1", + "stream-chain": "^2.2.4", + "stream-json": "^1.7.3", "superstatic": "^7.1.0", "tar": "^4.3.0", "tcp-port-used": "^1.0.1", @@ -105,6 +106,7 @@ "@types/semver": "^6.0.0", "@types/sinon": "^9.0.10", "@types/sinon-chai": "^3.2.2", + "@types/stream-json": "^1.7.2", "@types/supertest": "^2.0.6", "@types/tar": "^4.0.0", "@types/tcp-port-used": "^1.0.0", @@ -2621,6 +2623,25 @@ "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", "dev": true }, + "node_modules/@types/stream-chain": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stream-chain/-/stream-chain-2.0.1.tgz", + "integrity": "sha512-D+Id9XpcBpampptkegH7WMsEk6fUdf9LlCIX7UhLydILsqDin4L0QT7ryJR0oycwC7OqohIzdfcMHVZ34ezNGg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stream-json": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@types/stream-json/-/stream-json-1.7.2.tgz", + "integrity": "sha512-i4LE2aWVb1R3p/Z6S6Sw9kmmOs4Drhg0SybZUyfM499I1c8p7MUKZHs4Sg9jL5eu4mDmcgfQ6eGIG3+rmfUWYw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/stream-chain": "*" + } + }, "node_modules/@types/superagent": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.3.tgz", @@ -4634,19 +4655,10 @@ "node": ">= 8" } }, - "node_modules/csv-streamify": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/csv-streamify/-/csv-streamify-3.0.4.tgz", - "integrity": "sha1-TLYUxX4/KZzKF7Y/3LStFnd39Ho=", - "dependencies": { - "through2": "2.0.1" - }, - "bin": { - "csv-streamify": "cli.js" - }, - "engines": { - "node": ">=0.12.0" - } + "node_modules/csv-parse": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.0.4.tgz", + "integrity": "sha512-5AIdl8l6n3iYQYxan5djB5eKDa+vBnhfWZtRpJTcrETWfVLYN0WSj3L9RwvgYt+psoO77juUr8TG8qpfGZifVQ==" }, "node_modules/d": { "version": "1.0.1", @@ -8228,14 +8240,6 @@ "node": "*" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "engines": [ - "node >= 0.2.0" - ] - }, "node_modules/jsonpath": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", @@ -8275,21 +8279,6 @@ "node": ">=0.10.0" } }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -11963,6 +11952,11 @@ "node": ">= 0.6" } }, + "node_modules/stream-chain": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.4.tgz", + "integrity": "sha512-9lsl3YM53V5N/I1C2uJtc3Kavyi3kNYN83VkKb/bMWRk7D9imiFyUPYa0PoZbLohSVOX1mYE9YsmwObZUsth6Q==" + }, "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -11973,6 +11967,14 @@ "stubs": "^3.0.0" } }, + "node_modules/stream-json": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.7.3.tgz", + "integrity": "sha512-Y6dXn9KKWSwxOqnvHGcdZy1PK+J+7alBwHCeU3W9oRqm4ilLRA0XSPmd1tWwhg7tv9EIxJTMWh7KF15tYelKJg==", + "dependencies": { + "stream-chain": "^2.2.4" + } + }, "node_modules/stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", @@ -12700,38 +12702,6 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, - "node_modules/through2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz", - "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", - "dependencies": { - "readable-stream": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/through2/node_modules/process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "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" - } - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "node_modules/timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", @@ -13714,14 +13684,6 @@ "node": "*" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", @@ -16088,6 +16050,25 @@ "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", "dev": true }, + "@types/stream-chain": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stream-chain/-/stream-chain-2.0.1.tgz", + "integrity": "sha512-D+Id9XpcBpampptkegH7WMsEk6fUdf9LlCIX7UhLydILsqDin4L0QT7ryJR0oycwC7OqohIzdfcMHVZ34ezNGg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/stream-json": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@types/stream-json/-/stream-json-1.7.2.tgz", + "integrity": "sha512-i4LE2aWVb1R3p/Z6S6Sw9kmmOs4Drhg0SybZUyfM499I1c8p7MUKZHs4Sg9jL5eu4mDmcgfQ6eGIG3+rmfUWYw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/stream-chain": "*" + } + }, "@types/superagent": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.3.tgz", @@ -17632,13 +17613,10 @@ } } }, - "csv-streamify": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/csv-streamify/-/csv-streamify-3.0.4.tgz", - "integrity": "sha1-TLYUxX4/KZzKF7Y/3LStFnd39Ho=", - "requires": { - "through2": "2.0.1" - } + "csv-parse": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.0.4.tgz", + "integrity": "sha512-5AIdl8l6n3iYQYxan5djB5eKDa+vBnhfWZtRpJTcrETWfVLYN0WSj3L9RwvgYt+psoO77juUr8TG8qpfGZifVQ==" }, "d": { "version": "1.0.1", @@ -20476,11 +20454,6 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, "jsonpath": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", @@ -20512,15 +20485,6 @@ "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", "dev": true }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -23434,6 +23398,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stream-chain": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.4.tgz", + "integrity": "sha512-9lsl3YM53V5N/I1C2uJtc3Kavyi3kNYN83VkKb/bMWRk7D9imiFyUPYa0PoZbLohSVOX1mYE9YsmwObZUsth6Q==" + }, "stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -23444,6 +23413,14 @@ "stubs": "^3.0.0" } }, + "stream-json": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.7.3.tgz", + "integrity": "sha512-Y6dXn9KKWSwxOqnvHGcdZy1PK+J+7alBwHCeU3W9oRqm4ilLRA0XSPmd1tWwhg7tv9EIxJTMWh7KF15tYelKJg==", + "requires": { + "stream-chain": "^2.2.4" + } + }, "stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", @@ -24025,40 +24002,6 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, - "through2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz", - "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", - "requires": { - "readable-stream": "~2.0.0", - "xtend": "~4.0.0" - }, - "dependencies": { - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "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" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, "timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", @@ -24795,11 +24738,6 @@ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, "y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", diff --git a/package.json b/package.json index 0c0e229ccfe..b27f2e223b3 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "cors": "^2.8.5", "cross-env": "^5.1.3", "cross-spawn": "^7.0.1", - "csv-streamify": "^3.0.4", + "csv-parse": "^5.0.4", "dotenv": "^6.1.0", "exegesis": "^4.1.0", "exegesis-express": "^4.0.0", @@ -110,7 +110,6 @@ "google-auth-library": "^7.11.0", "inquirer": "^8.2.0", "js-yaml": "^3.13.1", - "JSONStream": "^1.2.1", "jsonwebtoken": "^8.5.1", "leven": "^3.1.0", "lodash": "^4.17.21", @@ -128,6 +127,8 @@ "request": "^2.87.0", "rimraf": "^3.0.0", "semver": "^5.7.1", + "stream-chain": "^2.2.4", + "stream-json": "^1.7.3", "superstatic": "^7.1.0", "tar": "^4.3.0", "tcp-port-used": "^1.0.1", @@ -177,6 +178,7 @@ "@types/semver": "^6.0.0", "@types/sinon": "^9.0.10", "@types/sinon-chai": "^3.2.2", + "@types/stream-json": "^1.7.2", "@types/supertest": "^2.0.6", "@types/tar": "^4.0.0", "@types/tcp-port-used": "^1.0.0", diff --git a/src/commands/auth-import.js b/src/commands/auth-import.js deleted file mode 100644 index 5f959fc61d0..00000000000 --- a/src/commands/auth-import.js +++ /dev/null @@ -1,138 +0,0 @@ -"use strict"; - -var csv = require("csv-streamify"); -var clc = require("cli-color"); -var fs = require("fs"); -var jsonStream = require("JSONStream"); -var _ = require("lodash"); - -var { Command } = require("../command"); -var accountImporter = require("../accountImporter"); -var needProjectId = require("../projectUtils").needProjectId; -const { logger } = require("../logger"); -var { requirePermissions } = require("../requirePermissions"); -var utils = require("../utils"); - -var MAX_BATCH_SIZE = 1000; -var validateOptions = accountImporter.validateOptions; -var validateUserJson = accountImporter.validateUserJson; -var transArrayToUser = accountImporter.transArrayToUser; -var serialImportUsers = accountImporter.serialImportUsers; - -module.exports = new Command("auth:import [dataFile]") - .description("import users into your Firebase project from a data file(.csv or .json)") - .option( - "--hash-algo ", - "specify the hash algorithm used in password for these accounts" - ) - .option("--hash-key ", "specify the key used in hash algorithm") - .option( - "--salt-separator ", - "specify the salt separator which will be appended to salt when verifying password. only used by SCRYPT now." - ) - .option("--rounds ", "specify how many rounds for hash calculation.") - .option( - "--mem-cost ", - "specify the memory cost for firebase scrypt, or cpu/memory cost for standard scrypt" - ) - .option("--parallelization ", "specify the parallelization for standard scrypt.") - .option("--block-size ", "specify the block size (normally is 8) for standard scrypt.") - .option("--dk-len ", "specify derived key length for standard scrypt.") - .option( - "--hash-input-order ", - "specify the order of password and salt. Possible values are SALT_FIRST and PASSWORD_FIRST. " + - "MD5, SHA1, SHA256, SHA512, HMAC_MD5, HMAC_SHA1, HMAC_SHA256, HMAC_SHA512 support this flag." - ) - .before(requirePermissions, ["firebaseauth.users.create", "firebaseauth.users.update"]) - .action(function (dataFile, options) { - var projectId = needProjectId(options); - var checkRes = validateOptions(options); - if (!checkRes.valid) { - return checkRes; - } - var hashOptions = checkRes; - - if (!_.endsWith(dataFile, ".csv") && !_.endsWith(dataFile, ".json")) { - return utils.reject("Data file must end with .csv or .json", { exit: 1 }); - } - var stats = fs.statSync(dataFile); - var fileSizeInBytes = stats.size; - logger.info("Processing " + clc.bold(dataFile) + " (" + fileSizeInBytes + " bytes)"); - - var inStream = fs.createReadStream(dataFile); - var batches = []; - var currentBatch = []; - var counter = 0; - return new Promise(function (resolve, reject) { - var parser; - if (dataFile.endsWith(".csv")) { - parser = csv({ objectMode: true }); - parser - .on("data", function (line) { - counter++; - var user = transArrayToUser( - line.map(function (str) { - // Ignore starting '|'' and trailing '|'' - var newStr = str.trim().replace(/^["|'](.*)["|']$/, "$1"); - return newStr === "" ? undefined : newStr; - }) - ); - if (user.error) { - return reject( - "Line " + counter + " (" + line + ") has invalid data format: " + user.error - ); - } - currentBatch.push(user); - if (currentBatch.length === MAX_BATCH_SIZE) { - batches.push(currentBatch); - currentBatch = []; - } - }) - .on("end", function () { - if (currentBatch.length) { - batches.push(currentBatch); - } - return resolve(batches); - }); - inStream.pipe(parser); - } else { - parser = jsonStream.parse(["users", { emitKey: true }]); - parser - .on("data", function (pair) { - counter++; - var res = validateUserJson(pair.value); - if (res.error) { - return reject(res.error); - } - currentBatch.push(pair.value); - if (currentBatch.length === MAX_BATCH_SIZE) { - batches.push(currentBatch); - currentBatch = []; - } - }) - .on("end", function () { - if (currentBatch.length) { - batches.push(currentBatch); - } - return resolve(batches); - }); - inStream.pipe(parser); - } - }).then( - function (userListArr) { - logger.debug( - "Preparing to import", - counter, - "user records in", - userListArr.length, - "batches." - ); - if (userListArr.length) { - return serialImportUsers(projectId, hashOptions, userListArr, 0); - } - }, - function (error) { - return utils.reject(error, { exit: 1 }); - } - ); - }); diff --git a/src/commands/auth-import.ts b/src/commands/auth-import.ts new file mode 100644 index 00000000000..2a8e6f44d4a --- /dev/null +++ b/src/commands/auth-import.ts @@ -0,0 +1,139 @@ +import { parse } from "csv-parse"; +import * as Chain from "stream-chain"; +import * as clc from "cli-color"; +import * as fs from "fs-extra"; +import * as Pick from "stream-json/filters/Pick"; +import * as StreamArray from "stream-json/streamers/StreamArray"; + +import { Command } from "../command"; +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import { needProjectId } from "../projectUtils"; +import { Options } from "../options"; +import { requirePermissions } from "../requirePermissions"; +import * as accountImporter from "../accountImporter"; + +const MAX_BATCH_SIZE = 1000; +const validateOptions = accountImporter.validateOptions; +const validateUserJson = accountImporter.validateUserJson; +const transArrayToUser = accountImporter.transArrayToUser; +const serialImportUsers = accountImporter.serialImportUsers; + +module.exports = new Command("auth:import [dataFile]") + .description("import users into your Firebase project from a data file(.csv or .json)") + .option( + "--hash-algo ", + "specify the hash algorithm used in password for these accounts" + ) + .option("--hash-key ", "specify the key used in hash algorithm") + .option( + "--salt-separator ", + "specify the salt separator which will be appended to salt when verifying password. only used by SCRYPT now." + ) + .option("--rounds ", "specify how many rounds for hash calculation.") + .option( + "--mem-cost ", + "specify the memory cost for firebase scrypt, or cpu/memory cost for standard scrypt" + ) + .option("--parallelization ", "specify the parallelization for standard scrypt.") + .option("--block-size ", "specify the block size (normally is 8) for standard scrypt.") + .option("--dk-len ", "specify derived key length for standard scrypt.") + .option( + "--hash-input-order ", + "specify the order of password and salt. Possible values are SALT_FIRST and PASSWORD_FIRST. " + + "MD5, SHA1, SHA256, SHA512, HMAC_MD5, HMAC_SHA1, HMAC_SHA256, HMAC_SHA512 support this flag." + ) + .before(requirePermissions, ["firebaseauth.users.create", "firebaseauth.users.update"]) + .action(async (dataFile: string, options: Options) => { + const projectId = needProjectId(options); + const checkRes = validateOptions(options); + if (!checkRes.valid) { + return checkRes; + } + const hashOptions = checkRes; + + if (!dataFile.endsWith(".csv") && !dataFile.endsWith(".json")) { + throw new FirebaseError("Data file must end with .csv or .json"); + } + const stats = await fs.stat(dataFile); + const fileSizeInBytes = stats.size; + logger.info(`Processing ${clc.bold(dataFile)} (${fileSizeInBytes} bytes)`); + + const batches: any[] = []; + let currentBatch: any[] = []; + let counter = 0; + let userListArr: any[] = []; + const inStream = fs.createReadStream(dataFile); + if (dataFile.endsWith(".csv")) { + userListArr = await new Promise((resolve, reject) => { + const parser = parse(); + parser + .on("readable", () => { + let record: string[] = []; + while ((record = parser.read()) !== null) { + counter++; + const trimmed = record.map((s) => { + const str = s.trim().replace(/^["|'](.*)["|']$/, "$1"); + return str === "" ? undefined : str; + }); + const user = transArrayToUser(trimmed); + // TODO: Remove this casst once user can have an error. + const err = (user as any).error; + if (err) { + return reject( + new FirebaseError( + `Line ${counter} (${record.join(",")}) has invalid data format: ${err}` + ) + ); + } + currentBatch.push(user); + if (currentBatch.length === MAX_BATCH_SIZE) { + batches.push(currentBatch); + currentBatch = []; + } + } + }) + .on("end", () => { + if (currentBatch.length) { + batches.push(currentBatch); + } + resolve(batches); + }); + inStream.pipe(parser); + }); + } else { + userListArr = await new Promise((resolve, reject) => { + const pipeline = new Chain([ + Pick.withParser({ filter: /^users$/ }), + StreamArray.streamArray(), + ({ value }) => { + counter++; + const user = validateUserJson(value); + // TODO: Remove this casst once user can have an error. + const err = (user as any).error; + if (err) { + throw new FirebaseError(`Validation Error: ${err}`); + } + currentBatch.push(user); + if (currentBatch.length === MAX_BATCH_SIZE) { + batches.push(currentBatch); + currentBatch = []; + } + }, + ]); + pipeline.once("error", reject); + pipeline.on("finish", () => { + if (currentBatch.length) { + batches.push(currentBatch); + } + resolve(batches); + }); + inStream.pipe(pipeline); + }); + } + + logger.debug(`Preparing to import ${counter} user records in ${userListArr.length} batches.`); + if (userListArr.length) { + return serialImportUsers(projectId, hashOptions, userListArr, 0); + } + }); From 6d291e8e2158504437c58cf7849123d24093366a Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 20 Jan 2022 12:17:00 -0600 Subject: [PATCH 0035/1699] update node types package to 12 (#4005) * update node types package to 12 * a couple type issues... * uncomment linking --- npm-shrinkwrap.json | 2993 ++++++----------- package.json | 8 +- scripts/integration-helpers/cli.ts | 4 +- scripts/integration-helpers/framework.ts | 2 +- src/deploy/functions/runtimes/golang/index.ts | 2 +- src/emulator/downloadableEmulators.ts | 4 +- src/emulator/functionsEmulator.ts | 11 +- src/emulator/functionsEmulatorRuntime.ts | 22 +- src/emulator/storage/rules/runtime.ts | 6 +- src/hosting/proxy.ts | 2 +- src/test/apiv2.spec.ts | 8 +- .../emulators/functionsRuntimeWorker.spec.ts | 2 +- 12 files changed, 1155 insertions(+), 1909 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 861e6b5d92f..d423d13075b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,7 +9,7 @@ "version": "10.1.1", "license": "MIT", "dependencies": { - "@google-cloud/pubsub": "^2.7.0", + "@google-cloud/pubsub": "^2.18.4", "abort-controller": "^3.0.0", "ajv": "^6.12.6", "archiver": "^5.0.0", @@ -55,7 +55,7 @@ "stream-chain": "^2.2.4", "stream-json": "^1.7.3", "superstatic": "^7.1.0", - "tar": "^4.3.0", + "tar": "^6.1.11", "tcp-port-used": "^1.0.1", "tmp": "0.0.33", "triple-beam": "^1.3.0", @@ -97,7 +97,7 @@ "@types/marked-terminal": "^3.1.3", "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", - "@types/node": "^10.17.50", + "@types/node": "^12.20.39", "@types/node-fetch": "^2.5.7", "@types/progress": "^2.0.3", "@types/puppeteer": "^5.4.2", @@ -108,7 +108,7 @@ "@types/sinon-chai": "^3.2.2", "@types/stream-json": "^1.7.2", "@types/supertest": "^2.0.6", - "@types/tar": "^4.0.0", + "@types/tar": "^6.1.1", "@types/tcp-port-used": "^1.0.0", "@types/tmp": "^0.1.0", "@types/triple-beam": "^1.3.0", @@ -657,6 +657,16 @@ "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==", "dev": true }, + "node_modules/@firebase/analytics/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/analytics/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -681,77 +691,22 @@ "xmlhttprequest": "1.8.0" } }, - "node_modules/@firebase/app-compat": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.13.tgz", - "integrity": "sha512-K5eFU0bIbGTTRPihZEc1BtuOTwEtiKhu2tm4e+g9+c5cMSpJvr+GIQaN8A8SgDeqt13DP9lKqTic2NiG+6EQCw==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/app": "0.7.12", - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/app": { - "version": "0.7.12", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.12.tgz", - "integrity": "sha512-eniX/KcMA/iTuRqdYvMuRaPj3DGxWdXa5r2tsmtLbx8HvdY/Wzq3H0p7fyapBRPsg0rO+t3xzWDVZ3Blq2xfCA==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/component": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", - "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/util": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", - "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true, - "peer": true - }, "node_modules/@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", "dev": true }, + "node_modules/@firebase/app/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/app/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -794,160 +749,39 @@ } }, "node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "node_modules/@firebase/component/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", + "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", "dev": true, "dependencies": { + "@firebase/util": "0.3.4", "tslib": "^1.11.1" } }, "node_modules/@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.1.tgz", + "integrity": "sha512-/1HhR4ejpqUaM9Cn3KSeNdQvdlehWIhdfTVWFxS73ZlLYf7ayk9jITwH10H3ZOIm5yNzxF67p/U7Z/0IPhgWaQ==", "dev": true, "dependencies": { "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", + "@firebase/component": "0.1.21", + "@firebase/database-types": "0.6.1", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", + "@firebase/util": "0.3.4", "faye-websocket": "0.11.3", "tslib": "^1.11.1" } }, - "node_modules/@firebase/database-compat": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.5.tgz", - "integrity": "sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==", - "dev": true, - "dependencies": { - "@firebase/component": "0.5.10", - "@firebase/database": "0.12.5", - "@firebase/database-types": "0.9.4", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/app-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", - "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==", - "dev": true - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", - "dev": true, - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/component": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", - "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", - "dev": true, - "dependencies": { - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/database": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.5.tgz", - "integrity": "sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==", - "dev": true, - "dependencies": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/database-types": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.4.tgz", - "integrity": "sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==", - "dev": true, - "dependencies": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.4.3" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/util": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", - "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat/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==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@firebase/database-compat/node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, "node_modules/@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz", + "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==", "dev": true, "dependencies": { "@firebase/app-types": "0.6.1" } }, - "node_modules/@firebase/database/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/firestore": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.18.0.tgz", @@ -981,6 +815,16 @@ "@firebase/app-types": "0.x" } }, + "node_modules/@firebase/firestore/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/firestore/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1013,6 +857,25 @@ "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==", "dev": true }, + "node_modules/@firebase/functions/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/functions/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/installations": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.17.tgz", @@ -1039,6 +902,16 @@ "@firebase/app-types": "0.x" } }, + "node_modules/@firebase/installations/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/installations/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1081,6 +954,16 @@ "@firebase/app-types": "0.x" } }, + "node_modules/@firebase/messaging/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/messaging/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1114,6 +997,16 @@ "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==", "dev": true }, + "node_modules/@firebase/performance/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/performance/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1158,6 +1051,16 @@ "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==", "dev": true }, + "node_modules/@firebase/remote-config/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/remote-config/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1193,6 +1096,16 @@ "@firebase/util": "0.x" } }, + "node_modules/@firebase/storage/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/storage/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1207,7 +1120,6 @@ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", "dev": true, - "peer": true, "dependencies": { "tslib": "^1.11.1" } @@ -1335,22 +1247,22 @@ } }, "node_modules/@google-cloud/pubsub": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.7.0.tgz", - "integrity": "sha512-wc/XOo5Ibo3GWmuaLu80EBIhXSdu2vf99HUqBbdsSSkmRNIka2HqoIhLlOFnnncQn0lZnGL7wtKGIDLoH9LiBg==", + "version": "2.18.4", + "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.18.4.tgz", + "integrity": "sha512-mgKZ7XdXN7MEGK+MCmRKuoq3GBiuYIa9ytYuV1DIHbd+eYqqyPYZHvL8g/73eogkNYK5TxSydja7TCRDzfJaxA==", "dependencies": { "@google-cloud/paginator": "^3.0.0", "@google-cloud/precise-date": "^2.0.0", "@google-cloud/projectify": "^2.0.0", "@google-cloud/promisify": "^2.0.0", - "@opentelemetry/api": "^0.11.0", - "@opentelemetry/tracing": "^0.11.0", + "@opentelemetry/api": "^1.0.0", + "@opentelemetry/semantic-conventions": "^0.24.0", "@types/duplexify": "^3.6.0", "@types/long": "^4.0.0", "arrify": "^2.0.0", "extend": "^3.0.2", - "google-auth-library": "^6.1.2", - "google-gax": "^2.9.2", + "google-auth-library": "^7.0.0", + "google-gax": "2.28.1", "is-stream-ended": "^0.1.4", "lodash.snakecase": "^4.1.1", "p-defer": "^3.0.0" @@ -1359,44 +1271,6 @@ "node": ">=10" } }, - "node_modules/@google-cloud/pubsub/node_modules/google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/pubsub/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@google-cloud/pubsub/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==", - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, "node_modules/@google-cloud/storage": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.7.0.tgz", @@ -1468,29 +1342,27 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.8.tgz", - "integrity": "sha512-64hg5rmEm6F/NvlWERhHmmgxbWU8nD2TMWE+9TvG7/WcOrFT3fzg/Uu631pXRFwmJ4aWO/kp9vVSlr8FUjBDLA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.4.6.tgz", + "integrity": "sha512-Byau4xiXfIixb1PnW30V/P9mkrZ05lknyNqiK+cVY9J5hj3gecxd/anwaUbAM8j834zg1x78NvAbwGnMfWEu7A==", "dependencies": { - "@grpc/proto-loader": "^0.6.0-pre14", - "@types/node": "^12.12.47", - "google-auth-library": "^6.0.0", - "semver": "^6.2.0" + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" }, "engines": { "node": "^8.13.0 || >=10.10.0" } }, "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.6.0-pre9", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.0-pre9.tgz", - "integrity": "sha512-oM+LjpEjNzW5pNJjt4/hq1HYayNeQT+eGrOPABJnYHv7TyNPDNzkQ76rDYZF86X5swJOa4EujEMzQ9iiTdPgww==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.9.0", - "yargs": "^15.3.1" + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" @@ -1499,216 +1371,11 @@ "node": ">=6" } }, - "node_modules/@grpc/grpc-js/node_modules/@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, - "node_modules/@grpc/grpc-js/node_modules/@types/node": { - "version": "12.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.9.tgz", - "integrity": "sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q==" - }, - "node_modules/@grpc/grpc-js/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@grpc/grpc-js/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/@grpc/grpc-js/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@grpc/grpc-js/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==" - }, - "node_modules/@grpc/grpc-js/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==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@grpc/grpc-js/node_modules/google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@grpc/grpc-js/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@grpc/grpc-js/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==", - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@grpc/grpc-js/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==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@grpc/grpc-js/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==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@grpc/grpc-js/node_modules/protobufjs": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", - "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", - "hasInstallScript": true, - "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.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/@types/node": { - "version": "13.13.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", - "integrity": "sha512-ctzZJ+XsmHQwe3xp07gFUq4JxBaRSYzKHPgblR76//UanGST7vfFNF0+ty5eEbgTqsENopzoDK090xlha9dccQ==" - }, - "node_modules/@grpc/grpc-js/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@grpc/grpc-js/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@grpc/grpc-js/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "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.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@grpc/grpc-js/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@grpc/proto-loader": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.1.tgz", "integrity": "sha512-3y0FhacYAwWvyXshH18eDkUI40wT/uGio7MAegzY8lO5+wVsc19+1A7T0pPptae4kl7bdITL+0cHpnAPmryBjQ==", + "dev": true, "dependencies": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" @@ -1962,96 +1629,21 @@ } }, "node_modules/@opentelemetry/api": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.11.0.tgz", - "integrity": "sha512-K+1ADLMxduhsXoZ0GRfi9Pw162FvzBQLDQlHru1lg86rpIU+4XqdJkSGo6y3Kg+GmOWq1HNHOA/ydw/rzHQkRg==", - "dependencies": { - "@opentelemetry/context-base": "^0.11.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/context-base": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.11.0.tgz", - "integrity": "sha512-ESRk+572bftles7CVlugAj5Azrz61VO0MO0TS2pE9MLVL/zGmWuUBQryART6/nsrFqo+v9HPt37GPNcECTZR1w==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/core": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-0.11.0.tgz", - "integrity": "sha512-ZEKjBXeDGBqzouz0uJmrbEKNExEsQOhsZ3tJDCLcz5dUNoVw642oIn2LYWdQK2YdIfZbEmltiF65/csGsaBtFA==", - "dependencies": { - "@opentelemetry/api": "^0.11.0", - "@opentelemetry/context-base": "^0.11.0", - "semver": "^7.1.3" - }, - "engines": { - "node": ">=8.5.0" - } - }, - "node_modules/@opentelemetry/core/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-0.11.0.tgz", - "integrity": "sha512-o7DwV1TcezqBtS5YW2AWBcn01nVpPptIbTr966PLlVBcS//w8LkjeOShiSZxQ0lmV4b2en0FiSouSDoXk/5qIQ==", - "dependencies": { - "@opentelemetry/api": "^0.11.0", - "@opentelemetry/core": "^0.11.0" - }, + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz", + "integrity": "sha512-BuJuXRSJNQ3QoKA6GWWDyuLpOUck+9hAXNMCnrloc1aWVoy6Xq6t9PUV08aBZ4Lutqq2LEHM486bpZqoViScog==", "engines": { "node": ">=8.0.0" } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.11.0.tgz", - "integrity": "sha512-xsthnI/J+Cx0YVDGgUzvrH0ZTtfNtl866M454NarYwDrc0JvC24sYw+XS5PJyk2KDzAHtb0vlrumUc1OAut/Fw==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/tracing": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/tracing/-/tracing-0.11.0.tgz", - "integrity": "sha512-QweFmxzl32BcyzwdWCNjVXZT1WeENNS/RWETq/ohqu+fAsTcMyGcr6cOq/yDdFmtBy+bm5WVVdeByEjNS+c4/w==", - "deprecated": "Package renamed to @opentelemetry/sdk-trace-base", - "dependencies": { - "@opentelemetry/api": "^0.11.0", - "@opentelemetry/context-base": "^0.11.0", - "@opentelemetry/core": "^0.11.0", - "@opentelemetry/resources": "^0.11.0", - "@opentelemetry/semantic-conventions": "^0.11.0" - }, + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.24.0.tgz", + "integrity": "sha512-a/szuMQV0Quy0/M7kKdglcbRSoorleyyOwbTNNJ32O+RBN766wbQlMTvdimImTmwYWGr+NJOni1EcC242WlRcA==", "engines": { "node": ">=8.0.0" } }, - "node_modules/@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2325,16 +1917,6 @@ "@types/serve-static": "*" } }, - "node_modules/@types/express-jwt": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", - "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", - "dev": true, - "dependencies": { - "@types/express": "*", - "@types/express-unless": "*" - } - }, "node_modules/@types/express-serve-static-core": { "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", @@ -2346,15 +1928,6 @@ "@types/range-parser": "*" } }, - "node_modules/@types/express-unless": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.2.tgz", - "integrity": "sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/fs-extra": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", @@ -2413,9 +1986,9 @@ "dev": true }, "node_modules/@types/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", - "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "node_modules/@types/marked": { "version": "4.0.1", @@ -2458,9 +2031,9 @@ "dev": true }, "node_modules/@types/minipass": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-2.2.0.tgz", - "integrity": "sha512-wuzZksN4w4kyfoOv/dlpov4NOunwutLA/q7uc00xU02ZyUY+aoM5PWIXEKBMnm0NHd4a+N71BMjq+x7+2Af1fg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-IKmcvG5RnNUtRoxSsusfYnd7fPl8NCLjLutRDvpqwWUR55XvGfy6GIGQUSsKgT2A8qzMjsWfHZNU7d6gxFgqzQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -2482,9 +2055,9 @@ } }, "node_modules/@types/node": { - "version": "10.17.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", - "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==" + "version": "12.20.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.39.tgz", + "integrity": "sha512-U7PMwkDmc3bnL0e4U8oA0POpi1vfsYDc+DEUS2+rPxm9NlLcW1dBa5JcRhO633PoPUcCSWMNXrMsqhmAVEo+IQ==" }, "node_modules/@types/node-fetch": { "version": "2.5.7", @@ -2662,9 +2235,9 @@ } }, "node_modules/@types/tar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/tar/-/tar-4.0.3.tgz", - "integrity": "sha512-Z7AVMMlkI8NTWF0qGhC4QIX0zkV/+y0J8x7b/RsHrN0310+YNjoJd8UrApCiGBCWtKjxS9QhNqLi2UJNToh5hA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.1.tgz", + "integrity": "sha512-8mto3YZfVpqB1CHMaYz1TUYIQfZFbh/QbEq5Hsn6D0ilCfqRVCdalmc89B7vi3jhl9UYIk+dWDABShNfOkv5HA==", "dev": true, "dependencies": { "@types/minipass": "*", @@ -3223,6 +2796,27 @@ "string-width": "^4.1.0" } }, + "node_modules/ansi-align/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -3523,24 +3117,15 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/atlassian-openapi": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.15.tgz", - "integrity": "sha512-HzgdBHJ/9jZWZfass5DRJNG4vLxoFl6Zcl3B+8Cp2VSpEH7t0laBGnGtcthvj2h73hq8dzjKtVlG30agBZ4OPw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.13.tgz", + "integrity": "sha512-2/CRqPWZ15BBr9s6/c48QaBKvpxbonTeFGGXKFSmcSVFqH0KfFMWkgMSnCWKyAQ7gZ8Ch9BrqCDAG7ENzFWX2A==", "dev": true, "dependencies": { - "jsonpointer": "^5.0.0", + "jsonpointer": "^4.0.1", "urijs": "^1.18.10" } }, - "node_modules/atlassian-openapi/node_modules/jsonpointer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", - "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3752,7 +3337,28 @@ "node": ">=8" } }, - "node_modules/boxen/node_modules/supports-color": { + "node_modules/boxen/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", @@ -4168,13 +3774,33 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, + "node_modules/cliui/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -4707,6 +4333,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -5105,7 +4732,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -6032,9 +5658,9 @@ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, "node_modules/fast-text-encoding": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", - "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" }, "node_modules/fast-url-parser": { "version": "1.1.3", @@ -6213,52 +5839,36 @@ } }, "node_modules/firebase-admin": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", - "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.4.2.tgz", + "integrity": "sha512-mRnBJbW6BAz6DJkZ0GOUTkmnmCrwVzMreMc6O+RXWukFydOzi5Xr6TKSiPKxoOQw41r9IluP2AZ3Qzvlx2SR+g==", "dev": true, "dependencies": { - "@firebase/database-compat": "^0.1.1", - "@firebase/database-types": "^0.7.2", - "@types/node": ">=12.12.47", + "@firebase/database": "^0.8.1", + "@firebase/database-types": "^0.6.1", + "@types/node": "^10.10.0", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", "node-forge": "^0.10.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=10.10.0" }, "optionalDependencies": { "@google-cloud/firestore": "^4.5.0", "@google-cloud/storage": "^5.3.0" } }, - "node_modules/firebase-admin/node_modules/@firebase/app-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", - "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==", - "dev": true - }, - "node_modules/firebase-admin/node_modules/@firebase/database-types": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", - "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", - "dev": true, - "dependencies": { - "@firebase/app-types": "0.6.3" - } - }, "node_modules/firebase-admin/node_modules/@types/node": { - "version": "17.0.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", - "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", "dev": true }, "node_modules/firebase-functions": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.16.0.tgz", - "integrity": "sha512-6ISOn0JckMtpA3aJ/+wCCGhThUhBUrpZD+tSkUeolx0Vr+NoYFXA0+2YzJZa/A2MDU8gotPzUtnauLSEQvfClQ==", + "version": "3.15.7", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.15.7.tgz", + "integrity": "sha512-ZD7r8eoWWebgs+mTqfH8NLUT2C0f7/cyAvIA1RSUdBVQZN7MBBt3oSlN/rL3e+m6tdlJz6YbQ3hrOKOGjOVYvQ==", "dev": true, "dependencies": { "@types/cors": "^2.8.5", @@ -6271,7 +5881,7 @@ "node": "^8.13.0 || >=10.10.0" }, "peerDependencies": { - "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0" + "firebase-admin": "^8.0.0 || ^9.0.0" } }, "node_modules/firebase-functions/node_modules/@types/express": { @@ -6285,6 +5895,40 @@ "@types/serve-static": "*" } }, + "node_modules/firebase/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/firebase/node_modules/@firebase/database": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "dev": true, + "dependencies": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "faye-websocket": "0.11.3", + "tslib": "^1.11.1" + } + }, + "node_modules/firebase/node_modules/@firebase/database-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "dev": true, + "dependencies": { + "@firebase/app-types": "0.6.1" + } + }, "node_modules/firebase/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -6454,11 +6098,14 @@ } }, "node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dependencies": { - "minipass": "^2.6.0" + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" } }, "node_modules/fs.realpath": { @@ -7030,19 +6677,22 @@ "dev": true }, "node_modules/google-gax": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.9.2.tgz", - "integrity": "sha512-Pve4osEzNKpBZqFXMfGKBbKCtgnHpUe5IQMh5Ou+Xtg8nLcba94L3gF0xgM5phMdGRRqJn0SMjcuEVmOYu7EBg==", + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.28.1.tgz", + "integrity": "sha512-2Xjd3FrjlVd6Cmw2B2Aicpc/q92SwTpIOvxPUlnRg9w+Do8nu7UR+eQrgoKlo2FIUcUuDTvppvcx8toND0pK9g==", "dependencies": { - "@grpc/grpc-js": "~1.1.1", - "@grpc/proto-loader": "^0.5.1", + "@grpc/grpc-js": "~1.4.0", + "@grpc/proto-loader": "^0.6.1", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", - "google-auth-library": "^6.1.3", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.6.1", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", - "protobufjs": "^6.9.0", + "object-hash": "^2.1.1", + "proto3-json-serializer": "^0.1.5", + "protobufjs": "6.11.2", "retry-request": "^4.0.0" }, "bin": { @@ -7052,85 +6702,30 @@ "node": ">=10" } }, - "node_modules/google-gax/node_modules/@types/node": { - "version": "13.13.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", - "integrity": "sha512-ctzZJ+XsmHQwe3xp07gFUq4JxBaRSYzKHPgblR76//UanGST7vfFNF0+ty5eEbgTqsENopzoDK090xlha9dccQ==" - }, - "node_modules/google-gax/node_modules/google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/google-gax/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==", - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/google-gax/node_modules/protobufjs": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", - "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", - "hasInstallScript": true, + "node_modules/google-gax/node_modules/@grpc/proto-loader": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", "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.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" }, "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" } }, - "node_modules/google-gax/node_modules/protobufjs/node_modules/@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, "node_modules/google-p12-pem": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", - "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", "dependencies": { - "node-forge": "^1.0.0" + "node-forge": "^0.10.0" }, "bin": { "gp12-pem": "build/src/bin/gp12-pem.js" @@ -7139,14 +6734,6 @@ "node": ">=10" } }, - "node_modules/google-p12-pem/node_modules/node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -7646,6 +7233,27 @@ "node": ">=8" } }, + "node_modules/inquirer/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/inquirer/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7736,11 +7344,12 @@ } }, "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==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "optional": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/is-glob": { @@ -8073,21 +7682,6 @@ "valid-url": "^1" } }, - "node_modules/jose": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", - "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", - "dev": true, - "dependencies": { - "@panva/asn1.js": "^1.0.0" - }, - "engines": { - "node": ">=10.13.0 < 13 || >=13.7.0" - }, - "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", @@ -8335,45 +7929,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/jwks-rsa": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", - "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", - "dev": true, - "dependencies": { - "@types/express-jwt": "0.0.42", - "debug": "^4.3.2", - "jose": "^2.0.5", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" - }, - "engines": { - "node": ">=10 < 13 || >=14" - } - }, - "node_modules/jwks-rsa/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/jwks-rsa/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -8464,12 +8019,6 @@ "node": ">= 0.8.0" } }, - "node_modules/limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", - "dev": true - }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -8524,12 +8073,6 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -8778,32 +8321,6 @@ "node": ">=10" } }, - "node_modules/lru-memoizer": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", - "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", - "dev": true, - "dependencies": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" - } - }, - "node_modules/lru-memoizer/node_modules/lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "node_modules/lru-memoizer/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, "node_modules/lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -9085,27 +8602,28 @@ } }, "node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", + "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/minipass/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, "node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dependencies": { - "minipass": "^2.9.0" - } - }, + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -9482,7 +9000,6 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true, "engines": { "node": ">= 6.0.0" } @@ -9511,70 +9028,12 @@ "node": ">= 10.12.0" } }, - "node_modules/node-gyp/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "optional": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/node-gyp/node_modules/graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "optional": true }, - "node_modules/node-gyp/node_modules/minipass": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", - "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-gyp/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "optional": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/node-gyp/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-gyp/node_modules/qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -9641,23 +9100,6 @@ "node": ">=10" } }, - "node_modules/node-gyp/node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "optional": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/node-gyp/node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -9870,6 +9312,15 @@ "node": ">=8" } }, + "node_modules/nyc/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/nyc/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -9903,6 +9354,20 @@ "node": ">=8" } }, + "node_modules/nyc/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/nyc/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -10073,6 +9538,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -10279,6 +9752,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, "dependencies": { "p-try": "^2.0.0" }, @@ -10332,6 +9806,7 @@ "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, "engines": { "node": ">=6" } @@ -10468,6 +9943,7 @@ "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, "engines": { "node": ">=8" } @@ -10702,10 +10178,18 @@ "node": ">= 8" } }, + "node_modules/proto3-json-serializer": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.6.tgz", + "integrity": "sha512-tGbV6m6Kad8NqxMh5hw87euPS0YoZSAOIfvR01zYkQV8Gpx1V/8yU/0gCKCvfCkhAJsjvzzhnnsdQxA1w7PSog==", + "dependencies": { + "protobufjs": "^6.11.2" + } + }, "node_modules/protobufjs": { - "version": "6.8.8", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", - "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -10718,8 +10202,8 @@ "@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", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", "long": "^4.0.0" }, "bin": { @@ -10727,6 +10211,11 @@ "pbts": "bin/pbts" } }, + "node_modules/protobufjs/node_modules/@types/node": { + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" + }, "node_modules/proxy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.2.tgz", @@ -10833,12 +10322,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "node_modules/psl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", @@ -11293,7 +10776,8 @@ "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "node_modules/resolve": { "version": "1.17.0", @@ -11573,7 +11057,8 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "devOptional": true }, "node_modules/setimmediate": { "version": "1.0.5", @@ -12020,16 +11505,37 @@ } }, "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==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "optional": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "optional": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, "node_modules/strip-ansi": { @@ -12475,6 +11981,15 @@ "node": ">=8" } }, + "node_modules/swagger2openapi/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/swagger2openapi/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -12499,6 +12014,20 @@ "node": ">=8" } }, + "node_modules/swagger2openapi/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/swagger2openapi/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -12549,20 +12078,19 @@ } }, "node_modules/tar": { - "version": "4.4.18", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.18.tgz", - "integrity": "sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dependencies": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" }, "engines": { - "node": ">=4.5" + "node": ">= 10" } }, "node_modules/tar-fs": { @@ -12593,33 +12121,23 @@ } }, "node_modules/tar/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/tar/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" - } - ] + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } }, - "node_modules/tar/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } }, "node_modules/tcp-port-used": { "version": "1.0.1", @@ -13279,6 +12797,14 @@ "node": ">=10" } }, + "node_modules/update-notifier/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==", + "engines": { + "node": ">=8" + } + }, "node_modules/update-notifier/node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -13319,6 +12845,19 @@ "node": ">=10" } }, + "node_modules/update-notifier/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/update-notifier/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13496,7 +13035,8 @@ "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "node_modules/wide-align": { "version": "1.1.5", @@ -13518,6 +13058,27 @@ "node": ">=8" } }, + "node_modules/widest-line/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", @@ -13623,15 +13184,36 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "node_modules/wrap-ansi/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==", + "engines": { + "node": ">=8" + } }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "node_modules/wrap-ansi/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -13687,7 +13269,8 @@ "node_modules/y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true }, "node_modules/yallist": { "version": "4.0.0", @@ -13706,7 +13289,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -13724,7 +13306,6 @@ "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, "engines": { "node": ">=10" } @@ -13777,11 +13358,31 @@ "node": ">=8" } }, + "node_modules/yargs/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yargs/node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -14292,6 +13893,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14324,81 +13935,24 @@ "xmlhttprequest": "1.8.0" }, "dependencies": { - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - } - } - }, - "@firebase/app-compat": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.13.tgz", - "integrity": "sha512-K5eFU0bIbGTTRPihZEc1BtuOTwEtiKhu2tm4e+g9+c5cMSpJvr+GIQaN8A8SgDeqt13DP9lKqTic2NiG+6EQCw==", - "dev": true, - "peer": true, - "requires": { - "@firebase/app": "0.7.12", - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "@firebase/app": { - "version": "0.7.12", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.12.tgz", - "integrity": "sha512-eniX/KcMA/iTuRqdYvMuRaPj3DGxWdXa5r2tsmtLbx8HvdY/Wzq3H0p7fyapBRPsg0rO+t3xzWDVZ3Blq2xfCA==", - "dev": true, - "peer": true, - "requires": { - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, "@firebase/component": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", - "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", "dev": true, - "peer": true, "requires": { - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^2.1.0" + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" } }, "@firebase/util": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", - "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", "dev": true, - "peer": true, "requires": { - "tslib": "^2.1.0" + "tslib": "^1.11.1" } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true, - "peer": true } } }, @@ -14432,152 +13986,34 @@ "requires": {} }, "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", + "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", "dev": true, "requires": { - "@firebase/util": "0.3.2", + "@firebase/util": "0.3.4", "tslib": "^1.11.1" - }, - "dependencies": { - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - } } }, "@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.1.tgz", + "integrity": "sha512-/1HhR4ejpqUaM9Cn3KSeNdQvdlehWIhdfTVWFxS73ZlLYf7ayk9jITwH10H3ZOIm5yNzxF67p/U7Z/0IPhgWaQ==", "dev": true, "requires": { "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", + "@firebase/component": "0.1.21", + "@firebase/database-types": "0.6.1", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", + "@firebase/util": "0.3.4", "faye-websocket": "0.11.3", "tslib": "^1.11.1" - }, - "dependencies": { - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - } - } - }, - "@firebase/database-compat": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.5.tgz", - "integrity": "sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==", - "dev": true, - "requires": { - "@firebase/component": "0.5.10", - "@firebase/database": "0.12.5", - "@firebase/database-types": "0.9.4", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "@firebase/app-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", - "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==", - "dev": true - }, - "@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", - "dev": true, - "requires": {} - }, - "@firebase/component": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", - "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", - "dev": true, - "requires": { - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "@firebase/database": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.5.tgz", - "integrity": "sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==", - "dev": true, - "requires": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "@firebase/database-types": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.4.tgz", - "integrity": "sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==", - "dev": true, - "requires": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.4.3" - } - }, - "@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/util": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", - "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - } } }, "@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz", + "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==", "dev": true, "requires": { "@firebase/app-types": "0.6.1" @@ -14600,6 +14036,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14629,6 +14075,27 @@ "@firebase/messaging-types": "0.5.0", "node-fetch": "2.6.1", "tslib": "^1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + } + } } }, "@firebase/functions-types": { @@ -14650,6 +14117,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14688,6 +14165,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14720,6 +14207,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14762,6 +14259,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14791,6 +14298,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14814,7 +14331,6 @@ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", "dev": true, - "peer": true, "requires": { "tslib": "^1.11.1" } @@ -14923,62 +14439,25 @@ "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" }, "@google-cloud/pubsub": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.7.0.tgz", - "integrity": "sha512-wc/XOo5Ibo3GWmuaLu80EBIhXSdu2vf99HUqBbdsSSkmRNIka2HqoIhLlOFnnncQn0lZnGL7wtKGIDLoH9LiBg==", + "version": "2.18.4", + "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.18.4.tgz", + "integrity": "sha512-mgKZ7XdXN7MEGK+MCmRKuoq3GBiuYIa9ytYuV1DIHbd+eYqqyPYZHvL8g/73eogkNYK5TxSydja7TCRDzfJaxA==", "requires": { "@google-cloud/paginator": "^3.0.0", "@google-cloud/precise-date": "^2.0.0", "@google-cloud/projectify": "^2.0.0", "@google-cloud/promisify": "^2.0.0", - "@opentelemetry/api": "^0.11.0", - "@opentelemetry/tracing": "^0.11.0", + "@opentelemetry/api": "^1.0.0", + "@opentelemetry/semantic-conventions": "^0.24.0", "@types/duplexify": "^3.6.0", "@types/long": "^4.0.0", "arrify": "^2.0.0", "extend": "^3.0.2", - "google-auth-library": "^6.1.2", - "google-gax": "^2.9.2", + "google-auth-library": "^7.0.0", + "google-gax": "2.28.1", "is-stream-ended": "^0.1.4", "lodash.snakecase": "^4.1.1", "p-defer": "^3.0.0" - }, - "dependencies": { - "google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - } } }, "@google-cloud/storage": { @@ -15000,232 +14479,60 @@ "gcs-resumable-upload": "^3.1.0", "get-stream": "^6.0.0", "hash-stream-validation": "^0.2.2", - "mime": "^2.2.0", - "mime-types": "^2.0.8", - "onetime": "^5.1.0", - "p-limit": "^3.0.1", - "pumpify": "^2.0.0", - "snakeize": "^0.1.0", - "stream-events": "^1.0.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true, - "optional": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "optional": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } - } - }, - "@google/events": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@google/events/-/events-5.1.1.tgz", - "integrity": "sha512-97u6AUfEXo6TxoBAdbziuhSL56+l69WzFahR6eTQE/bSjGPqT1+W4vS7eKaR7r60pGFrZZfqdFZ99uMbns3qgA==", - "dev": true - }, - "@grpc/grpc-js": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.8.tgz", - "integrity": "sha512-64hg5rmEm6F/NvlWERhHmmgxbWU8nD2TMWE+9TvG7/WcOrFT3fzg/Uu631pXRFwmJ4aWO/kp9vVSlr8FUjBDLA==", - "requires": { - "@grpc/proto-loader": "^0.6.0-pre14", - "@types/node": "^12.12.47", - "google-auth-library": "^6.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "@grpc/proto-loader": { - "version": "0.6.0-pre9", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.0-pre9.tgz", - "integrity": "sha512-oM+LjpEjNzW5pNJjt4/hq1HYayNeQT+eGrOPABJnYHv7TyNPDNzkQ76rDYZF86X5swJOa4EujEMzQ9iiTdPgww==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.9.0", - "yargs": "^15.3.1" - } - }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, - "@types/node": { - "version": "12.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.9.tgz", - "integrity": "sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "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==", - "requires": { - "color-name": "~1.1.4" - } - }, - "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==" - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "get-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "dev": true, + "optional": true }, - "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==", + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "optional": true, "requires": { - "p-limit": "^2.2.0" + "yocto-queue": "^0.1.0" } - }, - "protobufjs": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", - "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + } + } + }, + "@google/events": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@google/events/-/events-5.1.1.tgz", + "integrity": "sha512-97u6AUfEXo6TxoBAdbziuhSL56+l69WzFahR6eTQE/bSjGPqT1+W4vS7eKaR7r60pGFrZZfqdFZ99uMbns3qgA==", + "dev": true + }, + "@grpc/grpc-js": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.4.6.tgz", + "integrity": "sha512-Byau4xiXfIixb1PnW30V/P9mkrZ05lknyNqiK+cVY9J5hj3gecxd/anwaUbAM8j834zg1x78NvAbwGnMfWEu7A==", + "requires": { + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", "requires": { - "@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.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "13.13.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", - "integrity": "sha512-ctzZJ+XsmHQwe3xp07gFUq4JxBaRSYzKHPgblR76//UanGST7vfFNF0+ty5eEbgTqsENopzoDK090xlha9dccQ==" - } - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "requires": { - "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.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" } } } @@ -15234,6 +14541,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.1.tgz", "integrity": "sha512-3y0FhacYAwWvyXshH18eDkUI40wT/uGio7MAegzY8lO5+wVsc19+1A7T0pPptae4kl7bdITL+0cHpnAPmryBjQ==", + "dev": true, "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" @@ -15424,69 +14732,14 @@ } }, "@opentelemetry/api": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.11.0.tgz", - "integrity": "sha512-K+1ADLMxduhsXoZ0GRfi9Pw162FvzBQLDQlHru1lg86rpIU+4XqdJkSGo6y3Kg+GmOWq1HNHOA/ydw/rzHQkRg==", - "requires": { - "@opentelemetry/context-base": "^0.11.0" - } - }, - "@opentelemetry/context-base": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.11.0.tgz", - "integrity": "sha512-ESRk+572bftles7CVlugAj5Azrz61VO0MO0TS2pE9MLVL/zGmWuUBQryART6/nsrFqo+v9HPt37GPNcECTZR1w==" - }, - "@opentelemetry/core": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-0.11.0.tgz", - "integrity": "sha512-ZEKjBXeDGBqzouz0uJmrbEKNExEsQOhsZ3tJDCLcz5dUNoVw642oIn2LYWdQK2YdIfZbEmltiF65/csGsaBtFA==", - "requires": { - "@opentelemetry/api": "^0.11.0", - "@opentelemetry/context-base": "^0.11.0", - "semver": "^7.1.3" - }, - "dependencies": { - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@opentelemetry/resources": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-0.11.0.tgz", - "integrity": "sha512-o7DwV1TcezqBtS5YW2AWBcn01nVpPptIbTr966PLlVBcS//w8LkjeOShiSZxQ0lmV4b2en0FiSouSDoXk/5qIQ==", - "requires": { - "@opentelemetry/api": "^0.11.0", - "@opentelemetry/core": "^0.11.0" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz", + "integrity": "sha512-BuJuXRSJNQ3QoKA6GWWDyuLpOUck+9hAXNMCnrloc1aWVoy6Xq6t9PUV08aBZ4Lutqq2LEHM486bpZqoViScog==" }, "@opentelemetry/semantic-conventions": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.11.0.tgz", - "integrity": "sha512-xsthnI/J+Cx0YVDGgUzvrH0ZTtfNtl866M454NarYwDrc0JvC24sYw+XS5PJyk2KDzAHtb0vlrumUc1OAut/Fw==" - }, - "@opentelemetry/tracing": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/tracing/-/tracing-0.11.0.tgz", - "integrity": "sha512-QweFmxzl32BcyzwdWCNjVXZT1WeENNS/RWETq/ohqu+fAsTcMyGcr6cOq/yDdFmtBy+bm5WVVdeByEjNS+c4/w==", - "requires": { - "@opentelemetry/api": "^0.11.0", - "@opentelemetry/context-base": "^0.11.0", - "@opentelemetry/core": "^0.11.0", - "@opentelemetry/resources": "^0.11.0", - "@opentelemetry/semantic-conventions": "^0.11.0" - } - }, - "@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", - "dev": true + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.24.0.tgz", + "integrity": "sha512-a/szuMQV0Quy0/M7kKdglcbRSoorleyyOwbTNNJ32O+RBN766wbQlMTvdimImTmwYWGr+NJOni1EcC242WlRcA==" }, "@protobufjs/aspromise": { "version": "1.1.2", @@ -15752,16 +15005,6 @@ "@types/serve-static": "*" } }, - "@types/express-jwt": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", - "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", - "dev": true, - "requires": { - "@types/express": "*", - "@types/express-unless": "*" - } - }, "@types/express-serve-static-core": { "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", @@ -15773,15 +15016,6 @@ "@types/range-parser": "*" } }, - "@types/express-unless": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.2.tgz", - "integrity": "sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, "@types/fs-extra": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", @@ -15840,9 +15074,9 @@ "dev": true }, "@types/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", - "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/marked": { "version": "4.0.1", @@ -15887,9 +15121,9 @@ "dev": true }, "@types/minipass": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-2.2.0.tgz", - "integrity": "sha512-wuzZksN4w4kyfoOv/dlpov4NOunwutLA/q7uc00xU02ZyUY+aoM5PWIXEKBMnm0NHd4a+N71BMjq+x7+2Af1fg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-IKmcvG5RnNUtRoxSsusfYnd7fPl8NCLjLutRDvpqwWUR55XvGfy6GIGQUSsKgT2A8qzMjsWfHZNU7d6gxFgqzQ==", "dev": true, "requires": { "@types/node": "*" @@ -15911,9 +15145,9 @@ } }, "@types/node": { - "version": "10.17.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz", - "integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA==" + "version": "12.20.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.39.tgz", + "integrity": "sha512-U7PMwkDmc3bnL0e4U8oA0POpi1vfsYDc+DEUS2+rPxm9NlLcW1dBa5JcRhO633PoPUcCSWMNXrMsqhmAVEo+IQ==" }, "@types/node-fetch": { "version": "2.5.7", @@ -16089,9 +15323,9 @@ } }, "@types/tar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/tar/-/tar-4.0.3.tgz", - "integrity": "sha512-Z7AVMMlkI8NTWF0qGhC4QIX0zkV/+y0J8x7b/RsHrN0310+YNjoJd8UrApCiGBCWtKjxS9QhNqLi2UJNToh5hA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.1.tgz", + "integrity": "sha512-8mto3YZfVpqB1CHMaYz1TUYIQfZFbh/QbEq5Hsn6D0ilCfqRVCdalmc89B7vi3jhl9UYIk+dWDABShNfOkv5HA==", "dev": true, "requires": { "@types/minipass": "*", @@ -16479,6 +15713,23 @@ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "requires": { "string-width": "^4.1.0" + }, + "dependencies": { + "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==" + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "ansi-colors": { @@ -16740,21 +15991,13 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atlassian-openapi": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.15.tgz", - "integrity": "sha512-HzgdBHJ/9jZWZfass5DRJNG4vLxoFl6Zcl3B+8Cp2VSpEH7t0laBGnGtcthvj2h73hq8dzjKtVlG30agBZ4OPw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.13.tgz", + "integrity": "sha512-2/CRqPWZ15BBr9s6/c48QaBKvpxbonTeFGGXKFSmcSVFqH0KfFMWkgMSnCWKyAQ7gZ8Ch9BrqCDAG7ENzFWX2A==", "dev": true, "requires": { - "jsonpointer": "^5.0.0", + "jsonpointer": "^4.0.1", "urijs": "^1.18.10" - }, - "dependencies": { - "jsonpointer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", - "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", - "dev": true - } } }, "aws-sign2": { @@ -16926,6 +16169,21 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "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==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -17228,11 +16486,27 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "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==" + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } } }, "clone": { @@ -17658,7 +16932,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decamelize-keys": { "version": "1.1.0", @@ -18003,8 +17278,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-goat": { "version": "2.1.1", @@ -18676,9 +17950,9 @@ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, "fast-text-encoding": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", - "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" }, "fast-url-parser": { "version": "1.1.3", @@ -18819,6 +18093,40 @@ "@firebase/util": "0.3.2" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/database": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "dev": true, + "requires": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "faye-websocket": "0.11.3", + "tslib": "^1.11.1" + } + }, + "@firebase/database-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "dev": true, + "requires": { + "@firebase/app-types": "0.6.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -18831,49 +18139,33 @@ } }, "firebase-admin": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", - "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.4.2.tgz", + "integrity": "sha512-mRnBJbW6BAz6DJkZ0GOUTkmnmCrwVzMreMc6O+RXWukFydOzi5Xr6TKSiPKxoOQw41r9IluP2AZ3Qzvlx2SR+g==", "dev": true, "requires": { - "@firebase/database-compat": "^0.1.1", - "@firebase/database-types": "^0.7.2", + "@firebase/database": "^0.8.1", + "@firebase/database-types": "^0.6.1", "@google-cloud/firestore": "^4.5.0", "@google-cloud/storage": "^5.3.0", - "@types/node": ">=12.12.47", + "@types/node": "^10.10.0", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", "node-forge": "^0.10.0" }, "dependencies": { - "@firebase/app-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", - "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==", - "dev": true - }, - "@firebase/database-types": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", - "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", - "dev": true, - "requires": { - "@firebase/app-types": "0.6.3" - } - }, "@types/node": { - "version": "17.0.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", - "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", "dev": true } } }, "firebase-functions": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.16.0.tgz", - "integrity": "sha512-6ISOn0JckMtpA3aJ/+wCCGhThUhBUrpZD+tSkUeolx0Vr+NoYFXA0+2YzJZa/A2MDU8gotPzUtnauLSEQvfClQ==", + "version": "3.15.7", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.15.7.tgz", + "integrity": "sha512-ZD7r8eoWWebgs+mTqfH8NLUT2C0f7/cyAvIA1RSUdBVQZN7MBBt3oSlN/rL3e+m6tdlJz6YbQ3hrOKOGjOVYvQ==", "dev": true, "requires": { "@types/cors": "^2.8.5", @@ -19024,11 +18316,11 @@ } }, "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "requires": { - "minipass": "^2.6.0" + "minipass": "^3.0.0" } }, "fs.realpath": { @@ -19502,108 +18794,49 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", "dev": true - } - } - }, - "google-gax": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.9.2.tgz", - "integrity": "sha512-Pve4osEzNKpBZqFXMfGKBbKCtgnHpUe5IQMh5Ou+Xtg8nLcba94L3gF0xgM5phMdGRRqJn0SMjcuEVmOYu7EBg==", - "requires": { - "@grpc/grpc-js": "~1.1.1", - "@grpc/proto-loader": "^0.5.1", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^6.1.3", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "protobufjs": "^6.9.0", - "retry-request": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "13.13.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", - "integrity": "sha512-ctzZJ+XsmHQwe3xp07gFUq4JxBaRSYzKHPgblR76//UanGST7vfFNF0+ty5eEbgTqsENopzoDK090xlha9dccQ==" - }, - "google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "protobufjs": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", - "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + } + } + }, + "google-gax": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.28.1.tgz", + "integrity": "sha512-2Xjd3FrjlVd6Cmw2B2Aicpc/q92SwTpIOvxPUlnRg9w+Do8nu7UR+eQrgoKlo2FIUcUuDTvppvcx8toND0pK9g==", + "requires": { + "@grpc/grpc-js": "~1.4.0", + "@grpc/proto-loader": "^0.6.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.6.1", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^2.1.1", + "proto3-json-serializer": "^0.1.5", + "protobufjs": "6.11.2", + "retry-request": "^4.0.0" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", "requires": { - "@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.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - }, - "dependencies": { - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - } + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" } } } }, "google-p12-pem": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", - "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", "requires": { - "node-forge": "^1.0.0" - }, - "dependencies": { - "node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==" - } + "node-forge": "^0.10.0" } }, "got": { @@ -19994,6 +19227,21 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "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==" + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -20058,9 +19306,10 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "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==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "optional": true }, "is-glob": { "version": "4.0.3", @@ -20320,15 +19569,6 @@ "valid-url": "^1" } }, - "jose": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", - "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", - "dev": true, - "requires": { - "@panva/asn1.js": "^1.0.0" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -20536,36 +19776,6 @@ "safe-buffer": "^5.0.1" } }, - "jwks-rsa": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", - "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", - "dev": true, - "requires": { - "@types/express-jwt": "0.0.42", - "debug": "^4.3.2", - "jose": "^2.0.5", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" - }, - "dependencies": { - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -20643,12 +19853,6 @@ "type-check": "~0.3.2" } }, - "limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", - "dev": true - }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -20697,12 +19901,6 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -20919,34 +20117,6 @@ "yallist": "^4.0.0" } }, - "lru-memoizer": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", - "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", - "dev": true, - "requires": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", - "dev": true, - "requires": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -21156,27 +20326,20 @@ } }, "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", + "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } + "yallist": "^4.0.0" } }, "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "requires": { - "minipass": "^2.9.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" } }, "mkdirp": { @@ -21476,8 +20639,7 @@ "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, "node-gyp": { "version": "7.1.2", @@ -21497,52 +20659,12 @@ "which": "^2.0.2" }, "dependencies": { - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "optional": true - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "optional": true, - "requires": { - "minipass": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "optional": true }, - "minipass": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", - "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", - "optional": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "optional": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -21594,20 +20716,6 @@ "lru-cache": "^6.0.0" } }, - "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "optional": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - } - }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -21777,6 +20885,12 @@ "path-exists": "^4.0.0" } }, + "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==", + "dev": true + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -21801,6 +20915,17 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -21944,6 +21069,11 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -22098,6 +21228,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -22134,7 +21265,8 @@ "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==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "pac-proxy-agent": { "version": "5.0.0", @@ -22236,7 +21368,8 @@ "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==" + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -22409,10 +21542,18 @@ "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, + "proto3-json-serializer": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.6.tgz", + "integrity": "sha512-tGbV6m6Kad8NqxMh5hw87euPS0YoZSAOIfvR01zYkQV8Gpx1V/8yU/0gCKCvfCkhAJsjvzzhnnsdQxA1w7PSog==", + "requires": { + "protobufjs": "^6.11.2" + } + }, "protobufjs": { - "version": "6.8.8", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", - "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -22424,9 +21565,16 @@ "@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", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" + } } }, "proxy": { @@ -22514,12 +21662,6 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "psl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", @@ -22866,7 +22008,8 @@ "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "resolve": { "version": "1.17.0", @@ -23082,7 +22225,8 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "devOptional": true }, "setimmediate": { "version": "1.0.5", @@ -23459,13 +22603,30 @@ } }, "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==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "optional": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "optional": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "optional": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "strip-ansi": { @@ -23817,6 +22978,12 @@ "path-exists": "^4.0.0" } }, + "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==", + "dev": true + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -23835,6 +23002,17 @@ "p-limit": "^2.2.0" } }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -23878,33 +23056,27 @@ } }, "tar": { - "version": "4.4.18", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.18.tgz", - "integrity": "sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "requires": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" }, "dependencies": { "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "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==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" } } }, @@ -24434,6 +23606,11 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" }, + "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==" + }, "is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -24456,6 +23633,16 @@ "lru-cache": "^6.0.0" } }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -24596,7 +23783,8 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "wide-align": { "version": "1.1.5", @@ -24613,6 +23801,23 @@ "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "requires": { "string-width": "^4.0.0" + }, + "dependencies": { + "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==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + } } }, "winston": { @@ -24697,6 +23902,21 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "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==" + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } } } }, @@ -24741,7 +23961,8 @@ "y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true }, "yallist": { "version": "4.0.0", @@ -24757,7 +23978,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -24768,19 +23988,32 @@ "yargs-parser": "^20.2.2" }, "dependencies": { + "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==" + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" } } }, "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" }, "yargs-unparser": { "version": "2.0.0", diff --git a/package.json b/package.json index b27f2e223b3..4cc66b55851 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ ] }, "dependencies": { - "@google-cloud/pubsub": "^2.7.0", + "@google-cloud/pubsub": "^2.18.4", "abort-controller": "^3.0.0", "ajv": "^6.12.6", "archiver": "^5.0.0", @@ -130,7 +130,7 @@ "stream-chain": "^2.2.4", "stream-json": "^1.7.3", "superstatic": "^7.1.0", - "tar": "^4.3.0", + "tar": "^6.1.11", "tcp-port-used": "^1.0.1", "tmp": "0.0.33", "triple-beam": "^1.3.0", @@ -169,7 +169,7 @@ "@types/marked-terminal": "^3.1.3", "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", - "@types/node": "^10.17.50", + "@types/node": "^12.20.39", "@types/node-fetch": "^2.5.7", "@types/progress": "^2.0.3", "@types/puppeteer": "^5.4.2", @@ -180,7 +180,7 @@ "@types/sinon-chai": "^3.2.2", "@types/stream-json": "^1.7.2", "@types/supertest": "^2.0.6", - "@types/tar": "^4.0.0", + "@types/tar": "^6.1.1", "@types/tcp-port-used": "^1.0.0", "@types/tmp": "^0.1.0", "@types/triple-beam": "^1.3.0", diff --git a/scripts/integration-helpers/cli.ts b/scripts/integration-helpers/cli.ts index bd75ffca280..d7cd16ebf12 100644 --- a/scripts/integration-helpers/cli.ts +++ b/scripts/integration-helpers/cli.ts @@ -23,11 +23,11 @@ export class CLIProcess { } this.process = p; - this.process.stdout.on("data", (data: unknown) => { + this.process.stdout?.on("data", (data: unknown) => { process.stdout.write(`[${this.name} stdout] ` + data); }); - this.process.stderr.on("data", (data: unknown) => { + this.process.stderr?.on("data", (data: unknown) => { console.log(`[${this.name} stderr] ` + data); }); diff --git a/scripts/integration-helpers/framework.ts b/scripts/integration-helpers/framework.ts index 6afdda54c12..474d0a3af2c 100644 --- a/scripts/integration-helpers/framework.ts +++ b/scripts/integration-helpers/framework.ts @@ -162,7 +162,7 @@ export class TriggerEndToEndTest { return data.includes(ALL_EMULATORS_STARTED_LOG); }); - cli.process?.stdout.on("data", (data) => { + cli.process?.stdout?.on("data", (data) => { /* Functions V1 */ if (data.includes(RTDB_FUNCTION_LOG)) { this.rtdbTriggerCount++; diff --git a/src/deploy/functions/runtimes/golang/index.ts b/src/deploy/functions/runtimes/golang/index.ts index 968dc881be0..77ef1f2864f 100644 --- a/src/deploy/functions/runtimes/golang/index.ts +++ b/src/deploy/functions/runtimes/golang/index.ts @@ -120,7 +120,7 @@ export class Delegate { cwd: this.sourceDir, stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "inherit"], }); - childProcess.stdout.on("data", (chunk) => { + childProcess.stdout?.on("data", (chunk) => { logger.debug(chunk.toString()); }); return Promise.resolve(async () => { diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 9be90d7a8d5..476d1701812 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -325,11 +325,11 @@ async function _runBinary( `${description} logging to ${clc.bold(getLogFileName(emulator.name))}` ); - emulator.instance.stdout.on("data", (data) => { + emulator.instance.stdout?.on("data", (data) => { logger.log("DEBUG", data.toString()); emulator.stdout.write(data); }); - emulator.instance.stderr.on("data", (data) => { + emulator.instance.stderr?.on("data", (data) => { logger.log("DEBUG", data.toString()); emulator.stdout.write(data); diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index e0fc7a23cd4..95343b35eb4 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -107,7 +107,7 @@ export interface FunctionsRuntimeInstance { // A function to manually kill the child process as normal cleanup shutdown(): void; // A function to manually kill the child process in case of errors - kill(signal?: string): void; + kill(signal?: number): void; // Send an IPC message to the child process send(args: FunctionsRuntimeArgs): boolean; } @@ -1080,6 +1080,13 @@ export class FunctionsEmulator implements EmulatorInstance { stdio: ["pipe", "pipe", "pipe", "ipc"], }); + if (!childProcess.stderr) { + throw new FirebaseError(`childProcess.stderr is undefined.`); + } + if (!childProcess.stdout) { + throw new FirebaseError(`childProcess.stdout is undefined.`); + } + const buffers: { [pipe: string]: { pipe: stream.Readable; @@ -1113,7 +1120,7 @@ export class FunctionsEmulator implements EmulatorInstance { shutdown: () => { childProcess.kill(); }, - kill: (signal?: string) => { + kill: (signal?: number) => { childProcess.kill(signal); emitter.emit("log", new EmulatorLog("SYSTEM", "runtime-status", "killed")); }, diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index abb6e592254..a7c51aab7b5 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -638,10 +638,13 @@ async function initializeFirebaseAdminStubs(frb: FunctionsRuntimeBundle): Promis .finalize(); // Stub the admin module in the require cache - require.cache[adminResolution.resolution] = { - exports: proxiedAdminModule, - path: path.dirname(adminResolution.resolution), - }; + require.cache[adminResolution.resolution] = Object.assign( + require.cache[adminResolution.resolution], + { + exports: proxiedAdminModule, + path: path.dirname(adminResolution.resolution), + } + ); logDebug("firebase-admin has been stubbed.", { adminResolution, @@ -735,10 +738,13 @@ async function initializeFunctionsConfigHelper(frb: FunctionsRuntimeBundle): Pro .finalize(); // Stub the functions module in the require cache - require.cache[functionsResolution.resolution] = { - exports: proxiedFunctionsModule, - path: path.dirname(functionsResolution.resolution), - }; + require.cache[functionsResolution.resolution] = Object.assign( + require.cache[functionsResolution.resolution], + { + exports: proxiedFunctionsModule, + path: path.dirname(functionsResolution.resolution), + } + ); logDebug("firebase-functions has been stubbed.", { functionsResolution, diff --git a/src/emulator/storage/rules/runtime.ts b/src/emulator/storage/rules/runtime.ts index f8f00da3aa2..37016d065d4 100644 --- a/src/emulator/storage/rules/runtime.ts +++ b/src/emulator/storage/rules/runtime.ts @@ -152,7 +152,7 @@ export class StorageRulesRuntime { }); // This catches errors from the java process (i.e. missing jar file) - this._childprocess.stderr.on("data", (buf: Buffer) => { + this._childprocess.stderr?.on("data", (buf: Buffer) => { const error = buf.toString(); if (error.includes("jarfile")) { throw new FirebaseError( @@ -166,7 +166,7 @@ export class StorageRulesRuntime { } }); - this._childprocess.stdout.on("data", (buf: Buffer) => { + this._childprocess.stdout?.on("data", (buf: Buffer) => { const serializedRuntimeActionResponse = buf.toString("UTF8").trim(); if (serializedRuntimeActionResponse != "") { let rap; @@ -225,7 +225,7 @@ export class StorageRulesRuntime { }; const serializedRequest = JSON.stringify(runtimeActionRequest); - this._childprocess?.stdin.write(serializedRequest + "\n"); + this._childprocess?.stdin?.write(serializedRequest + "\n"); }); } diff --git a/src/hosting/proxy.ts b/src/hosting/proxy.ts index b51e1448557..cecd4620558 100644 --- a/src/hosting/proxy.ts +++ b/src/hosting/proxy.ts @@ -40,7 +40,7 @@ function makeVary(vary: string | null = ""): string { * the Firebase Hosting origin. */ export function proxyRequestHandler(url: string, rewriteIdentifier: string): RequestHandler { - return async (req: IncomingMessage, res: ServerResponse, next: () => void): Promise => { + return async (req: IncomingMessage, res: ServerResponse, next: () => void): Promise => { logger.info(`[hosting] Rewriting ${req.url} to ${url} for ${rewriteIdentifier}`); // Extract the __session cookie from headers to forward it to the // functions cookie is not a string[]. diff --git a/src/test/apiv2.spec.ts b/src/test/apiv2.spec.ts index 3510e056742..add4299c9d8 100644 --- a/src/test/apiv2.spec.ts +++ b/src/test/apiv2.spec.ts @@ -354,11 +354,11 @@ describe("apiv2", () => { res.end(JSON.stringify({ proxied: true })); }); await Promise.all([ - new Promise((resolve) => { - proxyServer.listen(52672, resolve); + new Promise((resolve) => { + proxyServer.listen(52672, () => resolve()); }), - new Promise((resolve) => { - targetServer.listen(52673, resolve); + new Promise((resolve) => { + targetServer.listen(52673, () => resolve()); }), ]); }); diff --git a/src/test/emulators/functionsRuntimeWorker.spec.ts b/src/test/emulators/functionsRuntimeWorker.spec.ts index 759e4baf2d0..edc7c7b68ee 100644 --- a/src/test/emulators/functionsRuntimeWorker.spec.ts +++ b/src/test/emulators/functionsRuntimeWorker.spec.ts @@ -32,7 +32,7 @@ class MockRuntimeInstance implements FunctionsRuntimeInstance { this.events.emit("exit", { reason: "shutdown" }); } - kill(signal?: string): void { + kill(signal?: number): void { this.events.emit("exit", { reason: "kill" }); } From 2559abb6b8029450149caf1700f4a1f41235d376 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 20 Jan 2022 15:55:40 -0600 Subject: [PATCH 0036/1699] replace api client with apiv2 client (#3993) --- src/accountExporter.js | 18 +- src/accountImporter.js | 19 +- src/defaultCredentials.ts | 6 +- src/functionsConfig.ts | 12 +- src/gcp/rules.ts | 84 +++---- src/prepareFirebaseRules.js | 66 ------ ...porter.spec.js => accountExporter.spec.ts} | 207 +++++++++--------- src/test/accountImporter.spec.js | 193 ---------------- src/test/accountImporter.spec.ts | 176 +++++++++++++++ 9 files changed, 346 insertions(+), 435 deletions(-) delete mode 100644 src/prepareFirebaseRules.js rename src/test/{accountExporter.spec.js => accountExporter.spec.ts} (56%) delete mode 100644 src/test/accountImporter.spec.js create mode 100644 src/test/accountImporter.spec.ts diff --git a/src/accountExporter.js b/src/accountExporter.js index 1406df9feb1..548e0ab19e1 100644 --- a/src/accountExporter.js +++ b/src/accountExporter.js @@ -4,9 +4,14 @@ var os = require("os"); var path = require("path"); var _ = require("lodash"); -var api = require("./api"); -var utils = require("./utils"); +const { Client } = require("./apiv2"); +const { googleOrigin } = require("./api"); var { FirebaseError } = require("./error"); +var utils = require("./utils"); + +const apiClient = new Client({ + urlPrefix: googleOrigin, +}); // TODO: support for MFA at runtime was added in PR #3173, but this exporter currently ignores `mfaInfo` and loses the data on export. var EXPORTED_JSON_KEYS = [ @@ -161,12 +166,9 @@ var serialExportUsers = function (projectId, options) { if (!options.timeoutRetryCount) { options.timeoutRetryCount = 0; } - return api - .request("POST", "/identitytoolkit/v3/relyingparty/downloadAccount", { - auth: true, - json: true, - data: postBody, - origin: api.googleOrigin, + return apiClient + .post("/identitytoolkit/v3/relyingparty/downloadAccount", postBody, { + skipLog: { resBody: true }, // This contains a lot of PII - don't log it. }) .then(function (ret) { options.timeoutRetryCount = 0; diff --git a/src/accountImporter.js b/src/accountImporter.js index f25fb3cd932..64b3dc571ab 100644 --- a/src/accountImporter.js +++ b/src/accountImporter.js @@ -3,10 +3,15 @@ var clc = require("cli-color"); var _ = require("lodash"); -var api = require("./api"); +const { Client } = require("./apiv2"); +const { googleOrigin } = require("./api"); const { logger } = require("./logger"); -var utils = require("./utils"); var { FirebaseError } = require("./error"); +var utils = require("./utils"); + +const apiClient = new Client({ + urlPrefix: googleOrigin, +}); // TODO: support for MFA at runtime was added in PR #3173, but this importer currently ignores `mfaInfo` and loses the data on import. var ALLOWED_JSON_KEYS = [ @@ -302,12 +307,10 @@ var validateUserJson = function (userJson) { var _sendRequest = function (projectId, userList, hashOptions) { logger.info("Starting importing " + userList.length + " account(s)."); - return api - .request("POST", "/identitytoolkit/v3/relyingparty/uploadAccount", { - auth: true, - json: true, - data: _genUploadAccountPostBody(projectId, userList, hashOptions), - origin: api.googleOrigin, + const postData = _genUploadAccountPostBody(projectId, userList, hashOptions); + return apiClient + .post("/identitytoolkit/v3/relyingparty/uploadAccount", postData, { + skipLog: { body: true }, // Contains a lot of PII - don't log. }) .then(function (ret) { if (ret.body.error) { diff --git a/src/defaultCredentials.ts b/src/defaultCredentials.ts index e06dcba3321..74b67b17bf1 100644 --- a/src/defaultCredentials.ts +++ b/src/defaultCredentials.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import * as api from "./api"; +import { clientId, clientSecret } from "./api"; import { Tokens, User, Account } from "./auth"; import { logger } from "./logger"; @@ -63,8 +63,8 @@ export function clearCredentials(account: Account): void { function getCredential(tokens: Tokens): RefreshTokenCredential | undefined { if (tokens.refresh_token) { return { - client_id: api.clientId, - client_secret: api.clientSecret, + client_id: clientId, + client_secret: clientSecret, refresh_token: tokens.refresh_token, type: "authorized_user", }; diff --git a/src/functionsConfig.ts b/src/functionsConfig.ts index 03fa41e1858..d005abf0c7a 100644 --- a/src/functionsConfig.ts +++ b/src/functionsConfig.ts @@ -1,7 +1,8 @@ import * as _ from "lodash"; import * as clc from "cli-color"; -import * as api from "./api"; +import { firebaseApiOrigin } from "./api"; +import { Client } from "./apiv2"; import { ensure as ensureApiEnabled } from "./ensureApiEnabled"; import { FirebaseError } from "./error"; import { needProjectId } from "./projectUtils"; @@ -10,6 +11,8 @@ import * as args from "./deploy/functions/args"; export const RESERVED_NAMESPACES = ["firebase"]; +const apiClient = new Client({ urlPrefix: firebaseApiOrigin }); + interface Id { config: string; variable: string; @@ -70,10 +73,9 @@ export function getAppEngineLocation(config: any): string { export async function getFirebaseConfig(options: any): Promise { const projectId = needProjectId(options); - const response = await api.request("GET", "/v1beta1/projects/" + projectId + "/adminSdkConfig", { - auth: true, - origin: api.firebaseApiOrigin, - }); + const response = await apiClient.get( + `/v1beta1/projects/${projectId}/adminSdkConfig` + ); return response.body; } diff --git a/src/gcp/rules.ts b/src/gcp/rules.ts index 0a2bf466385..01ef686baef 100644 --- a/src/gcp/rules.ts +++ b/src/gcp/rules.ts @@ -1,11 +1,14 @@ import * as _ from "lodash"; -import * as api from "../api"; +import { rulesOrigin } from "../api"; +import { Client } from "../apiv2"; import { logger } from "../logger"; import * as utils from "../utils"; const API_VERSION = "v1"; +const apiClient = new Client({ urlPrefix: rulesOrigin, apiVersion: API_VERSION }); + function _handleErrorResponse(response: any): any { if (response.body && response.body.error) { return utils.reject(response.body.error, { code: 2 }); @@ -21,7 +24,7 @@ function _handleErrorResponse(response: any): any { * Gets the latest ruleset name on the project. * @param projectId Project from which you want to get the ruleset. * @param service Service for the ruleset (ex: cloud.firestore or firebase.storage). - * @returns Name of the latest ruleset. + * @return Name of the latest ruleset. */ export async function getLatestRulesetName( projectId: string, @@ -29,7 +32,7 @@ export async function getLatestRulesetName( ): Promise { const releases = await listAllReleases(projectId); const prefix = `projects/${projectId}/releases/${service}`; - const release = _.find(releases, (r) => r.name.indexOf(prefix) === 0); + const release = _.find(releases, (r) => r.name.startsWith(prefix)); if (!release) { return null; @@ -44,12 +47,10 @@ const MAX_RELEASES_PAGE_SIZE = 10; */ export async function listReleases( projectId: string, - pageToken?: string + pageToken = "" ): Promise { - const response = await api.request("GET", `/${API_VERSION}/projects/${projectId}/releases`, { - auth: true, - origin: api.rulesOrigin, - query: { + const response = await apiClient.get(`/projects/${projectId}/releases`, { + queryParams: { pageSize: MAX_RELEASES_PAGE_SIZE, pageToken, }, @@ -105,12 +106,11 @@ export interface RulesetSource { * @return Array of files in the ruleset. Each entry has form { content, name }. */ export async function getRulesetContent(name: string): Promise { - const response = await api.request("GET", `/${API_VERSION}/${name}`, { - auth: true, - origin: api.rulesOrigin, + const response = await apiClient.get<{ source: RulesetSource }>(`/${name}`, { + skipLog: { resBody: true }, }); if (response.status === 200) { - const source: RulesetSource = response.body.source; + const source = response.body.source; return source.files; } @@ -124,15 +124,14 @@ const MAX_RULESET_PAGE_SIZE = 100; */ export async function listRulesets( projectId: string, - pageToken?: string + pageToken: string = "" ): Promise { - const response = await api.request("GET", `/${API_VERSION}/projects/${projectId}/rulesets`, { - auth: true, - origin: api.rulesOrigin, - query: { + const response = await apiClient.get(`/projects/${projectId}/rulesets`, { + queryParams: { pageSize: MAX_RULESET_PAGE_SIZE, pageToken, }, + skipLog: { resBody: true }, }); if (response.status === 200) { return response.body; @@ -178,14 +177,7 @@ export function getRulesetId(ruleset: ListRulesetsEntry): string { * by a release, the operation will fail. */ export async function deleteRuleset(projectId: string, id: string): Promise { - const response = await api.request( - "DELETE", - `/${API_VERSION}/projects/${projectId}/rulesets/${id}`, - { - auth: true, - origin: api.rulesOrigin, - } - ); + const response = await apiClient.delete(`/projects/${projectId}/rulesets/${id}`); if (response.status === 200) { return; } @@ -200,11 +192,11 @@ export async function deleteRuleset(projectId: string, id: string): Promise { const payload = { source: { files } }; - const response = await api.request("POST", `/${API_VERSION}/projects/${projectId}/rulesets`, { - auth: true, - data: payload, - origin: api.rulesOrigin, - }); + const response = await apiClient.post( + `/projects/${projectId}/rulesets`, + payload, + { skipLog: { body: true } } + ); if (response.status === 200) { logger.debug("[rules] created ruleset", response.body.name); return response.body.name; @@ -229,11 +221,10 @@ export async function createRelease( rulesetName, }; - const response = await api.request("POST", `/${API_VERSION}/projects/${projectId}/releases`, { - auth: true, - data: payload, - origin: api.rulesOrigin, - }); + const response = await apiClient.post( + `/projects/${projectId}/releases`, + payload + ); if (response.status === 200) { logger.debug("[rules] created release", response.body.name); return response.body.name; @@ -260,14 +251,9 @@ export async function updateRelease( }, }; - const response = await api.request( - "PATCH", - `/${API_VERSION}/projects/${projectId}/releases/${releaseName}`, - { - auth: true, - data: payload, - origin: api.rulesOrigin, - } + const response = await apiClient.patch( + `/projects/${projectId}/releases/${releaseName}`, + payload ); if (response.status === 200) { logger.debug("[rules] updated release", response.body.name); @@ -290,11 +276,9 @@ export async function updateOrCreateRelease( } export function testRuleset(projectId: string, files: RulesetFile[]): Promise { - return api.request("POST", `/${API_VERSION}/projects/${encodeURIComponent(projectId)}:test`, { - origin: api.rulesOrigin, - data: { - source: { files }, - }, - auth: true, - }); + return apiClient.post( + `/projects/${encodeURIComponent(projectId)}:test`, + { source: { files } }, + { skipLog: { body: true } } + ); } diff --git a/src/prepareFirebaseRules.js b/src/prepareFirebaseRules.js deleted file mode 100644 index 20f7ffd160c..00000000000 --- a/src/prepareFirebaseRules.js +++ /dev/null @@ -1,66 +0,0 @@ -"use strict"; - -var clc = require("cli-color"); -var fs = require("fs"); - -var api = require("./api"); -var utils = require("./utils"); - -var prepareFirebaseRules = function (component, options, payload) { - var rulesFileName = component + ".rules"; - var rulesPath = options.config.get(rulesFileName); - if (rulesPath) { - rulesPath = options.config.path(rulesPath); - var src = fs.readFileSync(rulesPath, "utf8"); - utils.logBullet(clc.bold.cyan(component + ":") + " checking rules for compilation errors..."); - return api - .request("POST", "/v1/projects/" + encodeURIComponent(options.project) + ":test", { - origin: api.rulesOrigin, - data: { - source: { - files: [ - { - content: src, - name: rulesFileName, - }, - ], - }, - }, - auth: true, - }) - .then(function (response) { - if (response.body && response.body.issues && response.body.issues.length > 0) { - var add = response.body.issues.length === 1 ? "" : "s"; - var message = - "Compilation error" + - add + - " in " + - clc.bold(options.config.get(rulesFileName)) + - ":\n"; - response.body.issues.forEach(function (issue) { - message += - "\n[" + - issue.severity.substring(0, 1) + - "] " + - issue.sourcePosition.line + - ":" + - issue.sourcePosition.column + - " - " + - issue.description; - }); - - return utils.reject(message, { exit: 1 }); - } - - utils.logSuccess(clc.bold.green(component + ":") + " rules file compiled successfully"); - payload[component] = { - rules: [{ name: options.config.get(rulesFileName), content: src }], - }; - return Promise.resolve(); - }); - } - - return Promise.resolve(); -}; - -module.exports = prepareFirebaseRules; diff --git a/src/test/accountExporter.spec.js b/src/test/accountExporter.spec.ts similarity index 56% rename from src/test/accountExporter.spec.js rename to src/test/accountExporter.spec.ts index ea18ee7edc7..6592cd4b884 100644 --- a/src/test/accountExporter.spec.js +++ b/src/test/accountExporter.spec.ts @@ -1,50 +1,54 @@ -"use strict"; +/* eslint-disable @typescript-eslint/no-empty-function */ +import { expect } from "chai"; +import * as nock from "nock"; +import * as os from "os"; +import * as sinon from "sinon"; -var chai = require("chai"); -var nock = require("nock"); -var os = require("os"); -var sinon = require("sinon"); +import * as accountExporter from "../accountExporter"; -var accountExporter = require("../accountExporter"); +describe("accountExporter", () => { + const validateOptions = accountExporter.validateOptions; + const serialExportUsers = accountExporter.serialExportUsers; -var expect = chai.expect; -describe("accountExporter", function () { - var validateOptions = accountExporter.validateOptions; - var serialExportUsers = accountExporter.serialExportUsers; - - describe("validateOptions", function () { - it("should reject when no format provided", function () { - return expect(() => validateOptions({}, "output_file")).to.throw; + describe("validateOptions", () => { + it("should reject when no format provided", () => { + expect(() => validateOptions({}, "output_file")).to.throw; }); - it("should reject when format is not csv or json", function () { - return expect(() => validateOptions({ format: "txt" }, "output_file")).to.throw; + it("should reject when format is not csv or json", () => { + expect(() => validateOptions({ format: "txt" }, "output_file")).to.throw; }); - it("should ignore format param when implicitly specified in file name", function () { - var ret = validateOptions({ format: "JSON" }, "output_file.csv"); + it("should ignore format param when implicitly specified in file name", () => { + const ret = validateOptions({ format: "JSON" }, "output_file.csv"); expect(ret.format).to.eq("csv"); }); - it("should use format param when not implicitly specified in file name", function () { - var ret = validateOptions({ format: "JSON" }, "output_file"); + it("should use format param when not implicitly specified in file name", () => { + const ret = validateOptions({ format: "JSON" }, "output_file"); expect(ret.format).to.eq("json"); }); }); - describe("serialExportUsers", function () { - var sandbox; - var userList = []; - var writeStream = { - write: function () {}, - end: function () {}, + describe("serialExportUsers", () => { + let sandbox: sinon.SinonSandbox; + let userList: { + localId: string; + email: string; + displayName: string; + disabled: boolean; + customAttributes?: string; + }[] = []; + const writeStream = { + write: () => {}, + end: () => {}, }; - var spyWrite; + let spyWrite: sinon.SinonSpy; - beforeEach(function () { + beforeEach(() => { sandbox = sinon.createSandbox(); spyWrite = sandbox.spy(writeStream, "write"); - for (var i = 0; i < 7; i++) { + for (let i = 0; i < 7; i++) { userList.push({ localId: i.toString(), email: "test" + i + "@test.org", @@ -54,13 +58,13 @@ describe("accountExporter", function () { } }); - afterEach(function () { + afterEach(() => { sandbox.restore(); nock.cleanAll(); userList = []; }); - it("should call api.request multiple times for JSON export", function () { + it("should call api.request multiple times for JSON export", async () => { nock("https://www.googleapis.com") .post("/identitytoolkit/v3/relyingparty/downloadAccount", { maxResults: 3, @@ -98,47 +102,47 @@ describe("accountExporter", function () { nextPageToken: "7", }); - return serialExportUsers("test-project-id", { + await serialExportUsers("test-project-id", { format: "JSON", batchSize: 3, writeStream: writeStream, - }).then(function () { - expect(spyWrite.callCount).to.eq(7); - expect(spyWrite.getCall(0).args[0]).to.eq(JSON.stringify(userList[0], null, 2)); - for (var j = 1; j < 7; j++) { - expect(spyWrite.getCall(j).args[0]).to.eq( - "," + os.EOL + JSON.stringify(userList[j], null, 2) - ); - } }); + expect(spyWrite.callCount).to.eq(7); + expect(spyWrite.getCall(0).args[0]).to.eq(JSON.stringify(userList[0], null, 2)); + for (let j = 1; j < 7; j++) { + expect(spyWrite.getCall(j).args[0]).to.eq( + "," + os.EOL + JSON.stringify(userList[j], null, 2) + ); + } + expect(nock.isDone()).to.be.true; }); - it("should call api.request multiple times for CSV export", function () { + it("should call api.request multiple times for CSV export", async () => { mockAllUsersRequests(); - return serialExportUsers("test-project-id", { + await serialExportUsers("test-project-id", { format: "csv", batchSize: 3, writeStream: writeStream, - }).then(function () { - expect(spyWrite.callCount).to.eq(userList.length); - for (var j = 0; j < userList.length; j++) { - var expectedEntry = - userList[j].localId + - "," + - userList[j].email + - ",false,,," + - userList[j].displayName + - Array(22).join(",") + // A lot of empty fields... - userList[j].disabled; - expect(spyWrite.getCall(j).args[0]).to.eq(expectedEntry + ",," + os.EOL); - } }); + expect(spyWrite.callCount).to.eq(userList.length); + for (let j = 0; j < userList.length; j++) { + const expectedEntry = + userList[j].localId + + "," + + userList[j].email + + ",false,,," + + userList[j].displayName + + Array(22).join(",") + // A lot of empty fields... + userList[j].disabled; + expect(spyWrite.getCall(j).args[0]).to.eq(expectedEntry + ",," + os.EOL); + } + expect(nock.isDone()).to.be.true; }); - it("should encapsulate displayNames with commas for csv formats", function () { + it("should encapsulate displayNames with commas for csv formats", async () => { // Initialize user with comma in display name. - var singleUser = { + const singleUser = { localId: "1", email: "test1@test.org", displayName: "John Tester1, CFA", @@ -163,60 +167,59 @@ describe("accountExporter", function () { nextPageToken: "1", }); - return serialExportUsers("test-project-id", { + await serialExportUsers("test-project-id", { format: "csv", batchSize: 1, writeStream: writeStream, - }).then(function () { - expect(spyWrite.callCount).to.eq(1); - var expectedEntry = - singleUser.localId + - "," + - singleUser.email + - ",false,,," + - '"' + - singleUser.displayName + - '"' + - Array(22).join(",") + // A lot of empty fields. - singleUser.disabled; - expect(spyWrite.getCall(0).args[0]).to.eq(expectedEntry + ",," + os.EOL); }); + expect(spyWrite.callCount).to.eq(1); + const expectedEntry = + singleUser.localId + + "," + + singleUser.email + + ",false,,," + + '"' + + singleUser.displayName + + '"' + + Array(22).join(",") + // A lot of empty fields. + singleUser.disabled; + expect(spyWrite.getCall(0).args[0]).to.eq(expectedEntry + ",," + os.EOL); + expect(nock.isDone()).to.be.true; }); - it("should not emit redundant comma in JSON on consecutive calls", function () { + it("should not emit redundant comma in JSON on consecutive calls", async () => { mockAllUsersRequests(); const correctString = '{\n "localId": "0",\n "email": "test0@test.org",\n "displayName": "John Tester0",\n "disabled": true\n}'; const firstWriteSpy = sinon.spy(); - return serialExportUsers("test-project-id", { + await serialExportUsers("test-project-id", { format: "JSON", batchSize: 3, - writeStream: { write: firstWriteSpy, end: function () {} }, - }).then(function () { - expect(firstWriteSpy.args[0][0]).to.be.eq( - correctString, - "The first call did not emit the correct string" - ); + writeStream: { write: firstWriteSpy, end: () => {} }, + }); + expect(firstWriteSpy.args[0][0]).to.be.eq( + correctString, + "The first call did not emit the correct string" + ); - mockAllUsersRequests(); + mockAllUsersRequests(); - const secondWriteSpy = sinon.spy(); - return serialExportUsers("test-project-id", { - format: "JSON", - batchSize: 3, - writeStream: { write: secondWriteSpy, end: function () {} }, - }).then(() => { - expect(secondWriteSpy.args[0][0]).to.be.eq( - correctString, - "The second call did not emit the correct string" - ); - }); + const secondWriteSpy = sinon.spy(); + await serialExportUsers("test-project-id", { + format: "JSON", + batchSize: 3, + writeStream: { write: secondWriteSpy, end: () => {} }, }); + expect(secondWriteSpy.args[0][0]).to.be.eq( + correctString, + "The second call did not emit the correct string" + ); + expect(nock.isDone()).to.be.true; }); - it("should export a user's custom attributes", function () { + it("should export a user's custom attributes", async () => { userList[0].customAttributes = '{ "customBoolean": true, "customString": "test", "customInt": 99 }'; userList[1].customAttributes = @@ -230,22 +233,22 @@ describe("accountExporter", function () { users: userList.slice(0, 3), nextPageToken: "3", }); - return serialExportUsers("test-project-id", { + await serialExportUsers("test-project-id", { format: "JSON", batchSize: 3, writeStream: writeStream, - }).then(function () { - expect(spyWrite.getCall(0).args[0]).to.eq(JSON.stringify(userList[0], null, 2)); - expect(spyWrite.getCall(1).args[0]).to.eq( - "," + os.EOL + JSON.stringify(userList[1], null, 2) - ); - expect(spyWrite.getCall(2).args[0]).to.eq( - "," + os.EOL + JSON.stringify(userList[2], null, 2) - ); }); + expect(spyWrite.getCall(0).args[0]).to.eq(JSON.stringify(userList[0], null, 2)); + expect(spyWrite.getCall(1).args[0]).to.eq( + "," + os.EOL + JSON.stringify(userList[1], null, 2) + ); + expect(spyWrite.getCall(2).args[0]).to.eq( + "," + os.EOL + JSON.stringify(userList[2], null, 2) + ); + expect(nock.isDone()).to.be.true; }); - function mockAllUsersRequests() { + function mockAllUsersRequests(): void { nock("https://www.googleapis.com") .post("/identitytoolkit/v3/relyingparty/downloadAccount", { maxResults: 3, diff --git a/src/test/accountImporter.spec.js b/src/test/accountImporter.spec.js deleted file mode 100644 index 4ce0e7fca17..00000000000 --- a/src/test/accountImporter.spec.js +++ /dev/null @@ -1,193 +0,0 @@ -"use strict"; - -var chai = require("chai"); -var sinon = require("sinon"); -var api = require("../api"); -var accountImporter = require("../accountImporter"); - -var expect = chai.expect; -describe("accountImporter", function () { - var transArrayToUser = accountImporter.transArrayToUser; - var validateOptions = accountImporter.validateOptions; - var validateUserJson = accountImporter.validateUserJson; - var serialImportUsers = accountImporter.serialImportUsers; - - describe("transArrayToUser", function () { - it("should reject when passwordHash is invalid base64", function () { - return expect(transArrayToUser(["123", undefined, undefined, "false"])).to.have.property( - "error" - ); - }); - - it("should not reject when passwordHash is valid base64", function () { - return expect( - transArrayToUser(["123", undefined, undefined, "Jlf7onfLbzqPNFP/1pqhx6fQF/w="]) - ).to.not.have.property("error"); - }); - }); - - describe("validateOptions", function () { - it("should reject when unsupported hash algorithm provided", function () { - return expect(() => validateOptions({ hashAlgo: "MD2" })).to.throw; - }); - - it("should reject when missing parameters", function () { - return expect(() => validateOptions({ hashAlgo: "HMAC_SHA1" })).to.throw; - }); - }); - - describe("validateUserJson", function () { - it("should reject when unknown fields in user json", function () { - return expect( - validateUserJson({ - uid: "123", - email: "test@test.org", - }) - ).to.have.property("error"); - }); - - it("should reject when unknown fields in providerUserInfo of user json", function () { - return expect( - validateUserJson({ - localId: "123", - email: "test@test.org", - providerUserInfo: [ - { - providerId: "google.com", - googleId: "abc", - email: "test@test.org", - }, - ], - }) - ).to.have.property("error"); - }); - - it("should reject when unknown providerUserInfo of user json", function () { - return expect( - validateUserJson({ - localId: "123", - email: "test@test.org", - providerUserInfo: [ - { - providerId: "otheridp.com", - rawId: "abc", - email: "test@test.org", - }, - ], - }) - ).to.have.property("error"); - }); - - it("should reject when passwordHash is invalid base64", function () { - return expect( - validateUserJson({ - localId: "123", - passwordHash: "false", - }) - ).to.have.property("error"); - }); - - it("should not reject when passwordHash is valid base64", function () { - return expect( - validateUserJson({ - localId: "123", - passwordHash: "Jlf7onfLbzqPNFP/1pqhx6fQF/w=", - }) - ).to.not.have.property("error"); - }); - }); - - describe("serialImportUsers", function () { - var sandbox; - var mockApi; - var batches = []; - var hashOptions = { - hashAlgo: "HMAC_SHA1", - hashKey: "a2V5MTIz", - }; - var expectedResponse = []; - - beforeEach(function () { - sandbox = sinon.createSandbox(); - mockApi = sandbox.mock(api); - for (var i = 0; i < 10; i++) { - batches.push([ - { - localId: i.toString(), - email: "test" + i + "@test.org", - }, - ]); - expectedResponse.push({ - status: 200, - response: "", - body: "", - }); - } - }); - - afterEach(function () { - mockApi.verify(); - sandbox.restore(); - batches = []; - expectedResponse = []; - }); - - it("should call api.request multiple times", function (done) { - for (var i = 0; i < batches.length; i++) { - mockApi - .expects("request") - .withArgs("POST", "/identitytoolkit/v3/relyingparty/uploadAccount", { - auth: true, - data: { - hashAlgorithm: "HMAC_SHA1", - signerKey: "a2V5MTIz", - targetProjectId: "test-project-id", - users: [{ email: "test" + i + "@test.org", localId: i.toString() }], - }, - json: true, - origin: "https://www.googleapis.com", - }) - .once() - .resolves(expectedResponse[i]); - } - return expect( - serialImportUsers("test-project-id", hashOptions, batches, 0) - ).to.eventually.notify(done); - }); - - it("should continue when some request's response is 200 but has `error` in response", function (done) { - expectedResponse[5] = { - status: 200, - response: "", - body: { - error: [ - { - index: 0, - message: "some error message", - }, - ], - }, - }; - for (var i = 0; i < batches.length; i++) { - mockApi - .expects("request") - .withArgs("POST", "/identitytoolkit/v3/relyingparty/uploadAccount", { - auth: true, - data: { - hashAlgorithm: "HMAC_SHA1", - signerKey: "a2V5MTIz", - targetProjectId: "test-project-id", - users: [{ email: "test" + i + "@test.org", localId: i.toString() }], - }, - json: true, - origin: "https://www.googleapis.com", - }) - .once() - .resolves(expectedResponse[i]); - } - return expect( - serialImportUsers("test-project-id", hashOptions, batches, 0) - ).to.eventually.notify(done); - }); - }); -}); diff --git a/src/test/accountImporter.spec.ts b/src/test/accountImporter.spec.ts new file mode 100644 index 00000000000..f2711280fde --- /dev/null +++ b/src/test/accountImporter.spec.ts @@ -0,0 +1,176 @@ +import * as nock from "nock"; +import { expect } from "chai"; + +import { googleOrigin } from "../api"; + +import * as accountImporter from "../accountImporter"; + +describe("accountImporter", () => { + before(() => { + nock.disableNetConnect(); + }); + + after(() => { + nock.enableNetConnect(); + }); + + const transArrayToUser = accountImporter.transArrayToUser; + const validateOptions = accountImporter.validateOptions; + const validateUserJson = accountImporter.validateUserJson; + const serialImportUsers = accountImporter.serialImportUsers; + + describe("transArrayToUser", () => { + it("should reject when passwordHash is invalid base64", () => { + expect(transArrayToUser(["123", undefined, undefined, "false"])).to.have.property("error"); + }); + + it("should not reject when passwordHash is valid base64", () => { + expect( + transArrayToUser(["123", undefined, undefined, "Jlf7onfLbzqPNFP/1pqhx6fQF/w="]) + ).to.not.have.property("error"); + }); + }); + + describe("validateOptions", () => { + it("should reject when unsupported hash algorithm provided", () => { + expect(() => validateOptions({ hashAlgo: "MD2" })).to.throw; + }); + + it("should reject when missing parameters", () => { + expect(() => validateOptions({ hashAlgo: "HMAC_SHA1" })).to.throw; + }); + }); + + describe("validateUserJson", () => { + it("should reject when unknown fields in user json", () => { + expect( + validateUserJson({ + uid: "123", + email: "test@test.org", + }) + ).to.have.property("error"); + }); + + it("should reject when unknown fields in providerUserInfo of user json", () => { + expect( + validateUserJson({ + localId: "123", + email: "test@test.org", + providerUserInfo: [ + { + providerId: "google.com", + googleId: "abc", + email: "test@test.org", + }, + ], + }) + ).to.have.property("error"); + }); + + it("should reject when unknown providerUserInfo of user json", () => { + expect( + validateUserJson({ + localId: "123", + email: "test@test.org", + providerUserInfo: [ + { + providerId: "otheridp.com", + rawId: "abc", + email: "test@test.org", + }, + ], + }) + ).to.have.property("error"); + }); + + it("should reject when passwordHash is invalid base64", () => { + expect( + validateUserJson({ + localId: "123", + passwordHash: "false", + }) + ).to.have.property("error"); + }); + + it("should not reject when passwordHash is valid base64", () => { + expect( + validateUserJson({ + localId: "123", + passwordHash: "Jlf7onfLbzqPNFP/1pqhx6fQF/w=", + }) + ).to.not.have.property("error"); + }); + }); + + describe("serialImportUsers", () => { + let batches: { localId: string; email: string }[][] = []; + const hashOptions = { + hashAlgo: "HMAC_SHA1", + hashKey: "a2V5MTIz", + }; + let expectedResponse: { status: number; body: any }[] = []; + + beforeEach(() => { + for (let i = 0; i < 10; i++) { + batches.push([ + { + localId: i.toString(), + email: `test${i}@test.org`, + }, + ]); + expectedResponse.push({ + status: 200, + body: {}, + }); + } + }); + + afterEach(() => { + batches = []; + expectedResponse = []; + }); + + it("should call api.request multiple times", async () => { + for (let i = 0; i < batches.length; i++) { + nock(googleOrigin) + .post("/identitytoolkit/v3/relyingparty/uploadAccount", { + hashAlgorithm: "HMAC_SHA1", + signerKey: "a2V5MTIz", + targetProjectId: "test-project-id", + users: [{ email: `test${i}@test.org`, localId: i.toString() }], + }) + .once() + .reply(expectedResponse[i].status, expectedResponse[i].body); + } + await serialImportUsers("test-project-id", hashOptions, batches, 0); + expect(nock.isDone()).to.be.true; + }); + + it("should continue when some request's response is 200 but has `error` in response", async () => { + expectedResponse[5] = { + status: 200, + body: { + error: [ + { + index: 0, + message: "some error message", + }, + ], + }, + }; + for (let i = 0; i < batches.length; i++) { + nock(googleOrigin) + .post("/identitytoolkit/v3/relyingparty/uploadAccount", { + hashAlgorithm: "HMAC_SHA1", + signerKey: "a2V5MTIz", + targetProjectId: "test-project-id", + users: [{ email: `test${i}@test.org`, localId: i.toString() }], + }) + .once() + .reply(expectedResponse[i].status, expectedResponse[i].body); + } + await serialImportUsers("test-project-id", hashOptions, batches, 0); + expect(nock.isDone()).to.be.true; + }); + }); +}); From f8cb830d2102d15eda5c1938d6e15625407deb79 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 20 Jan 2022 16:12:37 -0600 Subject: [PATCH 0037/1699] migrate extensions to apiv2 (#3995) * migrate extensions to apiv2 * more typing plz --- src/extensions/extensionsApi.ts | 264 +++++++------------ src/extensions/extensionsHelper.ts | 31 +-- src/extensions/provisioningHelper.ts | 18 +- src/extensions/resolveSource.ts | 7 +- src/test/extensions/extensionsHelper.spec.ts | 6 +- 5 files changed, 133 insertions(+), 193 deletions(-) diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index ddd1ac39c9f..8b88755406c 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -3,16 +3,19 @@ import * as _ from "lodash"; import * as clc from "cli-color"; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const { marked } = require("marked"); -import * as api from "../api"; -import * as apiv2 from "../apiv2"; -import * as refs from "./refs"; + +import { Client } from "../apiv2"; +import { extensionsOrigin } from "../api"; +import { FirebaseError } from "../error"; import { logger } from "../logger"; import * as operationPoller from "../operation-poller"; -import { FirebaseError } from "../error"; +import * as refs from "./refs"; const VERSION = "v1beta"; const PAGE_SIZE_MAX = 100; +const apiClient = new Client({ urlPrefix: extensionsOrigin, apiVersion: VERSION }); + export enum RegistryLaunchStage { EXPERIMENTAL = "EXPERIMENTAL", BETA = "BETA", @@ -178,24 +181,25 @@ async function createInstanceHelper( projectId: string, instanceId: string, config: any, - validateOnly: boolean = false + validateOnly = false ): Promise { - const createRes = await api.request("POST", `/${VERSION}/projects/${projectId}/instances/`, { - auth: true, - origin: api.extensionsOrigin, - data: { + const createRes = await apiClient.post<{ name: string; config: unknown }, ExtensionInstance>( + `/projects/${projectId}/instances/`, + { name: `projects/${projectId}/instances/${instanceId}`, - config: config, - }, - query: { - validateOnly, + config, }, - }); + { + queryParams: { + validateOnly: validateOnly ? "true" : "false", + }, + } + ); if (validateOnly) { - return createRes; + return createRes.body; } const pollRes = await operationPoller.pollOperation({ - apiOrigin: api.extensionsOrigin, + apiOrigin: extensionsOrigin, apiVersion: VERSION, operationResourceName: createRes.body.name, masterTimeout: 600000, @@ -247,16 +251,11 @@ export async function createInstance(args: { * @param instanceId the id of the instance to delete */ export async function deleteInstance(projectId: string, instanceId: string): Promise { - const deleteRes = await api.request( - "DELETE", - `/${VERSION}/projects/${projectId}/instances/${instanceId}`, - { - auth: true, - origin: api.extensionsOrigin, - } + const deleteRes = await apiClient.delete<{ name: string }>( + `/projects/${projectId}/instances/${instanceId}` ); const pollRes = await operationPoller.pollOperation({ - apiOrigin: api.extensionsOrigin, + apiOrigin: extensionsOrigin, apiVersion: VERSION, operationResourceName: deleteRes.body.name, masterTimeout: 600000, @@ -268,24 +267,9 @@ export async function deleteInstance(projectId: string, instanceId: string): Pro * Get an instance by its id. * @param projectId the project where the instance exists * @param instanceId the id of the instance to delete - * @param options extra options to pass to api.request */ -export async function getInstance( - projectId: string, - instanceId: string, - options: any = {} -): Promise { - const res = await api.request( - "GET", - `/${VERSION}/projects/${projectId}/instances/${instanceId}`, - _.assign( - { - auth: true, - origin: api.extensionsOrigin, - }, - options - ) - ); +export async function getInstance(projectId: string, instanceId: string): Promise { + const res = await apiClient.get(`/projects/${projectId}/instances/${instanceId}`); return res.body; } @@ -295,16 +279,17 @@ export async function getInstance( * @param projectId the project to list instances for */ export async function listInstances(projectId: string): Promise { - const instances: any[] = []; - const getNextPage = async (pageToken?: string) => { - const res = await api.request("GET", `/${VERSION}/projects/${projectId}/instances`, { - auth: true, - origin: api.extensionsOrigin, - query: { - pageSize: PAGE_SIZE_MAX, - pageToken, - }, - }); + const instances: ExtensionInstance[] = []; + const getNextPage = async (pageToken = ""): Promise => { + const res = await apiClient.get<{ instances: ExtensionInstance[]; nextPageToken?: string }>( + `/projects/${projectId}/instances`, + { + queryParams: { + pageSize: PAGE_SIZE_MAX, + pageToken, + }, + } + ); if (Array.isArray(res.body.instances)) { instances.push(...res.body.instances); } @@ -423,24 +408,21 @@ async function patchInstance(args: { validateOnly: boolean; data: any; }): Promise { - const updateRes = await api.request( - "PATCH", - `/${VERSION}/projects/${args.projectId}/instances/${args.instanceId}`, + const updateRes = await apiClient.patch( + `/projects/${args.projectId}/instances/${args.instanceId}`, + args.data, { - auth: true, - origin: api.extensionsOrigin, - query: { + queryParams: { updateMask: args.updateMask, - validateOnly: args.validateOnly, + validateOnly: args.validateOnly ? "true" : "false", }, - data: args.data, } ); if (args.validateOnly) { return updateRes; } const pollRes = await operationPoller.pollOperation({ - apiOrigin: api.extensionsOrigin, + apiOrigin: extensionsOrigin, apiVersion: VERSION, operationResourceName: updateRes.body.name, masterTimeout: 600000, @@ -474,16 +456,15 @@ export async function createSource( packageUri: string, extensionRoot: string ): Promise { - const createRes = await api.request("POST", `/${VERSION}/projects/${projectId}/sources/`, { - auth: true, - origin: api.extensionsOrigin, - data: { - packageUri, - extensionRoot, - }, + const createRes = await apiClient.post< + { packageUri: string; extensionRoot: string }, + ExtensionSource + >(`/projects/${projectId}/sources/`, { + packageUri, + extensionRoot, }); const pollRes = await operationPoller.pollOperation({ - apiOrigin: api.extensionsOrigin, + apiOrigin: extensionsOrigin, apiVersion: VERSION, operationResourceName: createRes.body.name, masterTimeout: 600000, @@ -494,22 +475,17 @@ export async function createSource( return pollRes; } -/** Get a extension source by its fully qualified path +/** + * Get a extension source by its fully qualified path * * @param sourceName the fully qualified path of the extension source (/projects//sources/) */ -export function getSource(sourceName: string): Promise { - return api - .request("GET", `/${VERSION}/${sourceName}`, { - auth: true, - origin: api.extensionsOrigin, - }) - .then((res) => { - if (res.body.spec) { - populateResourceProperties(res.body.spec); - } - return res.body; - }); +export async function getSource(sourceName: string): Promise { + const res = await apiClient.get(`/${sourceName}`); + if (res.body.spec) { + populateResourceProperties(res.body.spec); + } + return res.body; } /** @@ -521,10 +497,7 @@ export async function getExtensionVersion(extensionVersionRef: string): Promise< throw new FirebaseError(`ExtensionVersion ref "${extensionVersionRef}" must supply a version.`); } try { - const res = await api.request("GET", `/${VERSION}/${refs.toExtensionVersionName(ref)}`, { - auth: true, - origin: api.extensionsOrigin, - }); + const res = await apiClient.get(`/${refs.toExtensionVersionName(ref)}`); if (res.body.spec) { populateResourceProperties(res.body.spec); } @@ -547,16 +520,17 @@ export async function getExtensionVersion(extensionVersionRef: string): Promise< */ export async function listExtensions(publisherId: string): Promise { const extensions: Extension[] = []; - const getNextPage = async (pageToken?: string) => { - const res = await api.request("GET", `/${VERSION}/publishers/${publisherId}/extensions`, { - auth: true, - origin: api.extensionsOrigin, - showUnpublished: false, - query: { - pageSize: PAGE_SIZE_MAX, - pageToken, - }, - }); + const getNextPage = async (pageToken = "") => { + const res = await apiClient.get<{ extensions: Extension[]; nextPageToken: string }>( + `/publishers/${publisherId}/extensions`, + { + queryParams: { + showUnpublished: "false", + pageSize: PAGE_SIZE_MAX, + pageToken, + }, + } + ); if (Array.isArray(res.body.extensions)) { extensions.push(...res.body.extensions); } @@ -572,26 +546,20 @@ export async function listExtensions(publisherId: string): Promise * @param ref user-friendly identifier for the ExtensionVersion (publisher-id/extension-id) * @param showUnpublished whether to include unpublished ExtensionVersions, default = false */ -export async function listExtensionVersions( - ref: string, - filter?: string -): Promise { +export async function listExtensionVersions(ref: string, filter = ""): Promise { const { publisherId, extensionId } = refs.parse(ref); const extensionVersions: ExtensionVersion[] = []; - const getNextPage = async (pageToken?: string) => { - const res = await api.request( - "GET", - `/${VERSION}/publishers/${publisherId}/extensions/${extensionId}/versions`, - { - auth: true, - origin: api.extensionsOrigin, - query: { - filter, - pageSize: PAGE_SIZE_MAX, - pageToken, - }, - } - ); + const getNextPage = async (pageToken = "") => { + const res = await apiClient.get<{ + extensionVersions: ExtensionVersion[]; + nextPageToken: string; + }>(`/publishers/${publisherId}/extensions/${extensionId}/versions`, { + queryParams: { + filter, + pageSize: PAGE_SIZE_MAX, + pageToken, + }, + }); if (Array.isArray(res.body.extensionVersions)) { extensionVersions.push(...res.body.extensionVersions); } @@ -611,8 +579,7 @@ export async function getPublisherProfile( projectId: string, publisherId?: string ): Promise { - const client = new apiv2.Client({ urlPrefix: api.extensionsOrigin }); - const res = await client.get(`/${VERSION}/projects/${projectId}/publisherProfile`, { + const res = await apiClient.get(`/projects/${projectId}/publisherProfile`, { queryParams: publisherId == undefined ? undefined @@ -631,13 +598,10 @@ export async function registerPublisherProfile( projectId: string, publisherId: string ): Promise { - const res = await api.request( - "POST", - `/${VERSION}/projects/${projectId}/publisherProfile:register`, + const res = await apiClient.post<{ publisherId: string }, PublisherProfile>( + `/projects/${projectId}/publisherProfile:register`, { - auth: true, - origin: api.extensionsOrigin, - data: { publisherId }, + publisherId, } ); return res.body; @@ -653,13 +617,10 @@ export async function deprecateExtensionVersion( ): Promise { const ref = refs.parse(extensionRef); try { - const res = await api.request( - "POST", - `/${VERSION}/${refs.toExtensionVersionName(ref)}:deprecate`, + const res = await apiClient.post<{ deprecationMessage: string }, ExtensionVersion>( + `/${refs.toExtensionVersionName(ref)}:deprecate`, { - auth: true, - origin: api.extensionsOrigin, - data: { deprecationMessage }, + deprecationMessage, } ); return res.body; @@ -691,13 +652,8 @@ export async function deprecateExtensionVersion( export async function undeprecateExtensionVersion(extensionRef: string): Promise { const ref = refs.parse(extensionRef); try { - const res = await api.request( - "POST", - `/${VERSION}/${refs.toExtensionVersionName(ref)}:undeprecate`, - { - auth: true, - origin: api.extensionsOrigin, - } + const res = await apiClient.post( + `/${refs.toExtensionVersionName(ref)}:undeprecate` ); return res.body; } catch (err: any) { @@ -739,21 +695,16 @@ export async function publishExtensionVersion( // TODO(b/185176470): Publishing an extension with a previously deleted name will return 409. // Need to surface a better error, potentially by calling getExtension. - const publishRes = await api.request( - "POST", - `/${VERSION}/${refs.toExtensionName(ref)}/versions:publish`, - { - auth: true, - origin: api.extensionsOrigin, - data: { - versionId: ref.version, - packageUri, - extensionRoot: extensionRoot ?? "/", - }, - } - ); + const publishRes = await apiClient.post< + { versionId: string; packageUri: string; extensionRoot: string }, + ExtensionVersion + >(`/${refs.toExtensionName(ref)}/versions:publish`, { + versionId: ref.version, + packageUri, + extensionRoot: extensionRoot ?? "/", + }); const pollRes = await operationPoller.pollOperation({ - apiOrigin: api.extensionsOrigin, + apiOrigin: extensionsOrigin, apiVersion: VERSION, operationResourceName: publishRes.body.name, masterTimeout: 600000, @@ -770,12 +721,8 @@ export async function unpublishExtension(extensionRef: string): Promise { if (ref.version) { throw new FirebaseError(`Extension reference "${extensionRef}" must not contain a version.`); } - const url = `/${VERSION}/${refs.toExtensionName(ref)}:unpublish`; try { - await api.request("POST", url, { - auth: true, - origin: api.extensionsOrigin, - }); + await apiClient.post(`/${refs.toExtensionName(ref)}:unpublish`); } catch (err: any) { if (err.status === 403) { throw new FirebaseError( @@ -803,12 +750,8 @@ export async function deleteExtension(extensionRef: string): Promise { if (ref.version) { throw new FirebaseError(`Extension reference "${extensionRef}" must not contain a version.`); } - const url = `/${VERSION}/${refs.toExtensionName(ref)}`; try { - await api.request("DELETE", url, { - auth: true, - origin: api.extensionsOrigin, - }); + await apiClient.delete(`/${refs.toExtensionName(ref)}`); } catch (err: any) { if (err.status === 403) { throw new FirebaseError( @@ -835,10 +778,7 @@ export async function deleteExtension(extensionRef: string): Promise { export async function getExtension(extensionRef: string): Promise { const ref = refs.parse(extensionRef); try { - const res = await api.request("GET", `/${VERSION}/${refs.toExtensionName(ref)}`, { - auth: true, - origin: api.extensionsOrigin, - }); + const res = await apiClient.get(`/${refs.toExtensionName(ref)}`); return res.body; } catch (err: any) { if (err.status === 404) { diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 94fa6327709..2b8ca99679b 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -95,7 +95,7 @@ export const resourceTypeToNiceName: Record = { */ export function getDBInstanceFromURL(databaseUrl = ""): string { const instanceRegex = new RegExp("(?:https://)(.*)(?:.firebaseio.com)"); - const matches = databaseUrl.match(instanceRegex); + const matches = instanceRegex.exec(databaseUrl); if (matches && matches.length > 1) { return matches[1]; } @@ -528,7 +528,7 @@ export async function createSourceFromLocation( let packageUri: string; let extensionRoot: string; let objectPath = ""; - if (!URL_REGEX.test(sourceUri)) { + if (!sourceUri.startsWith("https:")) { const uploadSpinner = ora(" Archiving and uploading extension source code"); try { uploadSpinner.start(); @@ -669,25 +669,26 @@ export async function promptForRepeatInstance( * @param instanceId ID of the extension instance */ export async function instanceIdExists(projectId: string, instanceId: string): Promise { - const instanceRes = await getInstance(projectId, instanceId, { - resolveOnHTTPError: true, - }); - if (instanceRes.error) { - if (_.get(instanceRes, "error.code") === 404) { - return false; + try { + await getInstance(projectId, instanceId); + } catch (err: unknown) { + if (err instanceof FirebaseError) { + if (err.status === 404) { + return false; + } + const msg = `Unexpected error when checking if instance ID exists: ${err}`; + throw new FirebaseError(msg, { + original: err, + }); + } else { + throw err; } - const msg = - "Unexpected error when checking if instance ID exists: " + - _.get(instanceRes, "error.message"); - throw new FirebaseError(msg, { - original: instanceRes.error, - }); } return true; } export function isUrlPath(extInstallPath: string): boolean { - return URL_REGEX.test(extInstallPath); + return extInstallPath.startsWith("https:"); } export function isLocalPath(extInstallPath: string): boolean { diff --git a/src/extensions/provisioningHelper.ts b/src/extensions/provisioningHelper.ts index b42ea7d66bb..4c359e50de0 100644 --- a/src/extensions/provisioningHelper.ts +++ b/src/extensions/provisioningHelper.ts @@ -2,8 +2,8 @@ const { marked } = require("marked"); import * as extensionsApi from "./extensionsApi"; -import * as api from "../api"; -import * as refs from "./refs"; +import { firebaseStorageOrigin, firedataOrigin } from "../api"; +import { Client } from "../apiv2"; import { flattenArray } from "../functional"; import { FirebaseError } from "../error"; import { getExtensionVersion, InstanceSpec } from "../deploy/extensions/planner"; @@ -119,10 +119,8 @@ function getTriggerType(propertiesYaml: string | undefined) { } async function isStorageProvisioned(projectId: string): Promise { - const resp = await api.request("GET", `/v1beta/projects/${projectId}/buckets`, { - auth: true, - origin: api.firebaseStorageOrigin, - }); + const client = new Client({ urlPrefix: firebaseStorageOrigin, apiVersion: "v1beta" }); + const resp = await client.get<{ buckets: { name: string }[] }>(`/projects/${projectId}/buckets`); return !!resp.body?.buckets?.find((bucket: any) => { const bucketResourceName = bucket.name; // Bucket resource name looks like: projects/PROJECT_NUMBER/buckets/BUCKET_NAME @@ -134,9 +132,9 @@ async function isStorageProvisioned(projectId: string): Promise { } async function isAuthProvisioned(projectId: string): Promise { - const resp = await api.request("GET", `/v1/projects/${projectId}/products`, { - auth: true, - origin: api.firedataOrigin, - }); + const client = new Client({ urlPrefix: firedataOrigin, apiVersion: "v1" }); + const resp = await client.get<{ activation: { service: string }[] }>( + `/projects/${projectId}/products` + ); return !!resp.body?.activation?.map((a: any) => a.service).includes("FIREBASE_AUTH"); } diff --git a/src/extensions/resolveSource.ts b/src/extensions/resolveSource.ts index 8a1bd81c23f..ad5d0c35fa6 100644 --- a/src/extensions/resolveSource.ts +++ b/src/extensions/resolveSource.ts @@ -7,6 +7,8 @@ import * as api from "../api"; import { FirebaseError } from "../error"; import { logger } from "../logger"; import { promptOnce } from "../prompt"; +import { Client } from "../apiv2"; +import { firebaseExtensionsRegistryOrigin } from "../api"; const EXTENSIONS_REGISTRY_ENDPOINT = "/extensions.json"; @@ -112,9 +114,8 @@ export function getMinRequiredVersion(registryEntry: RegistryEntry): string { export async function getExtensionRegistry( onlyFeatured?: boolean ): Promise<{ [key: string]: RegistryEntry }> { - const res = await api.request("GET", EXTENSIONS_REGISTRY_ENDPOINT, { - origin: api.firebaseExtensionsRegistryOrigin, - }); + const client = new Client({ urlPrefix: firebaseExtensionsRegistryOrigin }); + const res = await client.get(EXTENSIONS_REGISTRY_ENDPOINT); const extensions = _.get(res, "body.mods") as { [key: string]: RegistryEntry }; if (onlyFeatured) { diff --git a/src/test/extensions/extensionsHelper.spec.ts b/src/test/extensions/extensionsHelper.spec.ts index 081bfb35802..efbcc3c9d9e 100644 --- a/src/test/extensions/extensionsHelper.spec.ts +++ b/src/test/extensions/extensionsHelper.spec.ts @@ -862,7 +862,7 @@ describe("extensionsHelper", () => { }); it("should return false if no instance with that name exists", async () => { - getInstanceStub.resolves({ error: { code: 404 } }); + getInstanceStub.throws(new FirebaseError("Not Found", { status: 404 })); const exists = await extensionsHelper.instanceIdExists("proj", TEST_NAME); expect(exists).to.be.false; @@ -876,11 +876,11 @@ describe("extensionsHelper", () => { }); it("should throw if it gets an unexpected error response from getInstance", async () => { - getInstanceStub.resolves({ error: { code: 500, message: "a message" } }); + getInstanceStub.throws(new FirebaseError("Internal Error", { status: 500 })); await expect(extensionsHelper.instanceIdExists("proj", TEST_NAME)).to.be.rejectedWith( FirebaseError, - "Unexpected error when checking if instance ID exists: a message" + "Unexpected error when checking if instance ID exists: FirebaseError: Internal Error" ); }); }); From 9c6127e90f172222473c1716be82ca63820ee91a Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 20 Jan 2022 16:57:09 -0600 Subject: [PATCH 0038/1699] migrate remoteconfig over to apiv2 (#3720) * migrate remoteconfig over to apiv2 * format datetimes better --- src/commands/remoteconfig-versions-list.ts | 7 +- src/remoteconfig/get.ts | 23 ++++-- src/remoteconfig/rollback.ts | 28 +++++-- src/remoteconfig/versionslist.ts | 21 +++-- src/test/remoteconfig/get.spec.ts | 74 +++++------------- src/test/remoteconfig/rollback.spec.ts | 89 ++++++---------------- src/test/remoteconfig/versionslist.spec.ts | 76 +++++------------- 7 files changed, 118 insertions(+), 200 deletions(-) diff --git a/src/commands/remoteconfig-versions-list.ts b/src/commands/remoteconfig-versions-list.ts index a26fc206275..4e3731ecfd3 100644 --- a/src/commands/remoteconfig-versions-list.ts +++ b/src/commands/remoteconfig-versions-list.ts @@ -5,13 +5,18 @@ import { needProjectId } from "../projectUtils"; import { requireAuth } from "../requireAuth"; import { requirePermissions } from "../requirePermissions"; import { Version, ListVersionsResult } from "../remoteconfig/interfaces"; +import { datetimeString } from "../utils"; import Table = require("cli-table"); const tableHead = ["Update User", "Version Number", "Update Time"]; function pushTableContents(table: Table, version: Version): number { - return table.push([version?.updateUser?.email, version?.versionNumber, version?.updateTime]); + return table.push([ + version.updateUser?.email, + version.versionNumber, + version.updateTime ? datetimeString(new Date(version.updateTime)) : "", + ]); } module.exports = new Command("remoteconfig:versions:list") diff --git a/src/remoteconfig/get.ts b/src/remoteconfig/get.ts index 600ebfb25f0..bc4063b7489 100644 --- a/src/remoteconfig/get.ts +++ b/src/remoteconfig/get.ts @@ -1,4 +1,5 @@ -import * as api from "../api"; +import { remoteConfigApiOrigin } from "../api"; +import { Client } from "../apiv2"; import { logger } from "../logger"; import { FirebaseError } from "../error"; import { RemoteConfigTemplate } from "./interfaces"; @@ -8,6 +9,11 @@ const TIMEOUT = 30000; // Creates a maximum limit of 50 names for each entry const MAX_DISPLAY_ITEMS = 50; +const apiClient = new Client({ + urlPrefix: remoteConfigApiOrigin, + apiVersion: "v1", +}); + /** * Function retrieves names for parameters and parameter groups * @param templateItems Input is template.parameters or template.parameterGroups @@ -42,21 +48,22 @@ export async function getTemplate( versionNumber?: string ): Promise { try { - let request = `/v1/projects/${projectId}/remoteConfig`; + const params = new URLSearchParams(); if (versionNumber) { - request = request + "?versionNumber=" + versionNumber; + params.set("versionNumber", versionNumber); } - const response = await api.request("GET", request, { - auth: true, - origin: api.remoteConfigApiOrigin, + const res = await apiClient.request({ + method: "GET", + path: `/projects/${projectId}/remoteConfig`, + queryParams: params, timeout: TIMEOUT, }); - return response.body; + return res.body; } catch (err: any) { logger.debug(err.message); throw new FirebaseError( `Failed to get Firebase Remote Config template for project ${projectId}. `, - { exit: 2, original: err } + { original: err } ); } } diff --git a/src/remoteconfig/rollback.ts b/src/remoteconfig/rollback.ts index ec1bd27efd2..83e23201393 100644 --- a/src/remoteconfig/rollback.ts +++ b/src/remoteconfig/rollback.ts @@ -1,4 +1,11 @@ -import api = require("../api"); +import { remoteConfigApiOrigin } from "../api"; +import { Client } from "../apiv2"; +import { RemoteConfigTemplate } from "./interfaces"; + +const apiClient = new Client({ + urlPrefix: remoteConfigApiOrigin, + apiVersion: "v1", +}); const TIMEOUT = 30000; @@ -6,14 +13,19 @@ const TIMEOUT = 30000; * Rolls back to a specific version of the Remote Config template * @param projectId Remote Config Template Project Id * @param versionNumber Remote Config Template version number to roll back to - * @return {Promise} Returns a promise of a Remote Config Template using the RemoteConfigTemplate interface + * @return Returns a promise of a Remote Config Template using the RemoteConfigTemplate interface */ -export async function rollbackTemplate(projectId: string, versionNumber?: number): Promise { - const requestPath = `/v1/projects/${projectId}/remoteConfig:rollback?versionNumber=${versionNumber}`; - const response = await api.request("POST", requestPath, { - auth: true, - origin: api.remoteConfigApiOrigin, +export async function rollbackTemplate( + projectId: string, + versionNumber?: number +): Promise { + const params = new URLSearchParams(); + params.set("versionNumber", `${versionNumber}`); + const res = await apiClient.request({ + method: "POST", + path: `/projects/${projectId}/remoteConfig:rollback`, + queryParams: params, timeout: TIMEOUT, }); - return response.body; + return res.body; } diff --git a/src/remoteconfig/versionslist.ts b/src/remoteconfig/versionslist.ts index f4642c02b20..697e17a199b 100644 --- a/src/remoteconfig/versionslist.ts +++ b/src/remoteconfig/versionslist.ts @@ -1,8 +1,14 @@ -import api = require("../api"); +import { remoteConfigApiOrigin } from "../api"; +import { Client } from "../apiv2"; import { FirebaseError } from "../error"; import { ListVersionsResult } from "./interfaces"; import { logger } from "../logger"; +const apiClient = new Client({ + urlPrefix: remoteConfigApiOrigin, + apiVersion: "v1", +}); + const TIMEOUT = 30000; /** @@ -14,13 +20,14 @@ const TIMEOUT = 30000; export async function getVersions(projectId: string, maxResults = 10): Promise { maxResults = maxResults || 300; try { - let request = `/v1/projects/${projectId}/remoteConfig:listVersions`; + const params = new URLSearchParams(); if (maxResults) { - request = request + "?pageSize=" + maxResults; + params.set("pageSize", `${maxResults}`); } - const response = await api.request("GET", request, { - auth: true, - origin: api.remoteConfigApiOrigin, + const response = await apiClient.request({ + method: "GET", + path: `/projects/${projectId}/remoteConfig:listVersions`, + queryParams: params, timeout: TIMEOUT, }); return response.body; @@ -28,7 +35,7 @@ export async function getVersions(projectId: string, maxResults = 10): Promise { - let sandbox: sinon.SinonSandbox; - let apiRequestStub: sinon.SinonStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - apiRequestStub = sandbox.stub(api, "request").throws("Unexpected API request call"); - }); - - afterEach(() => { - sandbox.restore(); - }); - describe("getTemplate", () => { + afterEach(() => { + expect(nock.isDone()).to.equal(true, "all nock stubs should have been called"); + nock.cleanAll(); + }); + it("should return the latest template", async () => { - apiRequestStub.onFirstCall().resolves({ body: expectedProjectInfo }); + nock(remoteConfigApiOrigin) + .get(`/v1/projects/${PROJECT_ID}/remoteConfig`) + .reply(200, expectedProjectInfo); const RCtemplate = await remoteconfig.getTemplate(PROJECT_ID); expect(RCtemplate).to.deep.equal(expectedProjectInfo); - expect(apiRequestStub).to.be.calledOnceWith( - "GET", - `/v1/projects/${PROJECT_ID}/remoteConfig`, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); }); it("should return the correct version of the template if version is specified", async () => { - apiRequestStub.onFirstCall().resolves({ body: expectedProjectInfo }); + nock(remoteConfigApiOrigin) + .get(`/v1/projects/${PROJECT_ID}/remoteConfig?versionNumber=${6}`) + .reply(200, expectedProjectInfo); const RCtemplateVersion = await remoteconfig.getTemplate(PROJECT_ID, "6"); expect(RCtemplateVersion).to.deep.equal(expectedProjectInfo); - expect(apiRequestStub).to.be.calledOnceWith( - "GET", - `/v1/projects/${PROJECT_ID}/remoteConfig?versionNumber=6`, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); }); it("should return a correctly parsed entry value with one parameter", () => { @@ -155,29 +135,11 @@ describe("Remote Config GET", () => { }); it("should reject if the api call fails", async () => { - const expectedError = new Error("HTTP Error 404: Not Found"); + nock(remoteConfigApiOrigin).get(`/v1/projects/${PROJECT_ID}/remoteConfig`).reply(404, {}); - apiRequestStub.onFirstCall().rejects(expectedError); - - let err; - try { - await remoteconfig.getTemplate(PROJECT_ID); - } catch (e: any) { - err = e; - } - - expect(err.message).to.equal( - `Failed to get Firebase Remote Config template for project ${PROJECT_ID}. ` - ); - expect(err.original).to.equal(expectedError); - expect(apiRequestStub).to.be.calledOnceWith( - "GET", - `/v1/projects/${PROJECT_ID}/remoteConfig`, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } + await expect(remoteconfig.getTemplate(PROJECT_ID)).to.eventually.be.rejectedWith( + FirebaseError, + /Failed to get Firebase Remote Config template/ ); }); }); diff --git a/src/test/remoteconfig/rollback.spec.ts b/src/test/remoteconfig/rollback.spec.ts index 19bf3ec942d..27139036f21 100644 --- a/src/test/remoteconfig/rollback.spec.ts +++ b/src/test/remoteconfig/rollback.spec.ts @@ -1,18 +1,14 @@ import { expect } from "chai"; - -import api = require("../../api"); -import sinon = require("sinon"); +import { remoteConfigApiOrigin } from "../../api"; +import * as nock from "nock"; import { RemoteConfigTemplate } from "../../remoteconfig/interfaces"; import * as remoteconfig from "../../remoteconfig/rollback"; +import { FirebaseError } from "../../error"; const PROJECT_ID = "the-remoteconfig-test-project"; -function createTemplate( - versionNumber: string, - date: string, - rollbackSource?: string -): RemoteConfigTemplate { +function createTemplate(versionNumber: string, date: string): RemoteConfigTemplate { return { parameterGroups: {}, version: { @@ -22,7 +18,6 @@ function createTemplate( updateTime: date, updateOrigin: "REST_API", versionNumber: versionNumber, - rollbackSource: rollbackSource, }, conditions: [], parameters: {}, @@ -34,51 +29,31 @@ const latestTemplate: RemoteConfigTemplate = createTemplate("115", "2020-08-06T2 const rollbackTemplate: RemoteConfigTemplate = createTemplate("114", "2020-08-07T23:11:41.629Z"); describe("RemoteConfig Rollback", () => { - let sandbox: sinon.SinonSandbox; - let apiRequestStub: sinon.SinonStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - apiRequestStub = sandbox.stub(api, "request").throws("Unexpected API request call"); - }); - afterEach(() => { - sandbox.restore(); + expect(nock.isDone()).to.equal(true, "all nock stubs should have been called"); + nock.cleanAll(); }); describe("rollbackCurrentVersion", () => { it("should return a rollback to the version number specified", async () => { - apiRequestStub.onFirstCall().resolves({ body: latestTemplate }); + nock(remoteConfigApiOrigin) + .post(`/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=${115}`) + .reply(200, latestTemplate); const RCtemplate = await remoteconfig.rollbackTemplate(PROJECT_ID, 115); expect(RCtemplate).to.deep.equal(latestTemplate); - expect(apiRequestStub).to.be.calledOnceWith( - "POST", - `/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=` + 115, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); }); - it("should reject invalid rollback version number", async () => { - apiRequestStub.onFirstCall().resolves({ body: latestTemplate }); + // TODO: there is no logic that this is testing. Is that intentional? + it.skip("should reject invalid rollback version number", async () => { + nock(remoteConfigApiOrigin) + .post(`/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=${1000}`) + .reply(200, latestTemplate); const RCtemplate = await remoteconfig.rollbackTemplate(PROJECT_ID, 1000); expect(RCtemplate).to.deep.equal(latestTemplate); - expect(apiRequestStub).to.be.calledOnceWith( - "POST", - `/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=` + 1000, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); try { await remoteconfig.rollbackTemplate(PROJECT_ID); } catch (e: any) { @@ -86,38 +61,24 @@ describe("RemoteConfig Rollback", () => { } }); - it("should return a rollback to the previous version", async () => { - apiRequestStub.onFirstCall().resolves({ body: rollbackTemplate }); + // TODO: this also is not testing anything in the file. Is this intentional? + it.skip("should return a rollback to the previous version", async () => { + nock(remoteConfigApiOrigin) + .post(`/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=${undefined}`) + .reply(200, rollbackTemplate); const RCtemplate = await remoteconfig.rollbackTemplate(PROJECT_ID); expect(RCtemplate).to.deep.equal(rollbackTemplate); - expect(apiRequestStub).to.be.calledWith( - "POST", - `/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=undefined`, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); }); it("should reject if the api call fails", async () => { - try { - await remoteconfig.rollbackTemplate(PROJECT_ID); - } catch (e: any) { - e; - } - - expect(apiRequestStub).to.be.calledWith( - "POST", - `/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=undefined`, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } + nock(remoteConfigApiOrigin) + .post(`/v1/projects/${PROJECT_ID}/remoteConfig:rollback?versionNumber=${4}`) + .reply(404, {}); + await expect(remoteconfig.rollbackTemplate(PROJECT_ID, 4)).to.eventually.be.rejectedWith( + FirebaseError, + /Not Found/ ); }); }); diff --git a/src/test/remoteconfig/versionslist.spec.ts b/src/test/remoteconfig/versionslist.spec.ts index 84c11b10baf..705efcf080d 100644 --- a/src/test/remoteconfig/versionslist.spec.ts +++ b/src/test/remoteconfig/versionslist.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; -import * as sinon from "sinon"; +import { remoteConfigApiOrigin } from "../../api"; +import * as nock from "nock"; -import * as api from "../../api"; import * as remoteconfig from "../../remoteconfig/versionslist"; import { ListVersionsResult, Version } from "../../remoteconfig/interfaces"; @@ -47,74 +47,47 @@ const expectedProjectInfoNoLimit: ListVersionsResult = { }; describe("RemoteConfig ListVersions", () => { - let sandbox: sinon.SinonSandbox; - let apiRequestStub: sinon.SinonStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - apiRequestStub = sandbox.stub(api, "request").throws("Unexpected API request call"); - }); - - afterEach(() => { - sandbox.restore(); - }); - describe("getVersionTemplate", () => { + afterEach(() => { + expect(nock.isDone()).to.equal(true, "all nock stubs should have been called"); + nock.cleanAll(); + }); + it("should return the list of versions up to the limit", async () => { - apiRequestStub.onFirstCall().resolves({ body: expectedProjectInfoLimit }); + nock(remoteConfigApiOrigin) + .get(`/v1/projects/${PROJECT_ID}/remoteConfig:listVersions?pageSize=${2}`) + .reply(200, expectedProjectInfoLimit); const RCtemplate = await remoteconfig.getVersions(PROJECT_ID, 2); expect(RCtemplate).to.deep.equal(expectedProjectInfoLimit); - expect(apiRequestStub).to.be.calledOnceWith( - "GET", - `/v1/projects/${PROJECT_ID}/remoteConfig:listVersions?pageSize=` + 2, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); }); it("should return all the versions when the limit is 0", async () => { - apiRequestStub.onFirstCall().resolves({ body: expectedProjectInfoNoLimit }); + nock(remoteConfigApiOrigin) + .get(`/v1/projects/${PROJECT_ID}/remoteConfig:listVersions?pageSize=${300}`) + .reply(200, expectedProjectInfoNoLimit); const RCtemplate = await remoteconfig.getVersions(PROJECT_ID, 0); expect(RCtemplate).to.deep.equal(expectedProjectInfoNoLimit); - expect(apiRequestStub).to.be.calledOnceWith( - "GET", - `/v1/projects/${PROJECT_ID}/remoteConfig:listVersions?pageSize=` + 300, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); }); it("should return with default 10 versions when no limit is set", async () => { - apiRequestStub.onFirstCall().resolves({ body: expectedProjectInfoDefault }); + nock(remoteConfigApiOrigin) + .get(`/v1/projects/${PROJECT_ID}/remoteConfig:listVersions?pageSize=${10}`) + .reply(200, expectedProjectInfoDefault); const RCtemplateVersion = await remoteconfig.getVersions(PROJECT_ID); expect(RCtemplateVersion.versions.length).to.deep.equal(10); expect(RCtemplateVersion).to.deep.equal(expectedProjectInfoDefault); - expect(apiRequestStub).to.be.calledOnceWith( - "GET", - `/v1/projects/${PROJECT_ID}/remoteConfig:listVersions?pageSize=` + 10, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); }); it("should reject if the api call fails", async () => { - const expectedError = new Error("HTTP Error 404: Not Found"); - apiRequestStub.onFirstCall().rejects(expectedError); + nock(remoteConfigApiOrigin) + .get(`/v1/projects/${PROJECT_ID}/remoteConfig:listVersions?pageSize=${10}`) + .reply(404, "Not Found"); let err; try { @@ -123,19 +96,10 @@ describe("RemoteConfig ListVersions", () => { err = e; } + expect(err).to.not.be.undefined; expect(err.message).to.equal( `Failed to get Remote Config template versions for Firebase project ${PROJECT_ID}. ` ); - expect(err.original).to.equal(expectedError); - expect(apiRequestStub).to.be.calledOnceWith( - "GET", - `/v1/projects/${PROJECT_ID}/remoteConfig:listVersions?pageSize=10`, - { - auth: true, - origin: api.remoteConfigApiOrigin, - timeout: 30000, - } - ); }); }); }); From c819a4c9d2a4e2e46f0bdded8dc75142cb327aca Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 20 Jan 2022 23:37:52 +0000 Subject: [PATCH 0039/1699] 10.1.2 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d423d13075b..b799d3bacf8 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.1.1", + "version": "10.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.1.1", + "version": "10.1.2", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 4cc66b55851..92c28dbde14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.1.1", + "version": "10.1.2", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 6a6fe02be06c6eda5c656453966e42c6873176a1 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 20 Jan 2022 23:38:19 +0000 Subject: [PATCH 0040/1699] [firebase-release] Removed change log and reset repo after 10.1.2 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fde720c8923..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Updates the streaming libraries used in `auth:import`. From e2d6189e8094a1218f7c513c5fedb56b72373016 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 20 Jan 2022 18:49:34 -0600 Subject: [PATCH 0041/1699] database management API client (#4030) * apiv2 for database rules management * remove commented out stuff --- src/database/metadata.ts | 55 ++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/database/metadata.ts b/src/database/metadata.ts index 24b0ed589d6..2927289c788 100644 --- a/src/database/metadata.ts +++ b/src/database/metadata.ts @@ -2,7 +2,8 @@ * Package for interacting with Realtime Database metadata. */ -import * as api from "../api"; +import { realtimeOrigin, rtdbMetadataOrigin } from "../api"; +import { Client } from "../apiv2"; import { logger } from "../logger"; import * as utils from "../utils"; @@ -30,12 +31,13 @@ export interface Ruleset { source: RulesetSource; } +const apiClient = new Client({ urlPrefix: rtdbMetadataOrigin }); + export async function listAllRulesets(databaseName: string): Promise { - const response = await api.request("GET", `/namespaces/${databaseName}/rulesets`, { - auth: true, - origin: api.rtdbMetadataOrigin, - json: true, - }); + const response = await apiClient.get<{ rulesets: Ruleset[] }>( + `/namespaces/${databaseName}/rulesets`, + { resolveOnHTTPError: true } + ); if (response.status === 200) { return response.body.rulesets; } @@ -43,11 +45,10 @@ export async function listAllRulesets(databaseName: string): Promise } export async function getRuleset(databaseName: string, rulesetId: string): Promise { - const response = await api.request("GET", `/namespaces/${databaseName}/rulesets/${rulesetId}`, { - auth: true, - origin: api.rtdbMetadataOrigin, - json: true, - }); + const response = await apiClient.get( + `/namespaces/${databaseName}/rulesets/${rulesetId}`, + { resolveOnHTTPError: true } + ); if (response.status === 200) { return response.body; } @@ -55,9 +56,8 @@ export async function getRuleset(databaseName: string, rulesetId: string): Promi } export async function getRulesetLabels(databaseName: string): Promise { - const response = await api.request("GET", `/namespaces/${databaseName}/ruleset_labels`, { - auth: true, - origin: api.rtdbMetadataOrigin, + const response = await apiClient.get(`/namespaces/${databaseName}/ruleset_labels`, { + resolveOnHTTPError: true, }); if (response.status === 200) { return response.body; @@ -69,24 +69,31 @@ export async function createRuleset( databaseName: string, source: RulesetSource ): Promise { - const response = await api.request("POST", `/.settings/rulesets.json`, { - auth: true, - origin: utils.addSubdomain(api.realtimeOrigin, databaseName), - json: false, - data: source, + const localApiClient = new Client({ + urlPrefix: utils.addSubdomain(realtimeOrigin, databaseName), }); + const response = await localApiClient.post( + `/.settings/rulesets.json`, + source, + { resolveOnHTTPError: true } + ); if (response.status === 200) { - return JSON.parse(response.body).id; + return response.body.id; } return handleErrorResponse(response); } export async function setRulesetLabels(databaseName: string, labels: LabelIds): Promise { - const response = await api.request("PUT", `/.settings/ruleset_labels.json`, { - auth: true, - origin: utils.addSubdomain(api.realtimeOrigin, databaseName), - data: labels, + const localApiClient = new Client({ + urlPrefix: utils.addSubdomain(realtimeOrigin, databaseName), }); + const response = await localApiClient.put( + `/.settings/ruleset_labels.json`, + labels, + { + resolveOnHTTPError: true, + } + ); if (response.status === 200) { return response.body; } From 77104247541ac75fbf1baaa11b63f638bcfce17d Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 20 Jan 2022 19:09:02 -0600 Subject: [PATCH 0042/1699] `request` dependency of dependencies... BEGONE! (#4029) * upgrade the re2 dependency in superstatic to remove request * upgrade universal-analytics to remove the last instance of request --- npm-shrinkwrap.json | 1923 +++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 954 insertions(+), 971 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b799d3bacf8..0a64e3b75c5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -60,7 +60,7 @@ "tmp": "0.0.33", "triple-beam": "^1.3.0", "tweetsodium": "0.0.5", - "universal-analytics": "^0.4.16", + "universal-analytics": "^0.5.3", "unzipper": "^0.10.10", "update-notifier": "^5.1.0", "uuid": "^8.3.2", @@ -1130,6 +1130,12 @@ "integrity": "sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==", "dev": true }, + "node_modules/@gar/promisify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz", + "integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==", + "optional": true + }, "node_modules/@google-cloud/common": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", @@ -1628,6 +1634,59 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.0.tgz", + "integrity": "sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@opentelemetry/api": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz", @@ -2724,11 +2783,48 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/agentkeepalive": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.0.tgz", + "integrity": "sha512-0PhAp58jZNw13UJv7NVdTGb0ZcghHUb3DrZ046JiiJY/BOaTTpbwdHq2VObPCBV8M2GPh7sgrJ3AQ8Ey468LJw==", + "optional": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/agentkeepalive/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agentkeepalive/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, + "devOptional": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -2796,27 +2892,6 @@ "string-width": "^4.1.0" } }, - "node_modules/ansi-align/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -2883,9 +2958,9 @@ } }, "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "optional": true }, "node_modules/archiver": { @@ -2944,19 +3019,6 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -2964,28 +3026,16 @@ "dev": true }, "node_modules/are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "optional": true, "dependencies": { "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "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": "^3.6.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/arg": { @@ -3337,27 +3387,6 @@ "node": ">=8" } }, - "node_modules/boxen/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/boxen/node_modules/supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -3480,6 +3509,71 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@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": "^6.0.0", + "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.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -3700,7 +3794,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -3780,27 +3874,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/cliui/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -3836,15 +3909,6 @@ "node": ">= 4" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", @@ -3876,6 +3940,15 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colornames": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", @@ -3960,19 +4033,6 @@ "node": ">= 10" } }, - "node_modules/compress-commons/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/compressible": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", @@ -4626,6 +4686,27 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -4654,9 +4735,9 @@ "optional": true }, "node_modules/env-paths": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "optional": true, "engines": { "node": ">=6" @@ -4667,6 +4748,12 @@ "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -6191,57 +6278,32 @@ "dev": true }, "node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", "optional": true, "dependencies": { - "aproba": "^1.0.3", + "ansi-regex": "^5.0.1", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", + "has-unicode": "^2.0.1", "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/gauge/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, - "dependencies": { - "number-is-nan": "^1.0.0" + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": "^12.13.0 || ^14.15.0 || >=16" } }, - "node_modules/gauge/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gauge/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "node_modules/gauge/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==", "optional": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/gaxios": { @@ -6756,9 +6818,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -7037,6 +7099,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7118,11 +7189,17 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7233,27 +7310,6 @@ "node": ">=8" } }, - "node_modules/inquirer/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/inquirer/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7277,9 +7333,9 @@ } }, "node_modules/install-artifact-from-github": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz", - "integrity": "sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.3.0.tgz", + "integrity": "sha512-iT8v1GwOAX0pPXifF/5ihnMhHOCo3OeK7z3TQa4CtSNCIg8k0UxqBEk9jRwz8OP68hHXvJ2gxRa89KYHtBkqGA==", "optional": true, "bin": { "install-from-cache": "bin/install-from-cache.js", @@ -7344,12 +7400,11 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "optional": true, + "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==", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/is-glob": { @@ -7386,6 +7441,12 @@ "node": ">=8" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", + "optional": true + }, "node_modules/is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", @@ -8357,6 +8418,70 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/make-fetch-happen/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/map-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", @@ -8612,6 +8737,71 @@ "node": ">=8" } }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -8837,9 +9027,9 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "optional": true }, "node_modules/nanoid": { @@ -9005,20 +9195,20 @@ } }, "node_modules/node-gyp": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", - "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", "optional": true, "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.3", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", - "npmlog": "^4.1.2", - "request": "^2.88.2", + "npmlog": "^6.0.0", "rimraf": "^3.0.2", - "semver": "^7.3.2", - "tar": "^6.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { @@ -9028,67 +9218,10 @@ "node": ">= 10.12.0" } }, - "node_modules/node-gyp/node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "optional": true - }, - "node_modules/node-gyp/node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "optional": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/node-gyp/node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "optional": true, - "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.3", - "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.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-gyp/node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "optional": true, - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "optional": true, "dependencies": { "lru-cache": "^6.0.0" @@ -9100,19 +9233,6 @@ "node": ">=10" } }, - "node_modules/node-gyp/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/node-gyp/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9193,24 +9313,18 @@ } }, "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", + "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", "optional": true, "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true, + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^12.13.0 || ^14.15.0 || >=16" } }, "node_modules/nyc": { @@ -9312,15 +9426,6 @@ "node": ">=8" } }, - "node_modules/nyc/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/nyc/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -9354,20 +9459,6 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nyc/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -10163,12 +10254,31 @@ "resolved": "https://registry.npmjs.org/promise-breaker/-/promise-breaker-5.0.0.tgz", "integrity": "sha512-mgsWQuG4kJ1dtO6e/QlNDLFtMkMzzecsC69aI5hlLEjGHFNpHrvGhFi4LiK5jg2SMQj74/diH+wZliL9LpGsyA==" }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "optional": true + }, "node_modules/promise-polyfill": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==", "dev": true }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -10323,9 +10433,9 @@ "dev": true }, "node_modules/psl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", - "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "node_modules/pump": { "version": "3.0.0", @@ -10497,15 +10607,15 @@ } }, "node_modules/re2": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/re2/-/re2-1.15.9.tgz", - "integrity": "sha512-AXWEhpMTBdC+3oqbjdU07dk0pBCvxh5vbOMLERL6Y8FYBSGn4vXlLe8cYszn64Yy7H8keVMrgPzoSvOd4mePpg==", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/re2/-/re2-1.17.3.tgz", + "integrity": "sha512-Dp5iWVR8W3C7Nm9DziMY4BleMPRb/pe6kvfbzLv80dVYaXRc9jRnwwNqU0oE/taRm0qYR1+Qrtzk9rPjS9ecaQ==", "hasInstallScript": true, "optional": true, "dependencies": { - "install-artifact-from-github": "^1.2.0", - "nan": "^2.14.2", - "node-gyp": "^7.1.2" + "install-artifact-from-github": "^1.3.0", + "nan": "^2.15.0", + "node-gyp": "^8.4.1" } }, "node_modules/read-pkg": { @@ -10587,9 +10697,9 @@ } }, "node_modules/readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -10710,9 +10820,9 @@ } }, "node_modules/request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", "dependencies": { "aws-sign2": "~0.7.0", @@ -10722,7 +10832,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -10732,12 +10842,12 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, "engines": { - "node": ">= 4" + "node": ">= 6" } }, "node_modules/request/node_modules/qs": { @@ -10820,6 +10930,15 @@ "node": ">=8" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/retry-request": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", @@ -11412,6 +11531,18 @@ "node": ">=0.10.0" } }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -11505,37 +11636,16 @@ } }, "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "optional": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { - "ansi-regex": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/strip-ansi": { @@ -11981,15 +12091,6 @@ "node": ">=8" } }, - "node_modules/swagger2openapi/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/swagger2openapi/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -12014,20 +12115,6 @@ "node": ">=8" } }, - "node_modules/swagger2openapi/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/swagger2openapi/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -12277,22 +12364,17 @@ } }, "node_modules/tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dependencies": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "^1.1.28", + "punycode": "^2.1.1" }, "engines": { "node": ">=0.8" } }, - "node_modules/tough-cookie/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, "node_modules/toxic": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toxic/-/toxic-1.0.1.tgz", @@ -12578,23 +12660,50 @@ "through": "^2.3.8" } }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/universal-analytics": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", - "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.5.3.tgz", + "integrity": "sha512-HXSMyIcf2XTvwZ6ZZQLfxfViRm/yTGoRgDeTbojtq6rezeyKB0sTBcKH2fhddnteAHRcHiKgr/ACpbgjGOC6RQ==", "dependencies": { - "debug": "^3.0.0", - "request": "^2.88.0", - "uuid": "^3.0.0" + "debug": "^4.3.1", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=12.18.2" } }, "node_modules/universal-analytics/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/universal-analytics/node_modules/ms": { @@ -12602,15 +12711,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/universal-analytics/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -12644,11 +12744,6 @@ "setimmediate": "~1.0.4" } }, - "node_modules/unzipper/node_modules/graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" - }, "node_modules/unzipper/node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -12797,14 +12892,6 @@ "node": ">=10" } }, - "node_modules/update-notifier/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==", - "engines": { - "node": ">=8" - } - }, "node_modules/update-notifier/node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -12835,27 +12922,14 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/update-notifier/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/update-notifier/node_modules/supports-color": { @@ -13058,27 +13132,6 @@ "node": ">=8" } }, - "node_modules/widest-line/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", @@ -13184,27 +13237,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/wrap-ansi/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13358,27 +13390,6 @@ "node": ">=8" } }, - "node_modules/yargs/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -13430,19 +13441,6 @@ "engines": { "node": ">= 10" } - }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } } }, "dependencies": { @@ -14341,6 +14339,12 @@ "integrity": "sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==", "dev": true }, + "@gar/promisify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz", + "integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==", + "optional": true + }, "@google-cloud/common": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", @@ -14731,6 +14735,45 @@ "fastq": "^1.6.0" } }, + "@npmcli/fs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.0.tgz", + "integrity": "sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA==", + "optional": true, + "requires": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "optional": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + } + } + }, "@opentelemetry/api": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz", @@ -15660,11 +15703,39 @@ } } }, + "agentkeepalive": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.0.tgz", + "integrity": "sha512-0PhAp58jZNw13UJv7NVdTGb0ZcghHUb3DrZ046JiiJY/BOaTTpbwdHq2VObPCBV8M2GPh7sgrJ3AQ8Ey468LJw==", + "optional": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, + "devOptional": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -15713,23 +15784,6 @@ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "requires": { "string-width": "^4.1.0" - }, - "dependencies": { - "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==" - }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } } }, "ansi-colors": { @@ -15780,9 +15834,9 @@ } }, "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "optional": true }, "archiver": { @@ -15803,16 +15857,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } } } }, @@ -15856,30 +15900,13 @@ "dev": true }, "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "optional": true, "requires": { "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "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": "^3.6.0" } }, "arg": { @@ -16169,21 +16196,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "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==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -16264,6 +16276,55 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "requires": { + "@npmcli/fs": "^1.0.0", + "@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": "^6.0.0", + "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.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "requires": { + "aggregate-error": "^3.0.0" + } + } + } + }, "cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -16436,7 +16497,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "devOptional": true }, "cli-boxes": { "version": "2.2.1", @@ -16490,23 +16551,6 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "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==" - }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } } }, "clone": { @@ -16534,12 +16578,6 @@ "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==", "dev": true }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true - }, "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", @@ -16571,6 +16609,12 @@ "simple-swizzle": "^0.2.2" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true + }, "colornames": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", @@ -16638,18 +16682,6 @@ "crc32-stream": "^4.0.1", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "compressible": { @@ -17179,6 +17211,26 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -17204,9 +17256,9 @@ "optional": true }, "env-paths": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "optional": true }, "env-variable": { @@ -17214,6 +17266,12 @@ "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -18393,50 +18451,28 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "optional": true, - "requires": { - "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" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } + "gauge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true } } }, @@ -18858,9 +18894,9 @@ } }, "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, "grapheme-splitter": { "version": "1.0.4", @@ -19093,6 +19129,15 @@ } } }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "optional": true, + "requires": { + "ms": "^2.0.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -19142,7 +19187,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "devOptional": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true }, "inflight": { "version": "1.0.6", @@ -19227,21 +19278,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "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==" - }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -19258,9 +19294,9 @@ } }, "install-artifact-from-github": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz", - "integrity": "sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.3.0.tgz", + "integrity": "sha512-iT8v1GwOAX0pPXifF/5ihnMhHOCo3OeK7z3TQa4CtSNCIg8k0UxqBEk9jRwz8OP68hHXvJ2gxRa89KYHtBkqGA==", "optional": true }, "ip": { @@ -19306,10 +19342,9 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "optional": true + "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==" }, "is-glob": { "version": "4.0.3", @@ -19333,6 +19368,12 @@ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", + "optional": true + }, "is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", @@ -20146,6 +20187,58 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "requires": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "socks-proxy-agent": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "optional": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + } + } + } + }, "map-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", @@ -20333,6 +20426,54 @@ "yallist": "^4.0.0" } }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "requires": { + "encoding": "^0.1.12", + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, "minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -20500,9 +20641,9 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "optional": true }, "nanoid": { @@ -20642,90 +20783,32 @@ "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, "node-gyp": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", - "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", "optional": true, "requires": { "env-paths": "^2.2.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.3", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", - "npmlog": "^4.1.2", - "request": "^2.88.2", + "npmlog": "^6.0.0", "rimraf": "^3.0.2", - "semver": "^7.3.2", - "tar": "^6.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", "which": "^2.0.2" }, "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "optional": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "optional": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "optional": true, - "requires": { - "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.3", - "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.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "optional": true - } - } - }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "optional": true, "requires": { "lru-cache": "^6.0.0" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -20787,23 +20870,17 @@ "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" }, "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", + "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true - }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -20885,12 +20962,6 @@ "path-exists": "^4.0.0" } }, - "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==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -20915,17 +20986,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -21530,12 +21590,28 @@ "resolved": "https://registry.npmjs.org/promise-breaker/-/promise-breaker-5.0.0.tgz", "integrity": "sha512-mgsWQuG4kJ1dtO6e/QlNDLFtMkMzzecsC69aI5hlLEjGHFNpHrvGhFi4LiK5jg2SMQj74/diH+wZliL9LpGsyA==" }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "optional": true + }, "promise-polyfill": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==", "dev": true }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, "propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -21663,9 +21739,9 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "psl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", - "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "pump": { "version": "3.0.0", @@ -21792,14 +21868,14 @@ } }, "re2": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/re2/-/re2-1.15.9.tgz", - "integrity": "sha512-AXWEhpMTBdC+3oqbjdU07dk0pBCvxh5vbOMLERL6Y8FYBSGn4vXlLe8cYszn64Yy7H8keVMrgPzoSvOd4mePpg==", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/re2/-/re2-1.17.3.tgz", + "integrity": "sha512-Dp5iWVR8W3C7Nm9DziMY4BleMPRb/pe6kvfbzLv80dVYaXRc9jRnwwNqU0oE/taRm0qYR1+Qrtzk9rPjS9ecaQ==", "optional": true, "requires": { - "install-artifact-from-github": "^1.2.0", - "nan": "^2.14.2", - "node-gyp": "^7.1.2" + "install-artifact-from-github": "^1.3.0", + "nan": "^2.15.0", + "node-gyp": "^8.4.1" } }, "read-pkg": { @@ -21864,9 +21940,9 @@ } }, "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -21957,9 +22033,9 @@ } }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -21968,7 +22044,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -21978,7 +22054,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, @@ -22043,6 +22119,12 @@ "signal-exit": "^3.0.2" } }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true + }, "retry-request": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", @@ -22523,6 +22605,15 @@ "tweetnacl": "~0.14.0" } }, + "ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "requires": { + "minipass": "^3.1.1" + } + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -22603,30 +22694,13 @@ } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "optional": true, + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { @@ -22978,12 +23052,6 @@ "path-exists": "^4.0.0" } }, - "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==", - "dev": true - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -23002,17 +23070,6 @@ "p-limit": "^2.2.0" } }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -23216,19 +23273,12 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "toxic": { @@ -23439,33 +23489,45 @@ "through": "^2.3.8" } }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, "universal-analytics": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", - "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.5.3.tgz", + "integrity": "sha512-HXSMyIcf2XTvwZ6ZZQLfxfViRm/yTGoRgDeTbojtq6rezeyKB0sTBcKH2fhddnteAHRcHiKgr/ACpbgjGOC6RQ==", "requires": { - "debug": "^3.0.0", - "request": "^2.88.0", - "uuid": "^3.0.0" + "debug": "^4.3.1", + "uuid": "^8.0.0" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" } } }, @@ -23496,11 +23558,6 @@ "setimmediate": "~1.0.4" }, "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" - }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -23606,11 +23663,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" }, - "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==" - }, "is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -23633,16 +23685,6 @@ "lru-cache": "^6.0.0" } }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23801,23 +23843,6 @@ "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "requires": { "string-width": "^4.0.0" - }, - "dependencies": { - "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==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - } } }, "winston": { @@ -23902,21 +23927,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "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==" - }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } } } }, @@ -23988,21 +23998,6 @@ "yargs-parser": "^20.2.2" }, "dependencies": { - "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==" - }, - "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==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -24077,18 +24072,6 @@ "archiver-utils": "^2.1.0", "compress-commons": "^4.0.2", "readable-stream": "^3.6.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } } } diff --git a/package.json b/package.json index 92c28dbde14..4469b86f200 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "tmp": "0.0.33", "triple-beam": "^1.3.0", "tweetsodium": "0.0.5", - "universal-analytics": "^0.4.16", + "universal-analytics": "^0.5.3", "unzipper": "^0.10.10", "update-notifier": "^5.1.0", "uuid": "^8.3.2", From 58cd286eb3d1d9e0adc375e80a1202146d7bf52e Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Fri, 21 Jan 2022 11:28:13 -0600 Subject: [PATCH 0043/1699] fixes javascript in hosting:init template (#4032) --- CHANGELOG.md | 1 + templates/init/hosting/index.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..1343034ef17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Updates Hosting initial `index.html` to be proper javascript. diff --git a/templates/init/hosting/index.html b/templates/init/hosting/index.html index daca6dda5d8..60e92365e8a 100644 --- a/templates/init/hosting/index.html +++ b/templates/init/hosting/index.html @@ -79,7 +79,7 @@

Firebase Hosting Setup Complete

'performance', ].filter(feature => typeof app[feature] === 'function'); loadEl.textContent = `Firebase SDK loaded with ${features.join(', ')}`; - } catch (e: any) { + } catch (e) { console.error(e); loadEl.textContent = 'Error loading the Firebase SDK, check the console.'; } From 89b79f22757fcdee1a64063af9ff559d8770fe55 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 24 Jan 2022 16:50:54 -0800 Subject: [PATCH 0044/1699] apiv2: add support for retry codes (#3996) * add support for retry codes * add retry logic to apiv2 * replace parse with new URL - parse is deprecated * prefix api logging * un-only the tests * add comment about consolidating Throttler and retry * migrate extensions to apiv2 (#3995) * migrate extensions to apiv2 * more typing plz * migrate remoteconfig over to apiv2 (#3720) * migrate remoteconfig over to apiv2 * format datetimes better * 10.1.2 * [firebase-release] Removed change log and reset repo after 10.1.2 release * database management API client (#4030) * apiv2 for database rules management * remove commented out stuff * `request` dependency of dependencies... BEGONE! (#4029) * upgrade the re2 dependency in superstatic to remove request * upgrade universal-analytics to remove the last instance of request Co-authored-by: Google Open Source Bot --- npm-shrinkwrap.json | 28 +++++--- package.json | 2 + src/apiv2.ts | 150 +++++++++++++++++++++++++++-------------- src/test/apiv2.spec.ts | 50 ++++++++++++++ 4 files changed, 173 insertions(+), 57 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0a64e3b75c5..e40fefb2061 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -50,6 +50,7 @@ "progress": "^2.0.3", "proxy-agent": "^5.0.0", "request": "^2.87.0", + "retry": "^0.13.1", "rimraf": "^3.0.0", "semver": "^5.7.1", "stream-chain": "^2.2.4", @@ -102,6 +103,7 @@ "@types/progress": "^2.0.3", "@types/puppeteer": "^5.4.2", "@types/request": "^2.48.1", + "@types/retry": "^0.12.1", "@types/rimraf": "^2.0.3", "@types/semver": "^6.0.0", "@types/sinon": "^9.0.10", @@ -2204,6 +2206,12 @@ "node": ">= 0.12" } }, + "node_modules/@types/retry": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", + "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", + "dev": true + }, "node_modules/@types/rimraf": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.3.tgz", @@ -10931,10 +10939,9 @@ } }, "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "optional": true, + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "engines": { "node": ">= 4" } @@ -15276,6 +15283,12 @@ } } }, + "@types/retry": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", + "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", + "dev": true + }, "@types/rimraf": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.3.tgz", @@ -22120,10 +22133,9 @@ } }, "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "optional": true + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" }, "retry-request": { "version": "4.1.3", diff --git a/package.json b/package.json index 4469b86f200..c092e7ec7a4 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "progress": "^2.0.3", "proxy-agent": "^5.0.0", "request": "^2.87.0", + "retry": "^0.13.1", "rimraf": "^3.0.0", "semver": "^5.7.1", "stream-chain": "^2.2.4", @@ -174,6 +175,7 @@ "@types/progress": "^2.0.3", "@types/puppeteer": "^5.4.2", "@types/request": "^2.48.1", + "@types/retry": "^0.12.1", "@types/rimraf": "^2.0.3", "@types/semver": "^6.0.0", "@types/sinon": "^9.0.10", diff --git a/src/apiv2.ts b/src/apiv2.ts index 3112ed3367f..a2bd4488cf0 100644 --- a/src/apiv2.ts +++ b/src/apiv2.ts @@ -1,7 +1,8 @@ import { AbortSignal } from "abort-controller"; +import { URL, URLSearchParams } from "url"; import { Readable } from "stream"; -import { parse, URLSearchParams } from "url"; import * as ProxyAgent from "proxy-agent"; +import * as retry from "retry"; import AbortController from "abort-controller"; import fetch, { HeadersInit, Response, RequestInit, Headers } from "node-fetch"; import util from "util"; @@ -54,6 +55,14 @@ interface ClientHandlingOptions { resBody?: boolean; }; resolveOnHTTPError?: boolean; + /** Codes on which to retry. Defaults to none. */ + retryCodes?: number[]; + /** Number of retries. Defaults to 0 (one attempt) with no retryCodes, 1 with retryCodes. */ + retries?: number; + /** Minimum timeout between retries. Defaults to 1s. */ + retryMinTimeout?: number; + /** Maximum timeout between retries. Defaults to 5s. */ + retryMaxTimeout?: number; } export type ClientRequestOptions = RequestOptions & ClientVerbOptions; @@ -342,57 +351,100 @@ export class Client { fetchOptions.body = JSON.stringify(options.body); } - this.logRequest(options); - - let res: Response; - try { - res = await fetch(fetchURL, fetchOptions); - } catch (thrown: any) { - const err = thrown instanceof Error ? thrown : new Error(thrown); - const isAbortError = err.name.includes("AbortError"); - if (isAbortError) { - throw new FirebaseError(`Timeout reached making request to ${fetchURL}`, { original: err }); - } - throw new FirebaseError(`Failed to make request to ${fetchURL}`, { original: err }); - } finally { - // If we succeed or failed, clear the timeout. - if (reqTimeout) { - clearTimeout(reqTimeout); - } + // TODO(bkendall): Refactor this to use Throttler _or_ refactor Throttle to use `retry`. + const operationOptions: retry.OperationOptions = { + retries: options.retryCodes?.length ? 1 : 2, + minTimeout: 1 * 1000, + maxTimeout: 5 * 1000, + }; + if (typeof options.retries === "number") { + operationOptions.retries = options.retries; } - - let body: ResT; - if (options.responseType === "json") { - const text = await res.text(); - // Some responses, such as 204 and occasionally 202s don't have - // any content. We can't just rely on response code (202 may have conent) - // and unfortuantely res.length is unreliable (many requests return zero). - if (!text.length) { - body = undefined as unknown as ResT; - } else { - body = JSON.parse(text) as ResT; - } - } else if (options.responseType === "stream") { - body = res.body as unknown as ResT; - } else { - throw new FirebaseError(`Unable to interpret response. Please set responseType.`, { - exit: 2, - }); + if (typeof options.retryMinTimeout === "number") { + operationOptions.minTimeout = options.retryMinTimeout; } - - this.logResponse(res, body, options); - - if (res.status >= 400) { - if (!options.resolveOnHTTPError) { - throw responseToError({ statusCode: res.status }, body); - } + if (typeof options.retryMaxTimeout === "number") { + operationOptions.maxTimeout = options.retryMaxTimeout; } + const operation = retry.operation(operationOptions); + + return await new Promise>((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + operation.attempt(async (currentAttempt): Promise => { + let res: Response; + let body: ResT; + try { + if (currentAttempt > 1) { + logger.debug( + `*** [apiv2] Attempting the request again. Attempt number ${currentAttempt}` + ); + } + this.logRequest(options); + + try { + res = await fetch(fetchURL, fetchOptions); + } catch (thrown: any) { + const err = thrown instanceof Error ? thrown : new Error(thrown); + const isAbortError = err.name.includes("AbortError"); + if (isAbortError) { + throw new FirebaseError(`Timeout reached making request to ${fetchURL}`, { + original: err, + }); + } + throw new FirebaseError(`Failed to make request to ${fetchURL}`, { original: err }); + } finally { + // If we succeed or failed, clear the timeout. + if (reqTimeout) { + clearTimeout(reqTimeout); + } + } + + if (options.responseType === "json") { + const text = await res.text(); + // Some responses, such as 204 and occasionally 202s don't have + // any content. We can't just rely on response code (202 may have conent) + // and unfortuantely res.length is unreliable (many requests return zero). + if (!text.length) { + body = undefined as unknown as ResT; + } else { + try { + body = JSON.parse(text) as ResT; + } catch (err: unknown) { + throw new FirebaseError(`Unable to parse JSON: ${err}`); + } + } + } else if (options.responseType === "stream") { + body = res.body as unknown as ResT; + } else { + throw new FirebaseError(`Unable to interpret response. Please set responseType.`, { + exit: 2, + }); + } + } catch (err: unknown) { + return err instanceof FirebaseError ? reject(err) : reject(new FirebaseError(`${err}`)); + } - return { - status: res.status, - response: res, - body, - }; + this.logResponse(res, body, options); + + if (res.status >= 400) { + if (options.retryCodes?.includes(res.status)) { + const err = responseToError({ statusCode: res.status }, body) || undefined; + if (operation.retry(err)) { + return; + } + } + if (!options.resolveOnHTTPError) { + return reject(responseToError({ statusCode: res.status }, body)); + } + } + + resolve({ + status: res.status, + response: res, + body, + }); + }); + }); } private logRequest(options: InternalClientRequestOptions): void { @@ -433,7 +485,7 @@ export class Client { } function isLocalInsecureRequest(urlPrefix: string): boolean { - const u = parse(urlPrefix); + const u = new URL(urlPrefix); return u.protocol === "http:"; } diff --git a/src/test/apiv2.spec.ts b/src/test/apiv2.spec.ts index add4299c9d8..6a67f3c84c7 100644 --- a/src/test/apiv2.spec.ts +++ b/src/test/apiv2.spec.ts @@ -59,6 +59,56 @@ describe("apiv2", () => { expect(nock.isDone()).to.be.true; }); + it("should be able to handle specified retry codes", async () => { + nock("https://example.com").get("/path/to/foo").once().reply(503, {}); + nock("https://example.com").get("/path/to/foo").once().reply(200, { foo: "bar" }); + + const c = new Client({ urlPrefix: "https://example.com" }); + const r = await c.request({ + method: "GET", + path: "/path/to/foo", + retryCodes: [503], + retries: 1, + retryMinTimeout: 10, + retryMaxTimeout: 15, + }); + expect(r.body).to.deep.equal({ foo: "bar" }); + expect(nock.isDone()).to.be.true; + }); + + it("should return an error if the retry never succeeds", async () => { + nock("https://example.com").get("/path/to/foo").twice().reply(503, {}); + + const c = new Client({ urlPrefix: "https://example.com" }); + const r = c.request({ + method: "GET", + path: "/path/to/foo", + retryCodes: [503], + retries: 1, + retryMinTimeout: 10, + retryMaxTimeout: 15, + }); + await expect(r).to.eventually.be.rejectedWith(FirebaseError, /503.+Error/); + expect(nock.isDone()).to.be.true; + }); + + it("should be able to resolve the error response if retry codes never succeed", async () => { + nock("https://example.com").get("/path/to/foo").twice().reply(503, {}); + + const c = new Client({ urlPrefix: "https://example.com" }); + const r = await c.request({ + method: "GET", + path: "/path/to/foo", + resolveOnHTTPError: true, + retryCodes: [503], + retries: 1, + retryMinTimeout: 10, + retryMaxTimeout: 15, + }); + expect(r.status).to.equal(503); + expect(nock.isDone()).to.be.true; + }); + it("should not allow resolving on http error when streaming", async () => { const c = new Client({ urlPrefix: "https://example.com" }); const r = c.request({ From abf01b3d4e188d3fefd183963b9ed12234df567b Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Tue, 25 Jan 2022 11:18:26 -0500 Subject: [PATCH 0045/1699] ext:dev:usage - Remove cloud monitoring link and increase buffer period (#4038) --- src/commands/ext-dev-usage.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/commands/ext-dev-usage.ts b/src/commands/ext-dev-usage.ts index 548777a4fb6..314bb4d3560 100644 --- a/src/commands/ext-dev-usage.ts +++ b/src/commands/ext-dev-usage.ts @@ -70,8 +70,8 @@ module.exports = new Command("ext:dev:usage ") const projectNumber = getPublisherProjectFromName(profile.name); - const past30d = new Date(); - past30d.setDate(past30d.getDate() - 30); + const past45d = new Date(); + past45d.setDate(past45d.getDate() - 45); const query: CmQuery = { filter: @@ -79,7 +79,7 @@ module.exports = new Command("ext:dev:usage ") `resource.type="firebaseextensions.googleapis.com/ExtensionVersion" ` + `resource.labels.extension="${extensionName}"`, "interval.endTime": new Date().toJSON(), - "interval.startTime": past30d.toJSON(), + "interval.startTime": past45d.toJSON(), view: TimeSeriesView.FULL, "aggregation.alignmentPeriod": (60 * 60 * 24).toString() + "s", "aggregation.perSeriesAligner": Aligner.ALIGN_MAX, @@ -117,18 +117,13 @@ module.exports = new Command("ext:dev:usage ") logger.info(table.toString()); - const link = await buildCloudMonitoringLink({ - projectNumber: projectNumber, - extensionName, - }); - utils.logLabeledBullet(logPrefix, `How to read this table:`); logger.info(`* Due to privacy considerations, numbers are reported as ranges.`); logger.info(`* In the absence of significant changes, we will render a '-' symbol.`); logger.info( `* You will need more than 10 installs over a period of more than 28 days to render sufficient data.` ); - logger.info(`For more detail, visit: ${link}`); + // TODO(b/216289102): Add buildCloudMonitoringLink back after UI is fixed. }); async function buildCloudMonitoringLink(args: { From 7cd410db53742374f178f73fbdea673143d3db68 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Tue, 25 Jan 2022 13:32:05 -0500 Subject: [PATCH 0046/1699] ext:dev:list - Clean up unused showUnpublished queryParams (#4039) --- src/extensions/extensionsApi.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index 8b88755406c..e6b37b9494c 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -516,7 +516,6 @@ export async function getExtensionVersion(extensionVersionRef: string): Promise< /** * @param publisherId the publisher for which we are listing Extensions - * @param showUnpublished whether to include unpublished Extensions, default = false */ export async function listExtensions(publisherId: string): Promise { const extensions: Extension[] = []; @@ -525,7 +524,6 @@ export async function listExtensions(publisherId: string): Promise `/publishers/${publisherId}/extensions`, { queryParams: { - showUnpublished: "false", pageSize: PAGE_SIZE_MAX, pageToken, }, @@ -544,7 +542,6 @@ export async function listExtensions(publisherId: string): Promise /** * @param ref user-friendly identifier for the ExtensionVersion (publisher-id/extension-id) - * @param showUnpublished whether to include unpublished ExtensionVersions, default = false */ export async function listExtensionVersions(ref: string, filter = ""): Promise { const { publisherId, extensionId } = refs.parse(ref); From 19dd831de0e20b48788135438410b7037a82a472 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 25 Jan 2022 11:55:07 -0800 Subject: [PATCH 0047/1699] migrate cloudbilling and functions to apiv2 (#3997) * add support for retry codes * migrate cloudbilling and functions to apiv2 * clean up a bit of code * formatting is hard --- src/gcp/cloudbilling.ts | 39 ++-- src/gcp/cloudfunctions.ts | 91 ++++------ src/test/gcp/cloudfunctions.spec.ts | 270 ++++++++++++++-------------- 3 files changed, 190 insertions(+), 210 deletions(-) diff --git a/src/gcp/cloudbilling.ts b/src/gcp/cloudbilling.ts index 6bfe67b909d..d37b76f13ce 100644 --- a/src/gcp/cloudbilling.ts +++ b/src/gcp/cloudbilling.ts @@ -1,7 +1,9 @@ -import * as api from "../api"; +import { cloudbillingOrigin } from "../api"; +import { Client } from "../apiv2"; import * as utils from "../utils"; const API_VERSION = "v1"; +const client = new Client({ urlPrefix: cloudbillingOrigin, apiVersion: API_VERSION }); export interface BillingAccount { name: string; @@ -14,14 +16,9 @@ export interface BillingAccount { * @param projectId */ export async function checkBillingEnabled(projectId: string): Promise { - const res = await api.request( - "GET", - utils.endpoint([API_VERSION, "projects", projectId, "billingInfo"]), - { - auth: true, - origin: api.cloudbillingOrigin, - retryCodes: [500, 503], - } + const res = await client.get<{ billingEnabled: boolean }>( + utils.endpoint(["projects", projectId, "billingInfo"]), + { retryCodes: [500, 503] } ); return res.body.billingEnabled; } @@ -35,17 +32,12 @@ export async function setBillingAccount( projectId: string, billingAccountName: string ): Promise { - const res = await api.request( - "PUT", - utils.endpoint([API_VERSION, "projects", projectId, "billingInfo"]), + const res = await client.put<{ billingAccountName: string }, { billingEnabled: boolean }>( + utils.endpoint(["projects", projectId, "billingInfo"]), { - auth: true, - origin: api.cloudbillingOrigin, - retryCodes: [500, 503], - data: { - billingAccountName: billingAccountName, - }, - } + billingAccountName: billingAccountName, + }, + { retryCodes: [500, 503] } ); return res.body.billingEnabled; } @@ -55,10 +47,9 @@ export async function setBillingAccount( * @return {!Promise} */ export async function listBillingAccounts(): Promise { - const res = await api.request("GET", utils.endpoint([API_VERSION, "billingAccounts"]), { - auth: true, - origin: api.cloudbillingOrigin, - retryCodes: [500, 503], - }); + const res = await client.get<{ billingAccounts: BillingAccount[] }>( + utils.endpoint(["billingAccounts"]), + { retryCodes: [500, 503] } + ); return res.body.billingAccounts || []; } diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 63859131479..4f18d5071cc 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -3,14 +3,16 @@ import * as clc from "cli-color"; import { FirebaseError } from "../error"; import { logger } from "../logger"; import { previews } from "../previews"; -import * as api from "../api"; import * as backend from "../deploy/functions/backend"; import * as utils from "../utils"; import * as proto from "./proto"; import * as runtimes from "../deploy/functions/runtimes"; import * as iam from "./iam"; +import { Client } from "../apiv2"; +import { functionsOrigin } from "../api"; export const API_VERSION = "v1"; +const client = new Client({ urlPrefix: functionsOrigin, apiVersion: API_VERSION }); interface Operation { name: string; @@ -174,17 +176,15 @@ function functionsOpLogReject(funcName: string, type: string, err: any): void { */ export async function generateUploadUrl(projectId: string, location: string): Promise { const parent = "projects/" + projectId + "/locations/" + location; - const endpoint = "/" + API_VERSION + "/" + parent + "/functions:generateUploadUrl"; + const endpoint = `/${parent}/functions:generateUploadUrl`; try { - const res = await api.request("POST", endpoint, { - auth: true, - json: false, - origin: api.functionsOrigin, - retryCodes: [503], - }); - const responseBody = JSON.parse(res.body); - return responseBody.uploadUrl; + const res = await client.post( + endpoint, + {}, + { retryCodes: [503] } + ); + return res.body.uploadUrl; } catch (err: any) { logger.info( "\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support." @@ -202,19 +202,18 @@ export async function createFunction( ): Promise { // the API is a POST to the collection that owns the function name. const apiPath = cloudFunction.name.substring(0, cloudFunction.name.lastIndexOf("/")); - const endpoint = `/${API_VERSION}/${apiPath}`; + const endpoint = `/${apiPath}`; try { const headers: Record = {}; if (previews.artifactregistry) { headers["X-Firebase-Artifact-Registry"] = "optin"; } - const res = await api.request("POST", endpoint, { - headers, - auth: true, - data: cloudFunction, - origin: api.functionsOrigin, - }); + const res = await client.post, CloudFunction>( + endpoint, + cloudFunction, + { headers } + ); return { name: res.body.name, type: "create", @@ -239,16 +238,12 @@ interface IamOptions { * @param options The Iam options to set. */ export async function setIamPolicy(options: IamOptions): Promise { - const endpoint = `/${API_VERSION}/${options.name}:setIamPolicy`; + const endpoint = `/${options.name}:setIamPolicy`; try { - await api.request("POST", endpoint, { - auth: true, - data: { - policy: options.policy, - updateMask: Object.keys(options.policy).join(","), - }, - origin: api.functionsOrigin, + await client.post(endpoint, { + policy: options.policy, + updateMask: Object.keys(options.policy).join(","), }); } catch (err: any) { throw new FirebaseError(`Failed to set the IAM Policy on the function ${options.name}`, { @@ -269,13 +264,11 @@ interface GetIamPolicy { * @param fnName The full name and path of the Cloud Function. */ export async function getIamPolicy(fnName: string): Promise { - const endpoint = `/${API_VERSION}/${fnName}:getIamPolicy`; + const endpoint = `/${fnName}:getIamPolicy`; try { - return await api.request("GET", endpoint, { - auth: true, - origin: api.functionsOrigin, - }); + const res = await client.get(endpoint); + return res.body; } catch (err: any) { throw new FirebaseError(`Failed to get the IAM Policy on the function ${fnName}`, { original: err, @@ -288,7 +281,6 @@ export async function getIamPolicy(fnName: string): Promise { * @param projectId id of the project * @param fnName function name * @param invoker an array of invoker strings - * * @throws {@link FirebaseError} on an empty invoker, when the IAM Polciy fails to be grabbed or set */ export async function setInvokerCreate( @@ -317,7 +309,6 @@ export async function setInvokerCreate( * @param projectId id of the project * @param fnName function name * @param invoker an array of invoker strings - * * @throws {@link FirebaseError} on an empty invoker, when the IAM Polciy fails to be grabbed or set */ export async function setInvokerUpdate( @@ -362,7 +353,7 @@ export async function setInvokerUpdate( export async function updateFunction( cloudFunction: Omit ): Promise { - const endpoint = `/${API_VERSION}/${cloudFunction.name}`; + const endpoint = `/${cloudFunction.name}`; // Keys in labels and environmentVariables are user defined, so we don't recurse // for field masks. const fieldMasks = proto.fieldMasks( @@ -378,15 +369,16 @@ export async function updateFunction( if (previews.artifactregistry) { headers["X-Firebase-Artifact-Registry"] = "optin"; } - const res = await api.request("PATCH", endpoint, { - headers, - qs: { - updateMask: fieldMasks.join(","), - }, - auth: true, - data: cloudFunction, - origin: api.functionsOrigin, - }); + const res = await client.patch, CloudFunction>( + endpoint, + cloudFunction, + { + headers, + queryParams: { + updateMask: fieldMasks.join(","), + }, + } + ); return { done: false, name: res.body.name, @@ -402,12 +394,9 @@ export async function updateFunction( * @param options the Cloud Function to delete. */ export async function deleteFunction(name: string): Promise { - const endpoint = `/${API_VERSION}/${name}`; + const endpoint = `/${name}`; try { - const res = await api.request("DELETE", endpoint, { - auth: true, - origin: api.functionsOrigin, - }); + const res = await client.delete(endpoint); return { done: false, name: res.body.name, @@ -424,13 +413,9 @@ export type ListFunctionsResponse = { }; async function list(projectId: string, region: string): Promise { - const endpoint = - "/" + API_VERSION + "/projects/" + projectId + "/locations/" + region + "/functions"; + const endpoint = "/projects/" + projectId + "/locations/" + region + "/functions"; try { - const res = await api.request("GET", endpoint, { - auth: true, - origin: api.functionsOrigin, - }); + const res = await client.get(endpoint); if (res.body.unreachable && res.body.unreachable.length > 0) { logger.debug( `[functions] unable to reach the following regions: ${res.body.unreachable.join(", ")}` diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index d29d0c190a1..8e7182e8830 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; -import * as sinon from "sinon"; -import * as api from "../../api"; +import * as nock from "nock"; + +import { functionsOrigin } from "../../api"; import * as backend from "../../deploy/functions/backend"; import * as cloudfunctions from "../../gcp/cloudfunctions"; @@ -34,6 +35,15 @@ describe("cloudfunctions", () => { status: "ACTIVE", }; + before(() => { + nock.disableNetConnect(); + }); + + after(() => { + expect(nock.isDone()).to.be.true; + nock.enableNetConnect(); + }); + describe("functionFromEndpoint", () => { const UPLOAD_URL = "https://storage.googleapis.com/projects/-/buckets/sample/source.zip"; it("should guard against version mixing", () => { @@ -298,84 +308,79 @@ describe("cloudfunctions", () => { }); describe("setInvokerCreate", () => { - let sandbox: sinon.SinonSandbox; - let apiRequestStub: sinon.SinonStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - apiRequestStub = sandbox.stub(api, "request").throws("Unexpected API request call"); - }); - - afterEach(() => { - sandbox.restore(); - }); - it("should reject on emtpy invoker array", async () => { await expect(cloudfunctions.setInvokerCreate("project", "function", [])).to.be.rejected; }); it("should reject if the setting the IAM policy fails", async () => { - apiRequestStub.onFirstCall().throws("Error calling set api."); + nock(functionsOrigin) + .post("/v1/function:setIamPolicy", { + policy: { + bindings: [{ role: "roles/cloudfunctions.invoker", members: ["allUsers"] }], + etag: "", + version: 3, + }, + updateMask: "bindings,etag,version", + }) + .reply(418, {}); await expect( cloudfunctions.setInvokerCreate("project", "function", ["public"]) ).to.be.rejectedWith("Failed to set the IAM Policy on the function function"); - expect(apiRequestStub).to.be.calledOnce; }); it("should set a private policy on a function", async () => { - apiRequestStub.onFirstCall().callsFake((method: any, resource: any, options: any) => { - expect(options.data.policy).to.deep.eq({ - bindings: [ - { - role: "roles/cloudfunctions.invoker", - members: [], - }, - ], - etag: "", - version: 3, - }); - - return Promise.resolve(); - }); + nock(functionsOrigin) + .post("/v1/function:setIamPolicy", { + policy: { + bindings: [{ role: "roles/cloudfunctions.invoker", members: [] }], + etag: "", + version: 3, + }, + updateMask: "bindings,etag,version", + }) + .reply(200, {}); await expect(cloudfunctions.setInvokerCreate("project", "function", ["private"])).to.not.be .rejected; - expect(apiRequestStub).to.be.calledOnce; }); it("should set a public policy on a function", async () => { - apiRequestStub.onFirstCall().callsFake((method: any, resource: any, options: any) => { - expect(options.data.policy).to.deep.eq({ - bindings: [ - { - role: "roles/cloudfunctions.invoker", - members: ["allUsers"], - }, - ], - etag: "", - version: 3, - }); - - return Promise.resolve(); - }); + nock(functionsOrigin) + .post("/v1/function:setIamPolicy", { + policy: { + bindings: [{ role: "roles/cloudfunctions.invoker", members: ["allUsers"] }], + etag: "", + version: 3, + }, + updateMask: "bindings,etag,version", + }) + .reply(200, {}); await expect(cloudfunctions.setInvokerCreate("project", "function", ["public"])).to.not.be .rejected; - expect(apiRequestStub).to.be.calledOnce; }); it("should set the policy with a set of invokers with active policies", async () => { - apiRequestStub.onFirstCall().callsFake((method: any, resource: any, options: any) => { - options.data.policy.bindings[0].members.sort(); - expect(options.data.policy.bindings[0].members).to.deep.eq([ - "serviceAccount:service-account1@project.iam.gserviceaccount.com", - "serviceAccount:service-account2@project.iam.gserviceaccount.com", - "serviceAccount:service-account3@project.iam.gserviceaccount.com", - ]); - - return Promise.resolve(); - }); + nock(functionsOrigin) + .post("/v1/function:setIamPolicy", { + policy: { + bindings: [ + { + role: "roles/cloudfunctions.invoker", + members: [ + "serviceAccount:service-account1@project.iam.gserviceaccount.com", + "serviceAccount:service-account2@project.iam.gserviceaccount.com", + "serviceAccount:service-account3@project.iam.gserviceaccount.com", + ], + }, + ], + etag: "", + version: 3, + }, + updateMask: "bindings,etag,version", + }) + .reply(200, {}); await expect( cloudfunctions.setInvokerCreate("project", "function", [ @@ -384,108 +389,107 @@ describe("cloudfunctions", () => { "service-account3@", ]) ).to.not.be.rejected; - expect(apiRequestStub).to.be.calledOnce; }); }); describe("setInvokerUpdate", () => { - let sandbox: sinon.SinonSandbox; - let apiRequestStub: sinon.SinonStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - apiRequestStub = sandbox.stub(api, "request").throws("Unexpected API request call"); - }); - - afterEach(() => { - sandbox.restore(); - }); - it("should reject on emtpy invoker array", async () => { await expect(cloudfunctions.setInvokerUpdate("project", "function", [])).to.be.rejected; }); it("should reject if the getting the IAM policy fails", async () => { - apiRequestStub.onFirstCall().throws("Error calling get api."); + nock(functionsOrigin).get("/v1/function:getIamPolicy").reply(404, {}); await expect( cloudfunctions.setInvokerUpdate("project", "function", ["public"]) ).to.be.rejectedWith("Failed to get the IAM Policy on the function function"); - - expect(apiRequestStub).to.be.called; }); it("should reject if the setting the IAM policy fails", async () => { - apiRequestStub.onFirstCall().resolves({}); - apiRequestStub.onSecondCall().throws("Error calling set api."); + nock(functionsOrigin).get("/v1/function:getIamPolicy").reply(200, {}); + nock(functionsOrigin) + .post("/v1/function:setIamPolicy", { + policy: { + bindings: [{ role: "roles/cloudfunctions.invoker", members: ["allUsers"] }], + etag: "", + version: 3, + }, + updateMask: "bindings,etag,version", + }) + .reply(418, {}); await expect( cloudfunctions.setInvokerUpdate("project", "function", ["public"]) ).to.be.rejectedWith("Failed to set the IAM Policy on the function function"); - expect(apiRequestStub).to.be.calledTwice; }); it("should set a basic policy on a function without any polices", async () => { - apiRequestStub.onFirstCall().resolves({}); - apiRequestStub.onSecondCall().callsFake((method: any, resource: any, options: any) => { - expect(options.data.policy).to.deep.eq({ - bindings: [ - { - role: "roles/cloudfunctions.invoker", - members: ["allUsers"], - }, - ], - etag: "", - version: 3, - }); - - return Promise.resolve(); - }); + nock(functionsOrigin).get("/v1/function:getIamPolicy").reply(200, {}); + nock(functionsOrigin) + .post("/v1/function:setIamPolicy", { + policy: { + bindings: [{ role: "roles/cloudfunctions.invoker", members: ["allUsers"] }], + etag: "", + version: 3, + }, + updateMask: "bindings,etag,version", + }) + .reply(200, {}); await expect(cloudfunctions.setInvokerUpdate("project", "function", ["public"])).to.not.be .rejected; - expect(apiRequestStub).to.be.calledTwice; }); it("should set the policy with private invoker with active policies", async () => { - apiRequestStub.onFirstCall().resolves({ - bindings: [ - { role: "random-role", members: ["user:pineapple"] }, - { role: "roles/cloudfunctions.invoker", members: ["some-service-account"] }, - ], - etag: "1234", - version: 3, - }); - apiRequestStub.onSecondCall().callsFake((method: any, resource: any, options: any) => { - expect(options.data.policy).to.deep.eq({ + nock(functionsOrigin) + .get("/v1/function:getIamPolicy") + .reply(200, { bindings: [ { role: "random-role", members: ["user:pineapple"] }, - { role: "roles/cloudfunctions.invoker", members: [] }, + { role: "roles/cloudfunctions.invoker", members: ["some-service-account"] }, ], etag: "1234", version: 3, }); - - return Promise.resolve(); - }); + nock(functionsOrigin) + .post("/v1/function:setIamPolicy", { + policy: { + bindings: [ + { role: "random-role", members: ["user:pineapple"] }, + { role: "roles/cloudfunctions.invoker", members: [] }, + ], + etag: "1234", + version: 3, + }, + updateMask: "bindings,etag,version", + }) + .reply(200, {}); await expect(cloudfunctions.setInvokerUpdate("project", "function", ["private"])).to.not.be .rejected; - expect(apiRequestStub).to.be.calledTwice; }); it("should set the policy with a set of invokers with active policies", async () => { - apiRequestStub.onFirstCall().resolves({}); - apiRequestStub.onSecondCall().callsFake((method: any, resource: any, options: any) => { - options.data.policy.bindings[0].members.sort(); - expect(options.data.policy.bindings[0].members).to.deep.eq([ - "serviceAccount:service-account1@project.iam.gserviceaccount.com", - "serviceAccount:service-account2@project.iam.gserviceaccount.com", - "serviceAccount:service-account3@project.iam.gserviceaccount.com", - ]); - - return Promise.resolve(); - }); + nock(functionsOrigin).get("/v1/function:getIamPolicy").reply(200, {}); + nock(functionsOrigin) + .post("/v1/function:setIamPolicy", { + policy: { + bindings: [ + { + role: "roles/cloudfunctions.invoker", + members: [ + "serviceAccount:service-account1@project.iam.gserviceaccount.com", + "serviceAccount:service-account2@project.iam.gserviceaccount.com", + "serviceAccount:service-account3@project.iam.gserviceaccount.com", + ], + }, + ], + etag: "", + version: 3, + }, + updateMask: "bindings,etag,version", + }) + .reply(200, {}); await expect( cloudfunctions.setInvokerUpdate("project", "function", [ @@ -494,24 +498,25 @@ describe("cloudfunctions", () => { "service-account3@", ]) ).to.not.be.rejected; - expect(apiRequestStub).to.be.calledTwice; }); it("should not set the policy if the set of invokers is the same as the current invokers", async () => { - apiRequestStub.onFirstCall().resolves({ - bindings: [ - { - role: "roles/cloudfunctions.invoker", - members: [ - "serviceAccount:service-account1@project.iam.gserviceaccount.com", - "serviceAccount:service-account3@project.iam.gserviceaccount.com", - "serviceAccount:service-account2@project.iam.gserviceaccount.com", - ], - }, - ], - etag: "1234", - version: 3, - }); + nock(functionsOrigin) + .get("/v1/function:getIamPolicy") + .reply(200, { + bindings: [ + { + role: "roles/cloudfunctions.invoker", + members: [ + "serviceAccount:service-account1@project.iam.gserviceaccount.com", + "serviceAccount:service-account3@project.iam.gserviceaccount.com", + "serviceAccount:service-account2@project.iam.gserviceaccount.com", + ], + }, + ], + etag: "1234", + version: 3, + }); await expect( cloudfunctions.setInvokerUpdate("project", "function", [ @@ -520,7 +525,6 @@ describe("cloudfunctions", () => { "service-account1@", ]) ).to.not.be.rejected; - expect(apiRequestStub).to.be.calledOnce; }); }); }); From 3f915f4da6c151b44be6978a0a803faa395ee265 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 26 Jan 2022 10:52:45 -0800 Subject: [PATCH 0048/1699] migrate all of GCP folder to apiv2 (#4031) * migrate gcp folder to apiv2 * update runtimeconfig without converting to ts * clean up comments and things * clean up comments and things * remove TODOs now --- src/gcp/cloudlogging.ts | 22 ++++----- src/gcp/cloudmonitoring.ts | 16 ++++--- src/gcp/cloudscheduler.ts | 26 ++++------- src/gcp/firedata.ts | 14 ++++-- src/gcp/firestore.ts | 10 ++-- src/gcp/iam.ts | 86 ++++++++++++++-------------------- src/gcp/resourceManager.ts | 23 +++++---- src/gcp/runtimeconfig.js | 96 +++++++++++++++++--------------------- src/gcp/secretManager.ts | 90 ++++++++++++++--------------------- src/gcp/storage.ts | 56 +++++++++++----------- 10 files changed, 191 insertions(+), 248 deletions(-) diff --git a/src/gcp/cloudlogging.ts b/src/gcp/cloudlogging.ts index adc83e3c776..a37925d5832 100644 --- a/src/gcp/cloudlogging.ts +++ b/src/gcp/cloudlogging.ts @@ -1,4 +1,5 @@ -import * as api from "../api"; +import { cloudloggingOrigin } from "../api"; +import { Client } from "../apiv2"; import { FirebaseError } from "../error"; const API_VERSION = "v2"; @@ -32,17 +33,16 @@ export async function listEntries( pageSize: number, order: string ): Promise { - const endpoint = `/${API_VERSION}/entries:list`; + const client = new Client({ urlPrefix: cloudloggingOrigin, apiVersion: API_VERSION }); try { - const result = await api.request("POST", endpoint, { - auth: true, - data: { - resourceNames: [`projects/${projectId}`], - filter: filter, - orderBy: "timestamp " + order, - pageSize: pageSize, - }, - origin: api.cloudloggingOrigin, + const result = await client.post< + { resourceNames: string[]; filter: string; orderBy: string; pageSize: number }, + { entries: LogEntry[] } + >("/entries:list", { + resourceNames: [`projects/${projectId}`], + filter: filter, + orderBy: `timestamp ${order}`, + pageSize: pageSize, }); return result.body.entries; } catch (err: any) { diff --git a/src/gcp/cloudmonitoring.ts b/src/gcp/cloudmonitoring.ts index d5b7f3efb29..bab9aabb240 100644 --- a/src/gcp/cloudmonitoring.ts +++ b/src/gcp/cloudmonitoring.ts @@ -1,4 +1,5 @@ -import * as api from "../api"; +import { cloudMonitoringOrigin } from "../api"; +import { Client } from "../apiv2"; import { FirebaseError } from "../error"; export const CLOUD_MONITORING_VERSION = "v3"; @@ -133,14 +134,15 @@ export async function queryTimeSeries( query: CmQuery, projectNumber: number ): Promise { + const client = new Client({ + urlPrefix: cloudMonitoringOrigin, + apiVersion: CLOUD_MONITORING_VERSION, + }); try { - const res = await api.request( - "GET", - `/${CLOUD_MONITORING_VERSION}/projects/${projectNumber}/timeSeries/`, + const res = await client.get<{ timeSeries: TimeSeriesResponse }>( + `/projects/${projectNumber}/timeSeries/`, { - auth: true, - origin: api.cloudMonitoringOrigin, - data: query, + queryParams: query as { [key: string]: any }, } ); return res.body.timeSeries; diff --git a/src/gcp/cloudscheduler.ts b/src/gcp/cloudscheduler.ts index 1b1a7913dd6..dde0000bea6 100644 --- a/src/gcp/cloudscheduler.ts +++ b/src/gcp/cloudscheduler.ts @@ -2,7 +2,8 @@ import * as _ from "lodash"; import { FirebaseError } from "../error"; import { logger } from "../logger"; -import * as api from "../api"; +import { cloudschedulerOrigin } from "../api"; +import { Client } from "../apiv2"; import * as backend from "../deploy/functions/backend"; import * as proto from "./proto"; import { assertExhaustive } from "../functional"; @@ -80,6 +81,8 @@ export function assertValidJob(job: Job) { } } +const apiClient = new Client({ urlPrefix: cloudschedulerOrigin, apiVersion: VERSION }); + /** * Creates a cloudScheduler job. * If another job with that name already exists, this will return a 409. @@ -90,11 +93,7 @@ export function createJob(job: Job): Promise { // ie: projects/my-proj/locations/us-central1/jobs/firebase-schedule-func-us-east1 would become // projects/my-proj/locations/us-central1/jobs const strippedName = job.name.substring(0, job.name.lastIndexOf("/")); - return api.request("POST", `/${VERSION}/${strippedName}`, { - auth: true, - origin: api.cloudschedulerOrigin, - data: Object.assign({ timeZone: DEFAULT_TIME_ZONE }, job), - }); + return apiClient.post(`/${strippedName}`, Object.assign({ timeZone: DEFAULT_TIME_ZONE }, job)); } /** @@ -103,10 +102,7 @@ export function createJob(job: Job): Promise { * @param name The name of the job to delete. */ export function deleteJob(name: string): Promise { - return api.request("DELETE", `/${VERSION}/${name}`, { - auth: true, - origin: api.cloudschedulerOrigin, - }); + return apiClient.delete(`/${name}`); } /** @@ -115,9 +111,7 @@ export function deleteJob(name: string): Promise { * @param name The name of the job to get. */ export function getJob(name: string): Promise { - return api.request("GET", `/${VERSION}/${name}`, { - auth: true, - origin: api.cloudschedulerOrigin, + return apiClient.get(`/${name}`, { resolveOnHTTPError: true, }); } @@ -129,11 +123,7 @@ export function getJob(name: string): Promise { */ export function updateJob(job: Job): Promise { // Note that name cannot be updated. - return api.request("PATCH", `/${VERSION}/${job.name}`, { - auth: true, - origin: api.cloudschedulerOrigin, - data: Object.assign({ timeZone: DEFAULT_TIME_ZONE }, job), - }); + return apiClient.patch(`/${job.name}`, Object.assign({ timeZone: DEFAULT_TIME_ZONE }, job)); } /** diff --git a/src/gcp/firedata.ts b/src/gcp/firedata.ts index 55ed6093c84..796aba02445 100644 --- a/src/gcp/firedata.ts +++ b/src/gcp/firedata.ts @@ -1,4 +1,5 @@ -import * as api from "../api"; +import { firedataOrigin } from "../api"; +import { Client } from "../apiv2"; import { logger } from "../logger"; import * as utils from "../utils"; @@ -25,10 +26,13 @@ function _handleErrorResponse(response: any): any { * @return the list of databases. */ export async function listDatabaseInstances(projectNumber: string): Promise { - const response = await api.request("GET", `/v1/projects/${projectNumber}/databases`, { - auth: true, - origin: api.firedataOrigin, - }); + const client = new Client({ urlPrefix: firedataOrigin, apiVersion: "v1" }); + const response = await client.get<{ instance: DatabaseInstance[] }>( + `/projects/${projectNumber}/databases`, + { + resolveOnHTTPError: true, + } + ); if (response.status === 200) { return response.body.instance; } diff --git a/src/gcp/firestore.ts b/src/gcp/firestore.ts index ecbe59626b3..6e20e385f99 100644 --- a/src/gcp/firestore.ts +++ b/src/gcp/firestore.ts @@ -1,7 +1,7 @@ import { firestoreOriginOrEmulator } from "../api"; -import * as apiv2 from "../apiv2"; +import { Client } from "../apiv2"; -const _CLIENT = new apiv2.Client({ +const apiClient = new Client({ auth: true, apiVersion: "v1", urlPrefix: firestoreOriginOrEmulator, @@ -20,7 +20,7 @@ export function listCollectionIds(project: string): Promise { pageSize: 2147483647, }; - return _CLIENT.post(url, data).then((res) => { + return apiClient.post(url, data).then((res) => { return res.body.collectionIds || []; }); } @@ -35,7 +35,7 @@ export function listCollectionIds(project: string): Promise { * @return {Promise} a promise for the delete operation. */ export async function deleteDocument(doc: any): Promise { - return _CLIENT.delete(doc.name); + return apiClient.delete(doc.name); } /** @@ -56,6 +56,6 @@ export async function deleteDocuments(project: string, docs: any[]): Promise(url, data); + const res = await apiClient.post(url, data); return res.body.writeResults.length; } diff --git a/src/gcp/iam.ts b/src/gcp/iam.ts index 7ea41492ee7..a87bdc03f9c 100644 --- a/src/gcp/iam.ts +++ b/src/gcp/iam.ts @@ -1,10 +1,12 @@ -import * as api from "../api"; -import { endpoint } from "../utils"; +import { resourceManagerOrigin, iamOrigin } from "../api"; import { difference } from "lodash"; import { logger } from "../logger"; +import { Client } from "../apiv2"; const API_VERSION = "v1"; +const apiClient = new Client({ urlPrefix: iamOrigin, apiVersion: API_VERSION }); + // IAM Policy // https://cloud.google.com/resource-manager/reference/rest/Shared.Types/Policy export interface Binding { @@ -58,21 +60,16 @@ export async function createServiceAccount( displayName: string // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { - const response = await api.request( - "POST", - `/${API_VERSION}/projects/${projectId}/serviceAccounts`, - { - auth: true, - origin: api.iamOrigin, - data: { - accountId, - serviceAccount: { - displayName, - description, - }, - }, - } - ); + const response = await apiClient.post< + { accountId: string; serviceAccount: { displayName: string; description: string } }, + any + >(`/projects/${projectId}/serviceAccounts`, { + accountId, + serviceAccount: { + displayName, + description, + }, + }); return response.body; } @@ -86,13 +83,8 @@ export async function getServiceAccount( projectId: string, serviceAccountName: string ): Promise { - const response = await api.request( - "GET", - `/${API_VERSION}/projects/${projectId}/serviceAccounts/${serviceAccountName}@${projectId}.iam.gserviceaccount.com`, - { - auth: true, - origin: api.iamOrigin, - } + const response = await apiClient.get( + `/projects/${projectId}/serviceAccounts/${serviceAccountName}@${projectId}.iam.gserviceaccount.com` ); return response.body; } @@ -101,16 +93,14 @@ export async function createServiceAccountKey( projectId: string, serviceAccountName: string ): Promise { - const response = await api.request( - "POST", - `/${API_VERSION}/projects/${projectId}/serviceAccounts/${serviceAccountName}@${projectId}.iam.gserviceaccount.com/keys`, + const response = await apiClient.post< + { keyAlgorithm: string; privateKeyType: string }, + ServiceAccountKey + >( + `/projects/${projectId}/serviceAccounts/${serviceAccountName}@${projectId}.iam.gserviceaccount.com/keys`, { - auth: true, - origin: api.iamOrigin, - data: { - keyAlgorithm: "KEY_ALG_UNSPECIFIED", - privateKeyType: "TYPE_GOOGLE_CREDENTIALS_FILE", - }, + keyAlgorithm: "KEY_ALG_UNSPECIFIED", + privateKeyType: "TYPE_GOOGLE_CREDENTIALS_FILE", } ); return response.body; @@ -124,15 +114,9 @@ export async function createServiceAccountKey( */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function deleteServiceAccount(projectId: string, accountEmail: string): Promise { - return api.request( - "DELETE", - `/${API_VERSION}/projects/${projectId}/serviceAccounts/${accountEmail}`, - { - auth: true, - origin: api.iamOrigin, - resolveOnHTTPError: true, - } - ); + return apiClient.delete(`/projects/${projectId}/serviceAccounts/${accountEmail}`, { + resolveOnHTTPError: true, + }); } /** @@ -143,9 +127,7 @@ export function deleteServiceAccount(projectId: string, accountEmail: string): P * @return Details about the IAM role. */ export async function getRole(role: string): Promise<{ title: string; description: string }> { - const response = await api.request("GET", endpoint([API_VERSION, "roles", role]), { - auth: true, - origin: api.iamOrigin, + const response = await apiClient.get<{ title: string; description: string }>(`/roles/${role}`, { retryCodes: [500, 503], }); return response.body; @@ -171,6 +153,7 @@ export async function testResourceIamPermissions( resourceName: string, permissions: string[] ): Promise { + const localClient = new Client({ urlPrefix: origin, apiVersion }); if (process.env.FIREBASE_SKIP_INFORMATIONAL_IAM) { logger.debug( "[iam] skipping informational check of permissions", @@ -180,11 +163,12 @@ export async function testResourceIamPermissions( ); return { allowed: permissions, missing: [], passed: true }; } - const response = await api.request("POST", `/${apiVersion}/${resourceName}:testIamPermissions`, { - auth: true, - data: { permissions }, - origin, - }); + const response = await localClient.post<{ permissions: string[] }, { permissions: string[] }>( + `/${resourceName}:testIamPermissions`, + { + permissions, + } + ); const allowed = (response.body.permissions || []).sort(); const missing = difference(permissions, allowed); @@ -206,7 +190,7 @@ export async function testIamPermissions( permissions: string[] ): Promise { return testResourceIamPermissions( - api.resourceManagerOrigin, + resourceManagerOrigin, "v1", `projects/${projectId}`, permissions diff --git a/src/gcp/resourceManager.ts b/src/gcp/resourceManager.ts index cc58e0345a1..89274d23ffe 100644 --- a/src/gcp/resourceManager.ts +++ b/src/gcp/resourceManager.ts @@ -1,9 +1,12 @@ import { findIndex } from "lodash"; -import * as api from "../api"; +import { resourceManagerOrigin } from "../api"; +import { Client } from "../apiv2"; import { Binding, getServiceAccount, Policy } from "./iam"; const API_VERSION = "v1"; +const apiClient = new Client({ urlPrefix: resourceManagerOrigin, apiVersion: API_VERSION }); + // Roles listed at https://firebase.google.com/docs/projects/iam/roles-predefined-product export const firebaseRoles = { apiKeysViewer: "roles/serviceusage.apiKeysViewer", @@ -19,10 +22,7 @@ export const firebaseRoles = { * @param projectId the id of the project whose IAM Policy you want to get */ export async function getIamPolicy(projectId: string): Promise { - const response = await api.request("POST", `/${API_VERSION}/projects/${projectId}:getIamPolicy`, { - auth: true, - origin: api.resourceManagerOrigin, - }); + const response = await apiClient.post(`/projects/${projectId}:getIamPolicy`); return response.body; } @@ -37,16 +37,15 @@ export async function getIamPolicy(projectId: string): Promise { export async function setIamPolicy( projectId: string, newPolicy: Policy, - updateMask?: string + updateMask = "" ): Promise { - const response = await api.request("POST", `/${API_VERSION}/projects/${projectId}:setIamPolicy`, { - auth: true, - origin: api.resourceManagerOrigin, - data: { + const response = await apiClient.post<{ policy: Policy; updateMask: string }, Policy>( + `/projects/${projectId}:setIamPolicy`, + { policy: newPolicy, updateMask: updateMask, - }, - }); + } + ); return response.body; } diff --git a/src/gcp/runtimeconfig.js b/src/gcp/runtimeconfig.js index c3c1a538d0e..f774b132fd3 100644 --- a/src/gcp/runtimeconfig.js +++ b/src/gcp/runtimeconfig.js @@ -1,37 +1,36 @@ "use strict"; -var api = require("../api"); +const { runtimeconfigOrigin } = require("../api"); +const { Client } = require("../apiv2"); -var utils = require("../utils"); const { logger } = require("../logger"); var _ = require("lodash"); -var API_VERSION = "v1beta1"; +const API_VERSION = "v1beta1"; +const apiClient = new Client({ urlPrefix: runtimeconfigOrigin, apiVersion: API_VERSION }); function _listConfigs(projectId) { - return api - .request("GET", utils.endpoint([API_VERSION, "projects", projectId, "configs"]), { - auth: true, - origin: api.runtimeconfigOrigin, + return apiClient + .get(`/projects/${projectId}/configs`, { retryCodes: [500, 503], }) .then(function (resp) { - return Promise.resolve(resp.body.configs); + return resp.body.configs; }); } function _createConfig(projectId, configId) { var path = _.join(["projects", projectId, "configs"], "/"); - var endpoint = utils.endpoint([API_VERSION, path]); - return api - .request("POST", endpoint, { - auth: true, - origin: api.runtimeconfigOrigin, - data: { + return apiClient + .post( + `/projects/${projectId}/configs`, + { name: path + "/" + configId, }, - retryCodes: [500, 503], - }) + { + retryCodes: [500, 503], + } + ) .catch(function (err) { if (_.get(err, "context.response.statusCode") === 409) { // Config has already been created as part of a parallel operation during firebase functions:config:set @@ -42,10 +41,8 @@ function _createConfig(projectId, configId) { } function _deleteConfig(projectId, configId) { - return api - .request("DELETE", utils.endpoint([API_VERSION, "projects", projectId, "configs", configId]), { - auth: true, - origin: api.runtimeconfigOrigin, + return apiClient + .delete(`/projects/${projectId}/configs/${configId}`, { retryCodes: [500, 503], }) .catch(function (err) { @@ -58,10 +55,8 @@ function _deleteConfig(projectId, configId) { } function _listVariables(configPath) { - return api - .request("GET", utils.endpoint([API_VERSION, configPath, "variables"]), { - auth: true, - origin: api.runtimeconfigOrigin, + return apiClient + .get(`${configPath}/variables`, { retryCodes: [500, 503], }) .then(function (resp) { @@ -70,10 +65,8 @@ function _listVariables(configPath) { } function _getVariable(varPath) { - return api - .request("GET", utils.endpoint([API_VERSION, varPath]), { - auth: true, - origin: api.runtimeconfigOrigin, + return apiClient + .get(varPath, { retryCodes: [500, 503], }) .then(function (resp) { @@ -82,18 +75,18 @@ function _getVariable(varPath) { } function _createVariable(projectId, configId, varId, value) { - var path = _.join(["projects", projectId, "configs", configId, "variables"], "/"); - var endpoint = utils.endpoint([API_VERSION, path]); - return api - .request("POST", endpoint, { - auth: true, - origin: api.runtimeconfigOrigin, - data: { - name: path + "/" + varId, + const path = `/projects/${projectId}/configs/${configId}/variables`; + return apiClient + .post( + path, + { + name: `${path}/${varId}`, text: value, }, - retryCodes: [500, 503], - }) + { + retryCodes: [500, 503], + } + ) .catch(function (err) { if (_.get(err, "context.response.statusCode") === 404) { // parent config doesn't exist yet @@ -106,18 +99,19 @@ function _createVariable(projectId, configId, varId, value) { } function _updateVariable(projectId, configId, varId, value) { - var path = _.join(["projects", projectId, "configs", configId, "variables", varId], "/"); - var endpoint = utils.endpoint([API_VERSION, path]); - return api.request("PUT", endpoint, { - auth: true, - origin: api.runtimeconfigOrigin, - data: { + const path = `/projects/${projectId}/configs/${configId}/variables/${varId}`; + return apiClient.put( + path, + { name: path, text: value, }, - retryCodes: [500, 503], - }); + { + retryCodes: [500, 503], + } + ); } + function _setVariable(projectId, configId, varId, value) { var path = _.join(["projects", projectId, "configs", configId, "variables", varId], "/"); return _getVariable(path) @@ -133,14 +127,10 @@ function _setVariable(projectId, configId, varId, value) { } function _deleteVariable(projectId, configId, varId) { - var endpoint = - utils.endpoint([API_VERSION, "projects", projectId, "configs", configId, "variables", varId]) + - "?recursive=true"; - return api - .request("DELETE", endpoint, { - auth: true, - origin: api.runtimeconfigOrigin, + return apiClient + .delete(`/projects/${projectId}/configs/${configId}/variables/${varId}`, { retryCodes: [500, 503], + queryParams: { recursive: "true" }, }) .catch(function (err) { if (_.get(err, "context.response.statusCode") === 404) { diff --git a/src/gcp/secretManager.ts b/src/gcp/secretManager.ts index df7cd4136e7..b617d516118 100644 --- a/src/gcp/secretManager.ts +++ b/src/gcp/secretManager.ts @@ -1,5 +1,6 @@ import { logLabeledSuccess } from "../utils"; -import * as api from "../api"; +import { secretManagerOrigin } from "../api"; +import { Client } from "../apiv2"; export const secretManagerConsoleUri = (projectId: string) => `https://console.cloud.google.com/security/secret-manager?project=${projectId}`; @@ -16,19 +17,17 @@ export interface SecretVersion { versionId: string; } +const apiClient = new Client({ urlPrefix: secretManagerOrigin, apiVersion: "v1beta1" }); + export async function listSecrets(projectId: string): Promise { - const listRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets`, { - auth: true, - origin: api.secretManagerOrigin, - }); - return listRes.body.secrets.map((s: any) => parseSecretResourceName(s.name)); + const listRes = await apiClient.get<{ secrets: any[] }>(`/projects/${projectId}/secrets`); + return listRes.body.secrets.map((s) => parseSecretResourceName(s.name)); } export async function getSecret(projectId: string, name: string): Promise { - const getRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets/${name}`, { - auth: true, - origin: api.secretManagerOrigin, - }); + const getRes = await apiClient.get<{ name: string; labels: Record }>( + `/projects/${projectId}/secrets/${name}` + ); const secret = parseSecretResourceName(getRes.body.name); secret.labels = getRes.body.labels ?? {}; return secret; @@ -39,13 +38,8 @@ export async function getSecretVersion( name: string, version: string ): Promise { - const getRes = await api.request( - "GET", - `/v1beta1/projects/${projectId}/secrets/${name}/versions/${version}`, - { - auth: true, - origin: api.secretManagerOrigin, - } + const getRes = await apiClient.get<{ name: string }>( + `/projects/${projectId}/secrets/${name}/versions/${version}` ); return parseSecretVersionResourceName(getRes.body.name); } @@ -90,34 +84,28 @@ export async function createSecret( name: string, labels: Record ): Promise { - const createRes = await api.request( - "POST", - `/v1beta1/projects/${projectId}/secrets?secretId=${name}`, + const createRes = await apiClient.post< + { replication: { automatic: {} }; labels: Record }, + { name: string } + >( + `/projects/${projectId}/secrets`, { - auth: true, - origin: api.secretManagerOrigin, - data: { - replication: { - automatic: {}, - }, - labels, + replication: { + automatic: {}, }, - } + labels, + }, + { queryParams: { secretId: name } } ); return parseSecretResourceName(createRes.body.name); } export async function addVersion(secret: Secret, payloadData: string): Promise { - const res = await api.request( - "POST", - `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:addVersion`, + const res = await apiClient.post<{ payload: { data: string } }, { name: string }>( + `/projects/${secret.projectId}/secrets/${secret.name}:addVersion`, { - auth: true, - origin: api.secretManagerOrigin, - data: { - payload: { - data: Buffer.from(payloadData).toString("base64"), - }, + payload: { + data: Buffer.from(payloadData).toString("base64"), }, } ); @@ -136,13 +124,8 @@ export async function grantServiceAgentRole( serviceAccountEmail: string, role: string ): Promise { - const getPolicyRes = await api.request( - "GET", - `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy`, - { - auth: true, - origin: api.secretManagerOrigin, - } + const getPolicyRes = await apiClient.get<{ bindings: any[] }>( + `/projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy` ); const bindings = getPolicyRes.body.bindings || []; @@ -160,19 +143,14 @@ export async function grantServiceAgentRole( role: role, members: [`serviceAccount:${serviceAccountEmail}`], }); - await api.request( - "POST", - `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, + await apiClient.post<{ policy: { bindings: unknown }; updateMask: { paths: string } }, void>( + `/projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, { - auth: true, - origin: api.secretManagerOrigin, - data: { - policy: { - bindings, - }, - updateMask: { - paths: "bindings", - }, + policy: { + bindings, + }, + updateMask: { + paths: "bindings", }, } ); diff --git a/src/gcp/storage.ts b/src/gcp/storage.ts index 6bc8e5e5854..5f45bc88120 100644 --- a/src/gcp/storage.ts +++ b/src/gcp/storage.ts @@ -1,6 +1,7 @@ import * as path from "path"; -import * as api from "../api"; +import api, { storageOrigin } from "../api"; +import { Client } from "../apiv2"; import { logger } from "../logger"; import { FirebaseError } from "../error"; @@ -126,12 +127,11 @@ interface StorageServiceAccountResponse { kind: string; } +const storageAPIClient = new Client({ urlPrefix: storageOrigin, apiVersion: "v1" }); + export async function getDefaultBucket(projectId?: string): Promise { try { - const resp = await api.request("GET", "/v1/apps/" + projectId, { - auth: true, - origin: api.appengineOrigin, - }); + const resp = await storageAPIClient.get<{ defaultBucket: string }>(`/apps/${projectId}`); if (resp.body.defaultBucket === "undefined") { logger.debug("Default storage bucket is undefined."); throw new FirebaseError( @@ -153,19 +153,20 @@ export async function upload( extraHeaders?: Record ): Promise { const url = new URL(uploadUrl); - const result = await api.request("PUT", url.pathname + url.search, { - data: source.stream, + const localAPIClient = new Client({ urlPrefix: url.origin, auth: false }); + const res = await localAPIClient.request({ + method: "PUT", + path: url.pathname, + queryParams: url.searchParams, headers: { - "Content-Type": "application/zip", + "content-type": "application/zip", ...extraHeaders, }, - json: false, - origin: url.origin, - logOptions: { skipRequestBody: true }, + body: source.stream, + skipLog: { resBody: true }, }); - return { - generation: result.response.headers["x-goog-generation"], + generation: res.response.headers.get("x-goog-generation"), }; } @@ -181,21 +182,19 @@ export async function uploadObject(source: any, bucketName: string): Promise { * Gets a storage bucket from GCP. * Ref: https://cloud.google.com/storage/docs/json_api/v1/buckets/get * @param {string} bucketName name of the storage bucket - * @returns a bucket resource object + * @return a bucket resource object */ export async function getBucket(bucketName: string): Promise { try { - const result = await api.request("GET", `/storage/v1/b/${bucketName}`, { - auth: true, - origin: api.storageOrigin, - }); + const localAPIClient = new Client({ urlPrefix: storageOrigin }); + const result = await localAPIClient.get(`/storage/v1/b/${bucketName}`); return result.body; } catch (err: any) { logger.debug(err); @@ -234,7 +231,6 @@ export async function getBucket(bucketName: string): Promise { /** * Find the service account for the Cloud Storage Resource * @param {string} projectId the project identifier - * * @returns: * { * "email_address": string, @@ -243,10 +239,10 @@ export async function getBucket(bucketName: string): Promise { */ export async function getServiceAccount(projectId: string): Promise { try { - const response = await api.request("GET", `/storage/v1/projects/${projectId}/serviceAccount`, { - auth: true, - origin: api.storageOrigin, - }); + const localAPIClient = new Client({ urlPrefix: storageOrigin }); + const response = await localAPIClient.get( + `/storage/v1/projects/${projectId}/serviceAccount` + ); return response.body; } catch (err: any) { logger.debug(err); From 04c798d46c502f49914029e45c1ab08471381aff Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 27 Jan 2022 09:25:01 -0800 Subject: [PATCH 0049/1699] convert convertConfig into typescript (#4097) * convert convertConfig into typescript * guess I should call it the right way * fix json schema types --- schema/firebase-config.json | 399 +++++++++++++++++- src/deploy/hosting/convertConfig.js | 99 ----- src/deploy/hosting/convertConfig.ts | 119 ++++++ src/deploy/hosting/prepare.js | 2 +- src/firebaseConfig.ts | 8 +- src/test/deploy/hosting/convertConfig.spec.ts | 152 +++++++ 6 files changed, 663 insertions(+), 116 deletions(-) delete mode 100644 src/deploy/hosting/convertConfig.js create mode 100644 src/deploy/hosting/convertConfig.ts create mode 100644 src/test/deploy/hosting/convertConfig.spec.ts diff --git a/schema/firebase-config.json b/schema/firebase-config.json index d1da4d7447c..d82b69a9d9b 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -384,6 +384,38 @@ "headers": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "glob", + "headers" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { @@ -502,6 +534,25 @@ "redirects": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { @@ -517,8 +568,7 @@ }, "required": [ "destination", - "source", - "type" + "source" ], "type": "object" }, @@ -537,8 +587,7 @@ }, "required": [ "destination", - "regex", - "type" + "regex" ], "type": "object" } @@ -549,6 +598,82 @@ "rewrites": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "glob", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "glob" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { @@ -732,6 +857,38 @@ "headers": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "glob", + "headers" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { @@ -850,6 +1007,25 @@ "redirects": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { @@ -865,8 +1041,7 @@ }, "required": [ "destination", - "source", - "type" + "source" ], "type": "object" }, @@ -885,8 +1060,7 @@ }, "required": [ "destination", - "regex", - "type" + "regex" ], "type": "object" } @@ -897,6 +1071,82 @@ "rewrites": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "glob", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "glob" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { @@ -1080,6 +1330,38 @@ "headers": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "headers": { + "items": { + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "glob", + "headers" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { @@ -1198,6 +1480,25 @@ "redirects": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "type": { + "type": "number" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { @@ -1213,8 +1514,7 @@ }, "required": [ "destination", - "source", - "type" + "source" ], "type": "object" }, @@ -1233,8 +1533,7 @@ }, "required": [ "destination", - "regex", - "type" + "regex" ], "type": "object" } @@ -1245,6 +1544,82 @@ "rewrites": { "items": { "anyOf": [ + { + "additionalProperties": false, + "properties": { + "destination": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "destination", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "function": { + "type": "string" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "function", + "glob" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "glob": { + "type": "string" + }, + "run": { + "additionalProperties": false, + "properties": { + "region": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "serviceId" + ], + "type": "object" + } + }, + "required": [ + "glob", + "run" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "dynamicLinks": { + "type": "boolean" + }, + "glob": { + "type": "string" + } + }, + "required": [ + "dynamicLinks", + "glob" + ], + "type": "object" + }, { "additionalProperties": false, "properties": { diff --git a/src/deploy/hosting/convertConfig.js b/src/deploy/hosting/convertConfig.js deleted file mode 100644 index b30c401200a..00000000000 --- a/src/deploy/hosting/convertConfig.js +++ /dev/null @@ -1,99 +0,0 @@ -const _ = require("lodash"); -const { FirebaseError } = require("../../error"); - -/** - * extractPattern contains the logic for extracting exactly one glob/regexp - * from a Hosting rewrite/redirect/header specification - */ -function extractPattern(type, spec) { - const glob = spec.source || spec.glob; - const regex = spec.regex; - - if (glob && regex) { - throw new FirebaseError("Cannot specify a " + type + " pattern with both a glob and regex."); - } else if (glob) { - return { glob: glob }; - } else if (regex) { - return { regex: regex }; - } - throw new FirebaseError( - "Cannot specify a " + type + " with no pattern (either a glob or regex required)." - ); -} - -/** - * convertConfig takes a hosting config object from firebase.json and transforms it into - * the valid format for sending to the Firebase Hosting REST API - */ -module.exports = function (config) { - const out = {}; - - if (!config) { - return out; - } - - // rewrites - if (_.isArray(config.rewrites)) { - out.rewrites = config.rewrites.map(function (rewrite) { - const vRewrite = extractPattern("rewrite", rewrite); - if (rewrite.destination) { - vRewrite.path = rewrite.destination; - } else if (rewrite.function) { - vRewrite.function = rewrite.function; - } else if (rewrite.dynamicLinks) { - vRewrite.dynamicLinks = rewrite.dynamicLinks; - } else if (rewrite.run) { - vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run); - } - return vRewrite; - }); - } - - // redirects - if (_.isArray(config.redirects)) { - out.redirects = config.redirects.map(function (redirect) { - const vRedirect = extractPattern("redirect", redirect); - vRedirect.location = redirect.destination; - if (redirect.type) { - vRedirect.statusCode = redirect.type; - } - return vRedirect; - }); - } - - // headers - if (_.isArray(config.headers)) { - out.headers = config.headers.map(function (header) { - const vHeader = extractPattern("header", header); - vHeader.headers = {}; - (header.headers || []).forEach(function (h) { - vHeader.headers[h.key] = h.value; - }); - return vHeader; - }); - } - - // cleanUrls - if (_.has(config, "cleanUrls")) { - out.cleanUrls = config.cleanUrls; - } - - // trailingSlash - if (config.trailingSlash === true) { - out.trailingSlashBehavior = "ADD"; - } else if (config.trailingSlash === false) { - out.trailingSlashBehavior = "REMOVE"; - } - - // App association files - if (_.has(config, "appAssociation")) { - out.appAssociation = config.appAssociation; - } - - // i18n config - if (_.has(config, "i18n")) { - out.i18n = config.i18n; - } - - return out; -}; diff --git a/src/deploy/hosting/convertConfig.ts b/src/deploy/hosting/convertConfig.ts new file mode 100644 index 00000000000..e8d9f851265 --- /dev/null +++ b/src/deploy/hosting/convertConfig.ts @@ -0,0 +1,119 @@ +import { FirebaseError } from "../../error"; +import { HostingConfig, HostingRewrites, HostingHeaders } from "../../firebaseConfig"; + +function has(obj: { [k: string]: unknown }, k: string): boolean { + return obj[k] !== undefined; +} + +/** + * extractPattern contains the logic for extracting exactly one glob/regexp + * from a Hosting rewrite/redirect/header specification + */ +function extractPattern(type: string, spec: HostingRewrites | HostingHeaders): any { + let glob = ""; + let regex = ""; + if ("source" in spec) { + glob = spec.source; + } + if ("glob" in spec) { + glob = spec.glob; + } + if ("regex" in spec) { + regex = spec.regex; + } + + if (glob && regex) { + throw new FirebaseError(`Cannot specify a ${type} pattern with both a glob and regex.`); + } else if (glob) { + return { glob: glob }; + } else if (regex) { + return { regex: regex }; + } + throw new FirebaseError( + `Cannot specify a ${type} with no pattern (either a glob or regex required).` + ); +} + +/** + * convertConfig takes a hosting config object from firebase.json and transforms it into + * the valid format for sending to the Firebase Hosting REST API + */ +export function convertConfig(config?: HostingConfig): { [k: string]: any } { + if (Array.isArray(config)) { + throw new FirebaseError(`convertConfig should be given a single configuration, not an array.`, { + exit: 2, + }); + } + const out: { [k: string]: any } = {}; + + if (!config) { + return out; + } + + // rewrites + if (Array.isArray(config.rewrites)) { + out.rewrites = config.rewrites.map((rewrite) => { + const vRewrite = extractPattern("rewrite", rewrite); + if ("destination" in rewrite) { + vRewrite.path = rewrite.destination; + } else if ("function" in rewrite) { + vRewrite.function = rewrite.function; + } else if ("dynamicLinks" in rewrite) { + vRewrite.dynamicLinks = rewrite.dynamicLinks; + } else if ("run" in rewrite) { + vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run); + } + return vRewrite; + }); + } + + // redirects + if (Array.isArray(config.redirects)) { + out.redirects = config.redirects.map((redirect) => { + const vRedirect = extractPattern("redirect", redirect); + vRedirect.location = redirect.destination; + if (redirect.type) { + vRedirect.statusCode = redirect.type; + } + return vRedirect; + }); + } + + // headers + if (Array.isArray(config.headers)) { + out.headers = config.headers.map((header) => { + const vHeader = extractPattern("header", header); + vHeader.headers = {}; + if (Array.isArray(header.headers) && header.headers.length) { + header.headers.forEach((h) => { + vHeader.headers[h.key] = h.value; + }); + } + return vHeader; + }); + } + + // cleanUrls + if (has(config, "cleanUrls")) { + out.cleanUrls = config.cleanUrls; + } + + // trailingSlash + if (config.trailingSlash === true) { + out.trailingSlashBehavior = "ADD"; + } else if (config.trailingSlash === false) { + out.trailingSlashBehavior = "REMOVE"; + } + + // App association files + if (has(config, "appAssociation")) { + out.appAssociation = config.appAssociation; + } + + // i18n config + if (has(config, "i18n")) { + out.i18n = config.i18n; + } + + return out; +} diff --git a/src/deploy/hosting/prepare.js b/src/deploy/hosting/prepare.js index 35f6c8798c8..f586eebad00 100644 --- a/src/deploy/hosting/prepare.js +++ b/src/deploy/hosting/prepare.js @@ -3,7 +3,7 @@ const _ = require("lodash"); const api = require("../../api"); -const convertConfig = require("./convertConfig"); +const { convertConfig } = require("./convertConfig"); const deploymentTool = require("../../deploymentTool"); const { FirebaseError } = require("../../error"); const { normalizedHostingConfigs } = require("../../hosting/normalizedHostingConfigs"); diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index 4ced73e39c2..1db9a6e2a50 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -30,14 +30,14 @@ type DatabaseMultiple = ({ }> & Deployable)[]; -type HostingSource = { source: string } | { regex: string }; +type HostingSource = { glob: string } | { source: string } | { regex: string }; type HostingRedirects = HostingSource & { destination: string; - type: number; + type?: number; }; -type HostingRewrites = HostingSource & +export type HostingRewrites = HostingSource & ( | { destination: string } | { function: string } @@ -50,7 +50,7 @@ type HostingRewrites = HostingSource & | { dynamicLinks: boolean } ); -type HostingHeaders = HostingSource & { +export type HostingHeaders = HostingSource & { headers: { key: string; value: string; diff --git a/src/test/deploy/hosting/convertConfig.spec.ts b/src/test/deploy/hosting/convertConfig.spec.ts new file mode 100644 index 00000000000..c4a1925a0e3 --- /dev/null +++ b/src/test/deploy/hosting/convertConfig.spec.ts @@ -0,0 +1,152 @@ +import { expect } from "chai"; +import { HostingConfig } from "../../../firebaseConfig"; + +import { convertConfig } from "../../../deploy/hosting/convertConfig"; + +describe("convertConfig", () => { + const tests: Array<{ name: string; input: HostingConfig | undefined; want: any }> = [ + { + name: "returns nothing if no config is provided", + input: undefined, + want: {}, + }, + // Rewrites. + { + name: "returns rewrites for glob destination", + input: { rewrites: [{ glob: "/foo", destination: "https://example.com" }] }, + want: { rewrites: [{ glob: "/foo", path: "https://example.com" }] }, + }, + { + name: "returns rewrites for regex destination", + input: { rewrites: [{ glob: "/foo$", destination: "https://example.com" }] }, + want: { rewrites: [{ glob: "/foo$", path: "https://example.com" }] }, + }, + { + name: "returns rewrites for glob CF3", + input: { rewrites: [{ glob: "/foo", function: "foofn" }] }, + want: { rewrites: [{ glob: "/foo", function: "foofn" }] }, + }, + { + name: "returns rewrites for regex CF3", + input: { rewrites: [{ regex: "/foo$", function: "foofn" }] }, + want: { rewrites: [{ regex: "/foo$", function: "foofn" }] }, + }, + { + name: "returns rewrites for glob Run", + input: { rewrites: [{ glob: "/foo", run: { serviceId: "hello" } }] }, + want: { rewrites: [{ glob: "/foo", run: { region: "us-central1", serviceId: "hello" } }] }, + }, + { + name: "returns rewrites for regex Run", + input: { rewrites: [{ regex: "/foo$", run: { serviceId: "hello" } }] }, + want: { rewrites: [{ regex: "/foo$", run: { region: "us-central1", serviceId: "hello" } }] }, + }, + { + name: "returns rewrites for Run with specified regions", + input: { rewrites: [{ glob: "/foo", run: { serviceId: "hello", region: "us-midwest" } }] }, + want: { rewrites: [{ glob: "/foo", run: { region: "us-midwest", serviceId: "hello" } }] }, + }, + { + name: "returns rewrites for glob Dynamic Links", + input: { rewrites: [{ glob: "/foo", dynamicLinks: true }] }, + want: { rewrites: [{ glob: "/foo", dynamicLinks: true }] }, + }, + { + name: "returns rewrites for regex Dynamic Links", + input: { rewrites: [{ regex: "/foo$", dynamicLinks: true }] }, + want: { rewrites: [{ regex: "/foo$", dynamicLinks: true }] }, + }, + // Redirects. + { + name: "returns glob redirects without a specified code/type", + input: { redirects: [{ glob: "/foo", destination: "https://example.com" }] }, + want: { redirects: [{ glob: "/foo", location: "https://example.com" }] }, + }, + { + name: "returns regex redirects without a specified code/type", + input: { redirects: [{ regex: "/foo$", destination: "https://example.com" }] }, + want: { redirects: [{ regex: "/foo$", location: "https://example.com" }] }, + }, + { + name: "returns glob redirects with a specified code/type", + input: { redirects: [{ glob: "/foo", destination: "https://example.com", type: 301 }] }, + want: { redirects: [{ glob: "/foo", location: "https://example.com", statusCode: 301 }] }, + }, + // Headers. + { + name: "returns no headers if they weren't specified", + input: { headers: [{ glob: "/foo", headers: [] }] }, + want: { headers: [{ glob: "/foo", headers: {} }] }, + }, + { + name: "returns glob headers as a map", + input: { + headers: [ + { + glob: "/foo", + headers: [ + { key: "x-foo", value: "bar" }, + { key: "x-baz", value: "zap" }, + ], + }, + ], + }, + want: { headers: [{ glob: "/foo", headers: { "x-foo": "bar", "x-baz": "zap" } }] }, + }, + { + name: "returns regex headers as a map", + input: { + headers: [ + { + regex: "/foo&", + headers: [ + { key: "x-foo", value: "bar" }, + { key: "x-baz", value: "zap" }, + ], + }, + ], + }, + want: { headers: [{ regex: "/foo&", headers: { "x-foo": "bar", "x-baz": "zap" } }] }, + }, + // Clean URLs. + { + name: "returns clean URLs when it is false", + input: { cleanUrls: false }, + want: { cleanUrls: false }, + }, + { + name: "returns clean URLs when it is true", + input: { cleanUrls: true }, + want: { cleanUrls: true }, + }, + // Trailing Slash. + { + name: "returns trailing slash as ADD when true", + input: { trailingSlash: true }, + want: { trailingSlashBehavior: "ADD" }, + }, + { + name: "returns trailing slash as REMOVE when false", + input: { trailingSlash: false }, + want: { trailingSlashBehavior: "REMOVE" }, + }, + // App Association. + { + name: "returns app association as it is set", + input: { appAssociation: "myApp" }, + want: { appAssociation: "myApp" }, + }, + // i18n. + { + name: "returns i18n as it is set", + input: { i18n: { root: "bar" } }, + want: { i18n: { root: "bar" } }, + }, + ]; + + for (const test of tests) { + it(test.name, () => { + expect(convertConfig(test.input)).to.deep.equal(test.want); + }); + } +}); From 2e68803f994dbe4f72eb0965dd6a12e7a043b597 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 27 Jan 2022 10:25:30 -0800 Subject: [PATCH 0050/1699] Fix bug where we only enable AR when preview flag is turned on. (#4034) --- src/deploy/functions/prepare.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index f01cbf6bd86..bfa93fbf9b5 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -37,7 +37,7 @@ function hasDotenv(opts: functionsEnv.UserEnvsOpts): boolean { // an upgrade warning in the future. If it already is enabled though we want to // remember this and still use the cleaner if necessary. async function maybeEnableAR(projectId: string): Promise { - if (previews.artifactregistry) { + if (!previews.artifactregistry) { return ensureApiEnabled.check( projectId, "artifactregistry.googleapis.com", From d16a978df1be865a402a9713b29f75545b9384f4 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 27 Jan 2022 19:31:43 -0800 Subject: [PATCH 0051/1699] convert archiveDirectory to Typescript (#4102) * rewrite archiveDirectory into typescript and update appropriate calling locations * update some doc comments --- src/archiveDirectory.js | 180 ------------------- src/archiveDirectory.ts | 174 ++++++++++++++++++ src/gcp/storage.ts | 12 +- src/test/extensions/extensionsHelper.spec.ts | 39 ++-- 4 files changed, 210 insertions(+), 195 deletions(-) delete mode 100644 src/archiveDirectory.js create mode 100644 src/archiveDirectory.ts diff --git a/src/archiveDirectory.js b/src/archiveDirectory.js deleted file mode 100644 index 421bf9fc56b..00000000000 --- a/src/archiveDirectory.js +++ /dev/null @@ -1,180 +0,0 @@ -"use strict"; - -const _ = require("lodash"); -const archiver = require("archiver"); -const filesize = require("filesize"); -const fs = require("fs"); -const path = require("path"); -const tar = require("tar"); -const tmp = require("tmp"); - -const { listFiles } = require("./listFiles"); -const { FirebaseError } = require("./error"); -const fsAsync = require("./fsAsync"); -const { logger } = require("./logger"); -const utils = require("./utils"); - -/** - * Archives a directory to a temporary file and returns information about the - * new archive. Defaults to type "tar", and returns a .tar.gz file. - * @param {string} sourceDirectory - * @param {object} options - * @param {string=} options.type Type of directory to create: "tar", or "zip". - * @param {Array=} options.ignore Globs to be ignored. - * @return {!Promise>} Information about the archive: - * - `file`: file name - * - `stream`: read stream of the archive - * - `manifest`: list of all files in the archive - * - `size`: information about the size of the archive - * - `source`: the source directory of the archive, for reference - */ -const archiveDirectory = (sourceDirectory, options) => { - options = options || {}; - let postfix = ".tar.gz"; - if (options.type === "zip") { - postfix = ".zip"; - } - const tempFile = tmp.fileSync({ - prefix: "firebase-archive-", - postfix, - }); - - if (!options.ignore) { - options.ignore = []; - } - - let makeArchive; - if (options.type === "zip") { - makeArchive = _zipDirectory(sourceDirectory, tempFile, options); - } else { - makeArchive = _tarDirectory(sourceDirectory, tempFile, options); - } - return makeArchive - .then((archive) => { - logger.debug(`Archived ${filesize(archive.size)} in ${sourceDirectory}.`); - return archive; - }) - .catch((err) => { - if (err instanceof FirebaseError) { - throw err; - } - return utils.reject("Failed to create archive.", { - original: err, - }); - }); -}; - -/** - * Archives a directory and returns information about the local archive. - * @param {string} sourceDirectory - * @return {!Object} Information with the following keys: - * - file: name of the temp archive that was created. - * - stream: read stream of the temp archive. - * - size: the size of the file. - */ -const _tarDirectory = (sourceDirectory, tempFile, options) => { - const allFiles = listFiles(sourceDirectory, options.ignore); - - // `tar` returns a `TypeError` if `allFiles` is empty. Let's check a feww things. - try { - fs.statSync(sourceDirectory); - } catch (err) { - if (err.code === "ENOENT") { - return utils.reject(`Could not read directory "${sourceDirectory}"`); - } - throw err; - } - if (!allFiles.length) { - return utils.reject( - `Cannot create a tar archive with 0 files from directory "${sourceDirectory}"` - ); - } - - return tar - .create( - { - gzip: true, - file: tempFile.name, - cwd: sourceDirectory, - follow: true, - noDirRecurse: true, - portable: true, - }, - allFiles - ) - .then(() => { - const stats = fs.statSync(tempFile.name); - return { - file: tempFile.name, - stream: fs.createReadStream(tempFile.name), - manifest: allFiles, - size: stats.size, - source: sourceDirectory, - }; - }); -}; - -/** - * Zips a directory and returns information about the local archive. - * @param {string} sourceDirectory - * @return {!Object} - */ -const _zipDirectory = (sourceDirectory, tempFile, options) => { - const archiveFileStream = fs.createWriteStream(tempFile.name, { - flags: "w", - encoding: "binary", - }); - const archive = archiver("zip"); - const archiveDone = _pipeAsync(archive, archiveFileStream); - const allFiles = []; - - return fsAsync - .readdirRecursive({ path: sourceDirectory, ignore: options.ignore }) - .catch((err) => { - if (err.code === "ENOENT") { - return utils.reject(`Could not read directory "${sourceDirectory}"`, { original: err }); - } - throw err; - }) - .then(function (files) { - _.forEach(files, function (file) { - const name = path.relative(sourceDirectory, file.name); - allFiles.push(name); - archive.file(file.name, { - name, - mode: file.mode, - }); - }); - archive.finalize(); - return archiveDone; - }) - .then(() => { - const stats = fs.statSync(tempFile.name); - return { - file: tempFile.name, - stream: fs.createReadStream(tempFile.name), - manifest: allFiles, - size: stats.size, - source: sourceDirectory, - }; - }); -}; - -/** - * Pipes one stream to another, resolving the returned promise on finish or - * rejects on an error. - * @param {!*} from a Stream - * @param {!*} to a Stream - * @return {!Promise} - */ -const _pipeAsync = function (from, to) { - return new Promise(function (resolve, reject) { - to.on("finish", resolve); - to.on("error", reject); - from.pipe(to); - }); -}; - -module.exports = { - archiveDirectory, -}; diff --git a/src/archiveDirectory.ts b/src/archiveDirectory.ts new file mode 100644 index 00000000000..bb3d635af8d --- /dev/null +++ b/src/archiveDirectory.ts @@ -0,0 +1,174 @@ +import * as archiver from "archiver"; +import * as filesize from "filesize"; +import * as fs from "fs"; +import * as path from "path"; +import * as tar from "tar"; +import * as tmp from "tmp"; + +import { FirebaseError } from "./error"; +import { listFiles } from "./listFiles"; +import { logger } from "./logger"; +import { Readable, Writable } from "stream"; +import * as fsAsync from "./fsAsync"; + +export interface ArchiveOptions { + /** Type of archive to create. */ + type?: "tar" | "zip"; + /** Globs to be ignored. */ + ignore?: string[]; +} + +export interface ArchiveResult { + /** File name. */ + file: string; + /** Read stream of the archive. */ + stream: Readable; + /** List of all the files in the archive. */ + manifest: string[]; + /** The size of the archive. */ + size: number; + /** The source directory of the archive. */ + source: string; +} + +/** + * Archives a directory to a temporary file and returns information about the + * new archive. Defaults to type "tar", and returns a .tar.gz file. + */ +export async function archiveDirectory( + sourceDirectory: string, + options: ArchiveOptions = {} +): Promise { + let postfix = ".tar.gz"; + if (options.type === "zip") { + postfix = ".zip"; + } + const tempFile = tmp.fileSync({ + prefix: "firebase-archive-", + postfix, + }); + + if (!options.ignore) { + options.ignore = []; + } + + let makeArchive; + if (options.type === "zip") { + makeArchive = zipDirectory(sourceDirectory, tempFile, options); + } else { + makeArchive = tarDirectory(sourceDirectory, tempFile, options); + } + try { + const archive = await makeArchive; + logger.debug(`Archived ${filesize(archive.size)} in ${sourceDirectory}.`); + return archive; + } catch (err: any) { + if (err instanceof FirebaseError) { + throw err; + } + throw new FirebaseError("Failed to create archive.", { original: err }); + } +} + +/** + * Archives a directory and returns information about the local archive. + */ +async function tarDirectory( + sourceDirectory: string, + tempFile: tmp.FileResult, + options: ArchiveOptions +): Promise { + const allFiles = listFiles(sourceDirectory, options.ignore); + + // `tar` returns a `TypeError` if `allFiles` is empty. Let's check a feww things. + try { + fs.statSync(sourceDirectory); + } catch (err: any) { + if (err.code === "ENOENT") { + throw new FirebaseError(`Could not read directory "${sourceDirectory}"`); + } + throw err; + } + if (!allFiles.length) { + throw new FirebaseError( + `Cannot create a tar archive with 0 files from directory "${sourceDirectory}"` + ); + } + + await tar.create( + { + gzip: true, + file: tempFile.name, + cwd: sourceDirectory, + follow: true, + noDirRecurse: true, + portable: true, + }, + allFiles + ); + const stats = fs.statSync(tempFile.name); + return { + file: tempFile.name, + stream: fs.createReadStream(tempFile.name), + manifest: allFiles, + size: stats.size, + source: sourceDirectory, + }; +} + +/** + * Zips a directory and returns information about the local archive. + */ +async function zipDirectory( + sourceDirectory: string, + tempFile: tmp.FileResult, + options: ArchiveOptions +): Promise { + const archiveFileStream = fs.createWriteStream(tempFile.name, { + flags: "w", + encoding: "binary", + }); + const archive = archiver("zip"); + const archiveDone = pipeAsync(archive, archiveFileStream); + const allFiles: string[] = []; + + let files: fsAsync.ReaddirRecursiveFile[]; + try { + files = await fsAsync.readdirRecursive({ path: sourceDirectory, ignore: options.ignore }); + } catch (err: any) { + if (err.code === "ENOENT") { + throw new FirebaseError(`Could not read directory "${sourceDirectory}"`, { original: err }); + } + throw err; + } + for (const file of files) { + const name = path.relative(sourceDirectory, file.name); + allFiles.push(name); + archive.file(file.name, { + name, + mode: file.mode, + }); + } + void archive.finalize(); + await archiveDone; + const stats = fs.statSync(tempFile.name); + return { + file: tempFile.name, + stream: fs.createReadStream(tempFile.name), + manifest: allFiles, + size: stats.size, + source: sourceDirectory, + }; +} + +/** + * Pipes one stream to another, resolving the returned promise on finish or + * rejects on an error. + */ +async function pipeAsync(from: Readable, to: Writable): Promise { + return new Promise((resolve, reject) => { + to.on("finish", resolve); + to.on("error", reject); + from.pipe(to); + }); +} diff --git a/src/gcp/storage.ts b/src/gcp/storage.ts index 5f45bc88120..51a84b3465d 100644 --- a/src/gcp/storage.ts +++ b/src/gcp/storage.ts @@ -1,3 +1,4 @@ +import { Readable } from "stream"; import * as path from "path"; import api, { storageOrigin } from "../api"; @@ -172,12 +173,13 @@ export async function upload( /** * Uploads a zip file to the specified bucket using the firebasestorage api. - * @param {!Object} source a zip file to upload. Must contain: - * - `file` {string}: file name - * - `stream` {Stream}: read stream of the archive - * @param {string} bucketName a bucket to upload to */ -export async function uploadObject(source: any, bucketName: string): Promise { +export async function uploadObject( + /** Source with file (name) to upload, and stream of file. */ + source: { file: string; stream: Readable }, + /** Bucket to upload to. */ + bucketName: string +): Promise<{ bucket: string; object: string; generation: string | null }> { if (path.extname(source.file) !== ".zip") { throw new FirebaseError(`Expected a file name ending in .zip, got ${source.file}`); } diff --git a/src/test/extensions/extensionsHelper.spec.ts b/src/test/extensions/extensionsHelper.spec.ts index efbcc3c9d9e..984afd1c95f 100644 --- a/src/test/extensions/extensionsHelper.spec.ts +++ b/src/test/extensions/extensionsHelper.spec.ts @@ -9,6 +9,8 @@ import { storage } from "../../gcp"; import * as archiveDirectory from "../../archiveDirectory"; import * as prompt from "../../prompt"; import { ExtensionSource } from "../../extensions/extensionsApi"; +import { Readable } from "stream"; +import { ArchiveResult } from "../../archiveDirectory"; describe("extensionsHelper", () => { describe("substituteParams", () => { @@ -715,14 +717,22 @@ describe("extensionsHelper", () => { params: [], }, }; + const testArchivedFiles: ArchiveResult = { + file: "somefile", + manifest: ["file"], + size: 4, + source: "/some/path", + stream: new Readable(), + }; + const testUploadedArchive: { bucket: string; object: string; generation: string | null } = { + bucket: extensionsHelper.EXTENSIONS_BUCKET_NAME, + object: "object.zip", + generation: "1", + }; beforeEach(() => { - archiveStub = sinon.stub(archiveDirectory, "archiveDirectory").resolves({}); - uploadStub = sinon.stub(storage, "uploadObject").resolves({ - bucket: "firebase-ext-eap-uploads", - object: "object.zip", - generation: 42, - }); + archiveStub = sinon.stub(archiveDirectory, "archiveDirectory").resolves(testArchivedFiles); + uploadStub = sinon.stub(storage, "uploadObject").resolves(testUploadedArchive); createSourceStub = sinon.stub(extensionsApi, "createSource").resolves(testSource); deleteStub = sinon.stub(storage, "deleteObject").resolves(); }); @@ -736,7 +746,10 @@ describe("extensionsHelper", () => { expect(result).to.equal(testSource); expect(archiveStub).to.have.been.calledWith("."); - expect(uploadStub).to.have.been.calledWith({}, extensionsHelper.EXTENSIONS_BUCKET_NAME); + expect(uploadStub).to.have.been.calledWith( + testArchivedFiles, + extensionsHelper.EXTENSIONS_BUCKET_NAME + ); expect(createSourceStub).to.have.been.calledWith("test-proj", testUrl + "?alt=media", "/"); expect(deleteStub).to.have.been.calledWith( `/${extensionsHelper.EXTENSIONS_BUCKET_NAME}/object.zip` @@ -750,7 +763,10 @@ describe("extensionsHelper", () => { expect(result).to.equal(testSource); expect(archiveStub).to.have.been.calledWith("."); - expect(uploadStub).to.have.been.calledWith({}, extensionsHelper.EXTENSIONS_BUCKET_NAME); + expect(uploadStub).to.have.been.calledWith( + testArchivedFiles, + extensionsHelper.EXTENSIONS_BUCKET_NAME + ); expect(createSourceStub).to.have.been.calledWith("test-proj", testUrl + "?alt=media", "/"); expect(deleteStub).to.have.been.calledWith( `/${extensionsHelper.EXTENSIONS_BUCKET_NAME}/object.zip` @@ -769,7 +785,7 @@ describe("extensionsHelper", () => { expect(deleteStub).not.to.have.been.called; }); - it("should throw an error if one is thrown while uploading a local source ", async () => { + it("should throw an error if one is thrown while uploading a local source", async () => { uploadStub.throws(new FirebaseError("something bad happened")); await expect(extensionsHelper.createSourceFromLocation("test-proj", ".")).to.be.rejectedWith( @@ -777,7 +793,10 @@ describe("extensionsHelper", () => { ); expect(archiveStub).to.have.been.calledWith("."); - expect(uploadStub).to.have.been.calledWith({}, extensionsHelper.EXTENSIONS_BUCKET_NAME); + expect(uploadStub).to.have.been.calledWith( + testArchivedFiles, + extensionsHelper.EXTENSIONS_BUCKET_NAME + ); expect(createSourceStub).not.to.have.been.called; expect(deleteStub).not.to.have.been.called; }); From d6318c753d87aeed6003ccc97a1ff79b197c64df Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 31 Jan 2022 10:00:56 -0800 Subject: [PATCH 0052/1699] fixes issue where emulator would log about a node version mismatch when there was not one (#4024) (#4113) --- src/emulator/functionsEmulator.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 95343b35eb4..9bdee6ed3a0 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -896,14 +896,14 @@ export class FunctionsEmulator implements EmulatorInstance { "functions", `Using node@${requestedMajorVersion} from host.` ); + } else { + // Otherwise we'll warn and use the version that is currently running this process. + this.logger.log( + "WARN", + `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}". Using node@${hostMajorVersion} from host.` + ); } - // Otherwise we'll warn and use the version that is currently running this process. - this.logger.log( - "WARN", - `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}"` - ); - return process.execPath; } From 2cc197ed71f33911e7495f99fcbba150a1d98a98 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 31 Jan 2022 10:20:16 -0800 Subject: [PATCH 0053/1699] Add validation and API restrictions to concurrency (#4101) * Add validation and API restrictions to concurrency * don't setConcurrency if it's explicitly one; run formatter * Reset npm shrinkwrap * Add missing test case --- src/deploy/functions/backend.ts | 2 + src/deploy/functions/prepare.ts | 2 +- src/deploy/functions/release/fabricator.ts | 13 +-- src/deploy/functions/validate.ts | 35 ++++++++ .../functions/release/fabricator.spec.ts | 22 ++++- src/test/deploy/functions/validate.spec.ts | 85 +++++++++++++++++++ 6 files changed, 149 insertions(+), 10 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index d9e9f2c5b8d..913d56a05b7 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -146,6 +146,8 @@ export function memoryOptionDisplayName(option: MemoryOptions): string { }[option]; } +export const DEFAULT_MEMORY: MemoryOptions = 256; +export const MIN_MEMORY_FOR_CONCURRENCY: MemoryOptions = 2048; export const SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" }); /** diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index bfa93fbf9b5..b855b6c350c 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -168,7 +168,7 @@ export async function prepare( ); // Validate the function code that is being deployed. - validate.functionIdsAreValid(backend.allEndpoints(wantBackend)); + validate.endpointsAreValid(wantBackend); // Check what --only filters have been passed in. context.filters = getFilterGroups(options); diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index d61ec0b0827..c3798ce6b51 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -312,11 +312,14 @@ export class Fabricator { } } - await this.setConcurrency( - endpoint, - serviceName, - endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY - ); + const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; + if (mem >= backend.MIN_MEMORY_FOR_CONCURRENCY && endpoint.concurrency != 1) { + await this.setConcurrency( + endpoint, + serviceName, + endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY + ); + } } async updateV1Function(endpoint: backend.Endpoint, scraper: SourceTokenScraper): Promise { diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index def1fc2e99c..1ba2a116ad7 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -3,6 +3,41 @@ import * as clc from "cli-color"; import { FirebaseError } from "../../error"; import * as fsutils from "../../fsutils"; +import * as backend from "./backend"; + +/** Validate that the configuration for endpoints are valid. */ +export function endpointsAreValid(wantBackend: backend.Backend): void { + functionIdsAreValid(backend.allEndpoints(wantBackend)); + + // Our SDK doesn't let people articulate this, but it's theoretically possible in the manifest syntax. + const gcfV1WithConcurrency = backend + .allEndpoints(wantBackend) + .filter((endpoint) => (endpoint.concurrency || 1) != 1 && endpoint.platform == "gcfv1") + .map((endpoint) => endpoint.id); + if (gcfV1WithConcurrency.length) { + const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency.join( + "," + )} because they are GCF gen 1`; + throw new FirebaseError(msg); + } + + const tooSmallForConcurrency = backend + .allEndpoints(wantBackend) + .filter((endpoint) => { + if ((endpoint.concurrency || 1) == 1) { + return false; + } + const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; + return mem < backend.MIN_MEMORY_FOR_CONCURRENCY; + }) + .map((endpoint) => endpoint.id); + if (tooSmallForConcurrency.length) { + const msg = `Cannot set concurency on the functions ${tooSmallForConcurrency.join( + "," + )} because they have fewer than 2GB memory`; + throw new FirebaseError(msg); + } +} /** * Check that functions directory exists. diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 1dcf479cff4..e6fe910411f 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -374,17 +374,28 @@ describe("Fabricator", () => { ); }); - it("sets invoker and concurrency by default", async () => { + it("sets invoker and concurrency by default for large functions", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); - const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2" }); + const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2", availableMemoryMb: 2048 }); await fab.createV2Function(ep); expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["public"]); expect(setConcurrency).to.have.been.calledWith(ep, "service", 80); }); + it("does not set concurrency by default for small functions", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2", availableMemoryMb: 256 }); + + await fab.createV2Function(ep); + expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["public"]); + expect(setConcurrency).to.not.have.been.called; + }); + it("sets explicit invoker", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); @@ -426,10 +437,13 @@ describe("Fabricator", () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); - const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2", concurrency: 1 }); + const ep = endpoint( + { httpsTrigger: {} }, + { platform: "gcfv2", availableMemoryMb: 2048, concurrency: 2 } + ); await fab.createV2Function(ep); - expect(setConcurrency).to.have.been.calledWith(ep, "service", 1); + expect(setConcurrency).to.have.been.calledWith(ep, "service", 2); }); }); diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index 395ab96568c..82d6f583f44 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -5,6 +5,7 @@ import { FirebaseError } from "../../../error"; import * as fsutils from "../../../fsutils"; import * as validate from "../../../deploy/functions/validate"; import * as projectPath from "../../../projectPath"; +import * as backend from "../../../deploy/functions/backend"; describe("validate", () => { describe("functionsDirectoryExists", () => { @@ -113,4 +114,88 @@ describe("validate", () => { }).to.throw(FirebaseError); }); }); + + describe("endpointsAreValid", () => { + const ENDPOINT_BASE: backend.Endpoint = { + platform: "gcfv2", + id: "id", + region: "us-east1", + project: "project", + entryPoint: "func", + runtime: "nodejs16", + httpsTrigger: {}, + }; + + it("Disallows concurrency for GCF gen 1", () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + platform: "gcfv1", + availableMemoryMb: backend.MIN_MEMORY_FOR_CONCURRENCY, + concurrency: 2, + }; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw(/GCF gen 1/); + }); + + it("Allows endpoints with no mem and no concurrency", () => { + expect(() => validate.endpointsAreValid(backend.of(ENDPOINT_BASE))).to.not.throw; + }); + + it("Allows endpionts with mem and no concurrency", () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + availableMemoryMb: 256, + }; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; + }); + + it("Allows explicitly one concurrent", () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + concurrency: 1, + }; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; + }); + + it("Allows endpoints with enough mem and no concurrency", () => { + for (const mem of [2 << 10, 4 << 10, 8 << 10] as backend.MemoryOptions[]) { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + availableMemoryMb: mem, + }; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; + } + }); + + it("Allows endpoints with enough mem and explicit concurrency", () => { + for (const mem of [2 << 10, 4 << 10, 8 << 10] as backend.MemoryOptions[]) { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + availableMemoryMb: mem, + concurrency: 42, + }; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; + } + }); + + it("Disallows concurrency with too little memory (implicit)", () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + concurrency: 2, + }; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( + /they have fewer than 2GB memory/ + ); + }); + + it("Disallows concurrency with too little memory (explicit)", () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + concurrency: 2, + availableMemoryMb: 512, + }; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( + /they have fewer than 2GB memory/ + ); + }); + }); }); From ef206f2cf8ec97056d500b56f4adcbde80d72d7f Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 31 Jan 2022 10:37:07 -0800 Subject: [PATCH 0054/1699] Adding changelog entry for #4113 (#4114) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1343034ef17..11f5c829fc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Updates Hosting initial `index.html` to be proper javascript. +- Fix issue where the Cloud Functions for Firebase Emulator would incorrectly log a node version mismatch (#4024). From 0043ce5b844898739ae3ecaaef1c2d345e42b3f0 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 31 Jan 2022 18:48:16 +0000 Subject: [PATCH 0055/1699] 10.1.3 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e40fefb2061..96c4d1a65f9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.1.2", + "version": "10.1.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.1.2", + "version": "10.1.3", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index c092e7ec7a4..de548cbbb2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.1.2", + "version": "10.1.3", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 58344e0f631ac19f6b60cdcf2177863b289a0f8c Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 31 Jan 2022 18:48:41 +0000 Subject: [PATCH 0056/1699] [firebase-release] Removed change log and reset repo after 10.1.3 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f5c829fc6..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Updates Hosting initial `index.html` to be proper javascript. -- Fix issue where the Cloud Functions for Firebase Emulator would incorrectly log a node version mismatch (#4024). From 366f06b284b5371ee46ccf176bd1a093b749c9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andi=20P=C3=A4tzold?= Date: Mon, 31 Jan 2022 22:35:38 +0100 Subject: [PATCH 0057/1699] Remove @types/winston dependency (#4110) Co-authored-by: Bryan Kendall --- npm-shrinkwrap.json | 20 -------------------- package.json | 1 - 2 files changed, 21 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 96c4d1a65f9..12dc976f0d2 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -116,7 +116,6 @@ "@types/triple-beam": "^1.3.0", "@types/unzipper": "^0.10.0", "@types/uuid": "^8.3.1", - "@types/winston": "^2.4.4", "@types/ws": "^7.2.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", @@ -2359,16 +2358,6 @@ "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", "dev": true }, - "node_modules/@types/winston": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", - "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", - "deprecated": "This is a stub types definition. winston provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "winston": "*" - } - }, "node_modules/@types/ws": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.3.tgz", @@ -15436,15 +15425,6 @@ "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", "dev": true }, - "@types/winston": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", - "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", - "dev": true, - "requires": { - "winston": "*" - } - }, "@types/ws": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.3.tgz", diff --git a/package.json b/package.json index de548cbbb2b..5d6538064a4 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,6 @@ "@types/triple-beam": "^1.3.0", "@types/unzipper": "^0.10.0", "@types/uuid": "^8.3.1", - "@types/winston": "^2.4.4", "@types/ws": "^7.2.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", From aee925a77d0d51fbff8b5f1b22a69f7fc5278d9f Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Mon, 31 Jan 2022 17:22:23 -0500 Subject: [PATCH 0058/1699] Emulator: change default-bucket to the actual default bucket (#4100) * changing default bucket to project id and appspot url * using firebase config storage bucket * add changelog entry --- CHANGELOG.md | 1 + src/emulator/storage/apis/gcloud.ts | 4 ++-- src/emulator/storage/files.ts | 14 +++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..1da33383202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Updates the Storage Emulator to use the actual default storage bucket. diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index 640a0cc8358..fe74e16645f 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -27,10 +27,10 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { next(); }); - gcloudStorageAPI.get("/b", (req, res) => { + gcloudStorageAPI.get("/b", async (req, res) => { res.json({ kind: "storage#buckets", - items: storageLayer.listBuckets(), + items: await storageLayer.listBuckets(), }); }); diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 1fd0efb24e7..fbf089485f1 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -14,6 +14,10 @@ import * as fse from "fs-extra"; import * as rimraf from "rimraf"; import { StorageCloudFunctions } from "./cloudFunctions"; import { logger } from "../../logger"; +import { + constructDefaultAdminSdkConfig, + getProjectAdminSdkConfigOrCached, +} from "../adminSdkConfig"; interface BucketsList { buckets: { @@ -140,9 +144,13 @@ export class StorageLayer { } } - listBuckets(): CloudStorageBucketMetadata[] { + async listBuckets(): Promise { if (this._buckets.size == 0) { - this.createBucket("default-bucket"); + let adminSdkConfig = await getProjectAdminSdkConfigOrCached(this._projectId); + if (!adminSdkConfig) { + adminSdkConfig = constructDefaultAdminSdkConfig(this._projectId); + } + this.createBucket(adminSdkConfig.storageBucket!); } return [...this._buckets.values()]; @@ -494,7 +502,7 @@ export class StorageLayer { const bucketsList: BucketsList = { buckets: [], }; - for (const b of this.listBuckets()) { + for (const b of await this.listBuckets()) { bucketsList.buckets.push({ id: b.id }); } const bucketsFilePath = path.join(storageExportPath, "buckets.json"); From e261a2f0b35543052a7e2f7d6f5ce0df59738ca0 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 1 Feb 2022 10:21:26 -0800 Subject: [PATCH 0059/1699] uses correct endpoint to get default bucket (#4121) * uses correct endpoint to get default bucket * add test for storage deploy * set rule so that one still needs to be authed to access our storage in test --- .github/workflows/node-test.yml | 1 + CHANGELOG.md | 1 + package.json | 1 + scripts/storage-deploy-tests/run.sh | 54 +++++++++++++++++++++++++++++ src/gcp/storage.ts | 8 ++--- 5 files changed, 61 insertions(+), 4 deletions(-) create mode 100755 scripts/storage-deploy-tests/run.sh diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 99e28b3be35..11063906d25 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -73,6 +73,7 @@ jobs: - npm run test:emulator - npm run test:extensions-emulator - npm run test:triggers-end-to-end + - npm run test:storage-deploy - npm run test:storage-emulator-integration steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1da33383202..450317cf41b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Updates the Storage Emulator to use the actual default storage bucket. +- Fixes issue where `deploy` would fail with a `JSON: SyntaxError` error. (#4117) diff --git a/package.json b/package.json index 5d6538064a4..63dd6d41a94 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "test:extensions-emulator": "./scripts/extensions-emulator-tests/run.sh", "test:hosting": "./scripts/hosting-tests/run.sh", "test:triggers-end-to-end": "./scripts/triggers-end-to-end-tests/run.sh", + "test:storage-deploy": "./scripts/storage-deploy-tests/run.sh", "test:storage-emulator-integration": "./scripts/storage-emulator-integration/run.sh" }, "files": [ diff --git a/scripts/storage-deploy-tests/run.sh b/scripts/storage-deploy-tests/run.sh new file mode 100755 index 00000000000..0d3568e370e --- /dev/null +++ b/scripts/storage-deploy-tests/run.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -e +CWD="$(pwd)" + +source scripts/set-default-credentials.sh + +TARGET_FILE="${COMMIT_SHA}-${CI_JOB_ID}.txt" + +echo "Running in ${CWD}" +echo "Running with node: $(which node)" +echo "Running with npm: $(which npm)" +echo "Running with Application Creds: ${GOOGLE_APPLICATION_CREDENTIALS}" + +echo "Target project: ${FBTOOLS_TARGET_PROJECT}" + +echo "Initializing some variables..." +DATE="$(date)" +NUMBER="$(date '+%Y%m%d%H%M%S')" +echo "Variables initalized..." + +echo "Creating temp directory..." +TEMP_DIR="$(mktemp -d)" +echo "Created temp directory: ${TEMP_DIR}" + +echo "Installing firebase-tools..." +./scripts/npm-link.sh +echo "Installed firebase-tools: $(which firebase)" + +echo "Initializing temp directory..." +cd "${TEMP_DIR}" +cat > "firebase.json" <<- EOM +{ + "storage": { + "rules": "storage.rules" + } +} +EOM +cat > "storage.rules" <<- EOM +rules_version = '2'; +service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write: if request.auth!=null && $NUMBER == $NUMBER; + } + } +} +EOM +echo "Initialized temp directory." + +echo "Testing storage deployment..." +firebase deploy --only storage --project "${FBTOOLS_TARGET_PROJECT}" +RET_CODE="$?" +test "${RET_CODE}" == "0" || (echo "Expected exit code ${RET_CODE} to equal 0." && false) +echo "Tested storage deployment." diff --git a/src/gcp/storage.ts b/src/gcp/storage.ts index 51a84b3465d..60fb9c80e17 100644 --- a/src/gcp/storage.ts +++ b/src/gcp/storage.ts @@ -1,7 +1,7 @@ import { Readable } from "stream"; import * as path from "path"; -import api, { storageOrigin } from "../api"; +import api, { appengineOrigin, storageOrigin } from "../api"; import { Client } from "../apiv2"; import { logger } from "../logger"; import { FirebaseError } from "../error"; @@ -128,11 +128,10 @@ interface StorageServiceAccountResponse { kind: string; } -const storageAPIClient = new Client({ urlPrefix: storageOrigin, apiVersion: "v1" }); - export async function getDefaultBucket(projectId?: string): Promise { try { - const resp = await storageAPIClient.get<{ defaultBucket: string }>(`/apps/${projectId}`); + const appengineClient = new Client({ urlPrefix: appengineOrigin, apiVersion: "v1" }); + const resp = await appengineClient.get<{ defaultBucket: string }>(`/apps/${projectId}`); if (resp.body.defaultBucket === "undefined") { logger.debug("Default storage bucket is undefined."); throw new FirebaseError( @@ -183,6 +182,7 @@ export async function uploadObject( if (path.extname(source.file) !== ".zip") { throw new FirebaseError(`Expected a file name ending in .zip, got ${source.file}`); } + const storageAPIClient = new Client({ urlPrefix: storageOrigin, apiVersion: "v1" }); const location = `/${bucketName}/${path.basename(source.file)}`; const res = await storageAPIClient.request({ method: "PUT", From 2751efc05290cff234c86d4fadd86281461030c6 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 1 Feb 2022 19:00:38 +0000 Subject: [PATCH 0060/1699] 10.1.4 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 12dc976f0d2..a0dc970d5a4 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.1.3", + "version": "10.1.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.1.3", + "version": "10.1.4", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 63dd6d41a94..968502175f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.1.3", + "version": "10.1.4", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 727db0daf6363bb61e7f0cb3c6447afb4ba4291a Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 1 Feb 2022 19:01:02 +0000 Subject: [PATCH 0061/1699] [firebase-release] Removed change log and reset repo after 10.1.4 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 450317cf41b..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Updates the Storage Emulator to use the actual default storage bucket. -- Fixes issue where `deploy` would fail with a `JSON: SyntaxError` error. (#4117) From 6f180236d655ae964fc6054591bd7e10a75c9c71 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Wed, 2 Feb 2022 10:13:36 -0500 Subject: [PATCH 0062/1699] [fix] ext:install - Fix bug where only firebase extensions can be installed (#4120) * update convertOfficialExtensionsToList * Sort official extension list --- src/extensions/utils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/extensions/utils.ts b/src/extensions/utils.ts index 705e285ba51..45785373bf3 100644 --- a/src/extensions/utils.ts +++ b/src/extensions/utils.ts @@ -33,12 +33,14 @@ export function convertExtensionOptionToLabeledList(options: ParamOption[]): Lis export function convertOfficialExtensionsToList(officialExts: { [key: string]: RegistryEntry; }): ListItem[] { - return _.map(officialExts, (entry: RegistryEntry, key: string) => { + const l = _.map(officialExts, (entry: RegistryEntry, key: string) => { return { checked: false, - value: key, + value: `${entry.publisher}/${key}`, }; }); + l.sort((a, b) => a.value.localeCompare(b.value)); + return l; } /** From d72dc00fdab0399200ec0fc40d180606478e0e9a Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Fri, 4 Feb 2022 10:36:39 -0500 Subject: [PATCH 0063/1699] [Cleanup] Clean up Extension's old registry.json related dead code (#4126) * Trim RegistryEntry fields * Clean up unused functions * Remove min version check since it's a noop * Delete more unused functions * Cleanup * Format * Cleanup registry.json usage in tests --- src/commands/ext-info.ts | 1 - src/extensions/extensionsHelper.ts | 25 +---- src/extensions/resolveSource.ts | 101 +------------------ src/extensions/updateHelper.ts | 18 ---- src/extensions/warnings.ts | 3 + src/test/extensions/extensionsHelper.spec.ts | 66 ------------ src/test/extensions/resolveSource.spec.ts | 18 ---- src/test/extensions/updateHelper.spec.ts | 61 ----------- 8 files changed, 7 insertions(+), 286 deletions(-) diff --git a/src/commands/ext-info.ts b/src/commands/ext-info.ts index d1a05b27d96..e78fc40e725 100644 --- a/src/commands/ext-info.ts +++ b/src/commands/ext-info.ts @@ -3,7 +3,6 @@ import * as _ from "lodash"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; -import { resolveRegistryEntry } from "../extensions/resolveSource"; import * as extensionsApi from "../extensions/extensionsApi"; import { ensureExtensionsApiEnabled, logPrefix } from "../extensions/extensionsHelper"; import { isLocalExtension, getLocalExtensionSpec } from "../extensions/localHelper"; diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 2b8ca99679b..7e82a14c5b8 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -14,7 +14,7 @@ import { storageOrigin } from "../api"; import { archiveDirectory } from "../archiveDirectory"; import { convertOfficialExtensionsToList } from "./utils"; import { getFirebaseConfig } from "../functionsConfig"; -import { getExtensionRegistry, resolveSourceUrl, resolveRegistryEntry } from "./resolveSource"; +import { getExtensionRegistry } from "./resolveSource"; import { FirebaseError } from "../error"; import { checkResponse } from "./askUserForParam"; import { ensure } from "../ensureApiEnabled"; @@ -26,7 +26,6 @@ import { ExtensionVersion, getExtension, getInstance, - getSource, Param, publishExtensionVersion, } from "./extensionsApi"; @@ -564,28 +563,6 @@ async function deleteUploadedSource(objectPath: string) { } } -/** - * Looks up a ExtensionSource from a extensionName. If no source exists for that extensionName, returns undefined. - * @param extensionName a official extension source name - * or a One-Platform format source name (/project//sources/) - * @return an ExtensionSource corresponding to extensionName if one exists, undefined otherwise - */ -export async function getExtensionSourceFromName(extensionName: string): Promise { - const officialExtensionRegex = /^[a-zA-Z\-]+[0-9@.]*$/; - const existingSourceRegex = /projects\/.+\/sources\/.+/; - // if the provided extensionName contains only letters and hyphens, assume it is an official extension - if (officialExtensionRegex.test(extensionName)) { - const [name, version] = extensionName.split("@"); - const registryEntry = await resolveRegistryEntry(name); - const sourceUrl = resolveSourceUrl(registryEntry, name, version); - return await getSource(sourceUrl); - } else if (existingSourceRegex.test(extensionName)) { - logger.info(`Fetching the source "${extensionName}"...`); - return await getSource(extensionName); - } - throw new FirebaseError(`Could not find an extension named '${extensionName}'. `); -} - /** * Parses the publisher project number from publisher profile name. */ diff --git a/src/extensions/resolveSource.ts b/src/extensions/resolveSource.ts index ad5d0c35fa6..3cd6e6963b1 100644 --- a/src/extensions/resolveSource.ts +++ b/src/extensions/resolveSource.ts @@ -1,110 +1,15 @@ import * as _ from "lodash"; -import * as clc from "cli-color"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); -import * as semver from "semver"; -import * as api from "../api"; -import { FirebaseError } from "../error"; import { logger } from "../logger"; -import { promptOnce } from "../prompt"; import { Client } from "../apiv2"; import { firebaseExtensionsRegistryOrigin } from "../api"; const EXTENSIONS_REGISTRY_ENDPOINT = "/extensions.json"; -export interface RegistryEntry { - icons?: { [key: string]: string }; - labels: { [key: string]: string }; - versions: { [key: string]: string }; - updateWarnings?: { [key: string]: UpdateWarning[] }; - publisher: string; -} - -export interface UpdateWarning { - from: string; - description: string; - action?: string; -} - -/** - * Displays an update warning as markdown, and prompts the user for confirmation. - * @param updateWarning The update warning to display and prompt for. - */ -export async function confirmUpdateWarning(updateWarning: UpdateWarning): Promise { - logger.info(marked(updateWarning.description)); - if (updateWarning.action) { - logger.info(marked(updateWarning.action)); - } - const continueUpdate = await promptOnce({ - type: "confirm", - message: "Do you wish to continue with this update?", - default: false, - }); - if (!continueUpdate) { - throw new FirebaseError(`Update cancelled.`, { exit: 2 }); - } -} - /** - * Gets the sourceUrl for a given extension name and version from a registry entry - * @param registryEntry the registry entry to look through. - * @param name the name of the extension. - * @param version the version of the extension. Defaults to latest. - * @returns the source corresponding to extensionName in the registry. + * An Entry on the deprecated registry.json list. */ -export function resolveSourceUrl( - registryEntry: RegistryEntry, - name: string, - version?: string -): string { - const targetVersion = getTargetVersion(registryEntry, version); - const sourceUrl = _.get(registryEntry, ["versions", targetVersion]); - if (!sourceUrl) { - throw new FirebaseError( - `Could not find version ${clc.bold(version)} of extension ${clc.bold(name)}.` - ); - } - return sourceUrl; -} - -/** - * Checks if the given source comes from an official extension. - * @param registryEntry the registry entry to look through. - * @param sourceUrl the source URL of the extension. - */ -export function isOfficialSource(registryEntry: RegistryEntry, sourceUrl: string): boolean { - const versions = _.get(registryEntry, "versions"); - return _.includes(versions, sourceUrl); -} - -/** - * Looks up and returns a entry from the published extensions registry. - * @param name the name of the extension. - */ -export async function resolveRegistryEntry(name: string): Promise { - const extensionsRegistry = await getExtensionRegistry(); - const registryEntry = _.get(extensionsRegistry, name); - if (!registryEntry) { - throw new FirebaseError(`Unable to find extension source named ${clc.bold(name)}.`); - } - return registryEntry; -} - -/** - * Resolves a version or label to a version. - * @param registryEntry A registry entry to get the version from. - * @param versionOrLabel A version or label to resolve. Defaults to 'latest'. - */ -export function getTargetVersion(registryEntry: RegistryEntry, versionOrLabel?: string): string { - // The version to search for when a user passes a version x.y.z or no version. - const seekVersion = versionOrLabel || "latest"; - // The version to search for when a user passes a label like 'latest'. - const versionFromLabel = _.get(registryEntry, ["labels", seekVersion]); - return versionFromLabel || seekVersion; -} - -export function getMinRequiredVersion(registryEntry: RegistryEntry): string { - return _.get(registryEntry, ["labels", "minRequired"]); +export interface RegistryEntry { + publisher: string; } /** diff --git a/src/extensions/updateHelper.ts b/src/extensions/updateHelper.ts index 33e7470b2c3..74052463fff 100644 --- a/src/extensions/updateHelper.ts +++ b/src/extensions/updateHelper.ts @@ -5,7 +5,6 @@ const { marked } = require("marked"); import { FirebaseError } from "../error"; import { logger } from "../logger"; -import * as resolveSource from "./resolveSource"; import * as extensionsApi from "./extensionsApi"; import * as refs from "./refs"; import { @@ -243,24 +242,7 @@ export async function updateToVersionFromPublisherSource( )}).` ); } - let registryEntry; - try { - registryEntry = await resolveSource.resolveRegistryEntry(existingSpec.name); - } catch (err: any) { - logger.debug(`Unable to fetch registry.json entry for ${existingSpec.name}`); - } - if (registryEntry) { - // Do not allow user to "downgrade" to a version lower than the minimum required version. - const minVer = resolveSource.getMinRequiredVersion(registryEntry); - if (minVer && semver.gt(minVer, source.spec.version)) { - throw new FirebaseError( - `The version you are trying to update to (${clc.bold( - source.spec.version - )}) is less than the minimum version required (${clc.bold(minVer)}) to use this extension.` - ); - } - } showUpdateVersionInfo(instanceId, existingSpec.version, source.spec.version, extVersionRef); warningUpdateToOtherSource(SourceOrigin.PUBLISHED_EXTENSION); const releaseNotes = await changelog.getReleaseNotesForUpdate({ diff --git a/src/extensions/warnings.ts b/src/extensions/warnings.ts index f06f77a3010..d4c3188c098 100644 --- a/src/extensions/warnings.ts +++ b/src/extensions/warnings.ts @@ -41,6 +41,9 @@ function displayExperimentalWarning() { ); } +/** + * Show warning if extension is experimental or developed by 3P. + */ export async function displayWarningPrompts( publisherId: string, launchStage: RegistryLaunchStage, diff --git a/src/test/extensions/extensionsHelper.spec.ts b/src/test/extensions/extensionsHelper.spec.ts index 984afd1c95f..b163751b02f 100644 --- a/src/test/extensions/extensionsHelper.spec.ts +++ b/src/test/extensions/extensionsHelper.spec.ts @@ -802,72 +802,6 @@ describe("extensionsHelper", () => { }); }); - describe("getExtensionSourceFromName", () => { - let resolveRegistryEntryStub: sinon.SinonStub; - let getSourceStub: sinon.SinonStub; - - const testOnePlatformSourceName = "projects/test-proj/sources/abc123"; - const testRegistyEntry = { - labels: { latest: "0.1.1" }, - versions: { - "0.1.0": "projects/test-proj/sources/def456", - "0.1.1": testOnePlatformSourceName, - }, - publisher: "firebase", - }; - const testSource: ExtensionSource = { - name: "test", - packageUri: "", - hash: "abc123", - state: "ACTIVE", - spec: { - name: "", - version: "0.0.0", - sourceUrl: "", - resources: [], - params: [], - }, - }; - - beforeEach(() => { - resolveRegistryEntryStub = sinon - .stub(resolveSource, "resolveRegistryEntry") - .resolves(testRegistyEntry); - getSourceStub = sinon.stub(extensionsApi, "getSource").resolves(testSource); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should look up official source names in the registry and fetch the ExtensionSource found there", async () => { - const testOfficialName = "storage-resize-images"; - - const result = await extensionsHelper.getExtensionSourceFromName(testOfficialName); - - expect(resolveRegistryEntryStub).to.have.been.calledWith(testOfficialName); - expect(getSourceStub).to.have.been.calledWith(testOnePlatformSourceName); - expect(result).to.equal(testSource); - }); - - it("should fetch ExtensionSources when given a one platform name", async () => { - const result = await extensionsHelper.getExtensionSourceFromName(testOnePlatformSourceName); - - expect(resolveRegistryEntryStub).not.to.have.been.called; - expect(getSourceStub).to.have.been.calledWith(testOnePlatformSourceName); - expect(result).to.equal(testSource); - }); - - it("should throw an error if given a invalid namae", async () => { - await expect(extensionsHelper.getExtensionSourceFromName(".")).to.be.rejectedWith( - FirebaseError - ); - - expect(resolveRegistryEntryStub).not.to.have.been.called; - expect(getSourceStub).not.to.have.been.called; - }); - }); - describe("checkIfInstanceIdAlreadyExists", () => { const TEST_NAME = "image-resizer"; let getInstanceStub: sinon.SinonStub; diff --git a/src/test/extensions/resolveSource.spec.ts b/src/test/extensions/resolveSource.spec.ts index eba11b31639..f932bb47c9a 100644 --- a/src/test/extensions/resolveSource.spec.ts +++ b/src/test/extensions/resolveSource.spec.ts @@ -43,21 +43,3 @@ const testRegistryEntry = { ], }, }; - -describe("isPublishedSource", () => { - it("should return true for an published source", () => { - const result = resolveSource.isOfficialSource( - testRegistryEntry, - "projects/firebasemods/sources/2kd" - ); - expect(result).to.be.true; - }); - - it("should return false for an unpublished source", () => { - const result = resolveSource.isOfficialSource( - testRegistryEntry, - "projects/firebasemods/sources/invalid" - ); - expect(result).to.be.false; - }); -}); diff --git a/src/test/extensions/updateHelper.spec.ts b/src/test/extensions/updateHelper.spec.ts index 1c3606e523e..6835ee2719e 100644 --- a/src/test/extensions/updateHelper.spec.ts +++ b/src/test/extensions/updateHelper.spec.ts @@ -6,8 +6,6 @@ import { FirebaseError } from "../../error"; import { firebaseExtensionsRegistryOrigin } from "../../api"; import * as extensionsApi from "../../extensions/extensionsApi"; import * as extensionsHelper from "../../extensions/extensionsHelper"; -import * as prompt from "../../prompt"; -import * as resolveSource from "../../extensions/resolveSource"; import * as updateHelper from "../../extensions/updateHelper"; const SPEC = { @@ -35,8 +33,6 @@ const SPEC = { params: [], }; -const OLD_SPEC = Object.assign({}, SPEC, { version: "0.1.0" }); - const SOURCE = { name: "projects/firebasemods/sources/new-test-source", packageUri: "https://firebase-fake-bucket.com", @@ -61,47 +57,6 @@ const EXTENSION = { latestVersion: "0.2.0", }; -const REGISTRY_ENTRY = { - name: "test", - labels: { - latest: "0.2.0", - minRequired: "0.1.1", - }, - versions: { - "0.1.0": "projects/firebasemods/sources/2kd", - "0.1.1": "projects/firebasemods/sources/xyz", - "0.1.2": "projects/firebasemods/sources/123", - "0.2.0": "projects/firebasemods/sources/abc", - }, - updateWarnings: { - ">0.1.0 <0.2.0": [ - { - from: "0.1.0", - description: - "Starting Jan 15, HTTP functions will be private by default. [Learn more](https://someurl.com)", - action: - "After updating, it is highly recommended that you switch your Cloud Scheduler jobs to PubSub", - }, - ], - ">=0.2.0": [ - { - from: "0.1.0", - description: - "Starting Jan 15, HTTP functions will be private by default. [Learn more](https://someurl.com)", - action: - "After updating, you must switch your Cloud Scheduler jobs to PubSub, otherwise your extension will stop running.", - }, - { - from: ">0.1.0", - description: - "Starting Jan 15, HTTP functions will be private by default. [Learn more](https://someurl.com)", - action: - "If you have not already done so during a previous update, after updating, you must switch your Cloud Scheduler jobs to PubSub, otherwise your extension will stop running.", - }, - ], - }, -}; - const INSTANCE = { name: "projects/invader-zim/instances/instance-of-official-ext", createTime: "2019-05-19T00:20:10.416947Z", @@ -237,18 +192,12 @@ describe("updateHelper", () => { let getExtensionStub: sinon.SinonStub; let createSourceStub: sinon.SinonStub; let listExtensionVersionStub: sinon.SinonStub; - let registryStub: sinon.SinonStub; - let isOfficialStub: sinon.SinonStub; let getInstanceStub: sinon.SinonStub; beforeEach(() => { getExtensionStub = sinon.stub(extensionsApi, "getExtension"); createSourceStub = sinon.stub(extensionsApi, "getExtensionVersion"); listExtensionVersionStub = sinon.stub(extensionsApi, "listExtensionVersions"); - registryStub = sinon.stub(resolveSource, "resolveRegistryEntry"); - registryStub.resolves(REGISTRY_ENTRY); - isOfficialStub = sinon.stub(resolveSource, "isOfficialSource"); - isOfficialStub.returns(false); getInstanceStub = sinon.stub(extensionsApi, "getInstance").resolves(REGISTRY_INSTANCE); }); @@ -256,8 +205,6 @@ describe("updateHelper", () => { getExtensionStub.restore(); createSourceStub.restore(); listExtensionVersionStub.restore(); - registryStub.restore(); - isOfficialStub.restore(); getInstanceStub.restore(); }); @@ -293,18 +240,12 @@ describe("updateHelper", () => { let getExtensionStub: sinon.SinonStub; let createSourceStub: sinon.SinonStub; let listExtensionVersionStub: sinon.SinonStub; - let registryStub: sinon.SinonStub; - let isOfficialStub: sinon.SinonStub; let getInstanceStub: sinon.SinonStub; beforeEach(() => { getExtensionStub = sinon.stub(extensionsApi, "getExtension"); createSourceStub = sinon.stub(extensionsApi, "getExtensionVersion"); listExtensionVersionStub = sinon.stub(extensionsApi, "listExtensionVersions"); - registryStub = sinon.stub(resolveSource, "resolveRegistryEntry"); - registryStub.resolves(REGISTRY_ENTRY); - isOfficialStub = sinon.stub(resolveSource, "isOfficialSource"); - isOfficialStub.returns(false); getInstanceStub = sinon.stub(extensionsApi, "getInstance").resolves(REGISTRY_INSTANCE); }); @@ -312,8 +253,6 @@ describe("updateHelper", () => { getExtensionStub.restore(); createSourceStub.restore(); listExtensionVersionStub.restore(); - registryStub.restore(); - isOfficialStub.restore(); getInstanceStub.restore(); }); From 8b5590aaef7b3e5c9cfd35c48c3ec5b501484f9b Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 4 Feb 2022 15:03:18 -0800 Subject: [PATCH 0064/1699] Updates --no-localhost to use new Auth proxy service. (#4123) * Proof-of-concept for signing in remotely via auth proxy server. * Fix lint error * Update output of --remote * Replace --no-localhost with remote login. * Remove vestiges of --remote * CHANGELOG, fixes emu test. --- CHANGELOG.md | 1 + src/api.js | 1 + src/auth.ts | 97 ++++++++++++++++++++++++++++++++----------- src/commands/login.ts | 5 +-- 4 files changed, 76 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..0b7c242fc16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Improves experience for `firebase login --no-localhost`. diff --git a/src/api.js b/src/api.js index 0419a19391c..0544644c388 100644 --- a/src/api.js +++ b/src/api.js @@ -84,6 +84,7 @@ var _appendQueryData = function (path, data) { }; var api = { + authProxyOrigin: utils.envOverride("FIREBASE_AUTHPROXY_URL", "https://auth.firebase.tools"), // "In this context, the client secret is obviously not treated as a secret" // https://developers.google.com/identity/protocols/OAuth2InstalledApp clientId: utils.envOverride( diff --git a/src/auth.ts b/src/auth.ts index 036ce6f3c72..d14229f09f6 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -17,6 +17,10 @@ import { logger } from "./logger"; import { promptOnce } from "./prompt"; import * as scopes from "./scopes"; import { clearCredentials } from "./defaultCredentials"; +import { v4 as uuidv4 } from "uuid"; +import { randomBytes, createHash } from "crypto"; +import { bold } from "cli-color"; +import track from "./track"; /* eslint-disable camelcase */ // The wire protocol for an access token returned by Google. @@ -324,22 +328,32 @@ function getLoginUrl(callbackUrl: string, userHint?: string) { ); } -async function getTokensFromAuthorizationCode(code: string, callbackUrl: string) { +async function getTokensFromAuthorizationCode( + code: string, + callbackUrl: string, + verifier?: string +) { let res: { body?: TokensWithTTL; statusCode: number; }; + const params: Record = { + code: code, + client_id: api.clientId, + client_secret: api.clientSecret, + redirect_uri: callbackUrl, + grant_type: "authorization_code", + }; + + if (verifier) { + params["code_verifier"] = verifier; + } + try { res = await api.request("POST", "/o/oauth2/token", { origin: api.authOrigin, - form: { - code: code, - client_id: api.clientId, - client_secret: api.clientSecret, - redirect_uri: callbackUrl, - grant_type: "authorization_code", - }, + form: params, }); } catch (err: any) { if (err instanceof Error) { @@ -406,25 +420,56 @@ async function respondWithFile( req.socket.destroy(); } -async function loginWithoutLocalhost(userHint?: string): Promise { - const callbackUrl = getCallbackUrl(); - const authUrl = getLoginUrl(callbackUrl, userHint); +function urlsafeBase64(base64string: string) { + return base64string.replace(/\+/g, "-").replace(/=+$/, "").replace(/\//g, "_"); +} + +async function loginRemotely(userHint?: string): Promise { + const authProxyClient = new apiv2.Client({ + urlPrefix: api.authProxyOrigin, + auth: false, + }); + + const sessionId = uuidv4(); + const codeVerifier = randomBytes(32).toString("hex"); + // urlsafe base64 is required for code_challenge in OAuth PKCE + const codeChallenge = urlsafeBase64(createHash("sha256").update(codeVerifier).digest("base64")); + + const attestToken = ( + await authProxyClient.post<{ session_id: string }, { token: string }>("/attest", { + session_id: sessionId, + }) + ).body?.token; + + const loginUrl = `${api.authProxyOrigin}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`; logger.info(); - logger.info("Visit this URL on any device to log in:"); - logger.info(clc.bold.underline(authUrl)); + logger.info("To sign in to the Firebase CLI:"); + logger.info(); + logger.info("1. Take note of your session ID:"); + logger.info(); + logger.info(` ${bold(sessionId.substring(0, 5).toUpperCase())}`); + logger.info(); + logger.info("2. Visit the URL below on any device and follow the instructions to get your code:"); + logger.info(); + logger.info(` ${loginUrl}`); + logger.info(); + logger.info("3. Paste or enter the authorization code below once you have it:"); logger.info(); - open(authUrl); - - const code: string = await promptOnce({ + const code = await promptOnce({ type: "input", - name: "code", - message: "Paste authorization code here:", + message: "Enter authorization code:", }); - const tokens = await getTokensFromAuthorizationCode(code, callbackUrl); - // getTokensFromAuthorizationCode doesn't handle the --token case, so we know - // that we'll have a valid id_token. + + const tokens = await getTokensFromAuthorizationCode( + code, + `${api.authProxyOrigin}/complete`, + codeVerifier + ); + + track("login", "google_remote"); + return { user: jwt.decode(tokens.id_token!) as User, tokens: tokens, @@ -443,6 +488,8 @@ async function loginWithLocalhostGoogle(port: number, userHint?: string): Promis successTemplate, getTokensFromAuthorizationCode ); + + track("login", "google_localhost"); // getTokensFromAuthoirzationCode doesn't handle the --token case, so we know we'll // always have an id_token. return { @@ -456,13 +503,15 @@ async function loginWithLocalhostGitHub(port: number): Promise { const callbackUrl = getCallbackUrl(port); const authUrl = getGithubLoginUrl(callbackUrl); const successTemplate = "../templates/loginSuccessGithub.html"; - return loginWithLocalhost( + const tokens = await loginWithLocalhost( port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode ); + track("login", "google_localhost"); + return tokens; } async function loginWithLocalhost( @@ -521,10 +570,10 @@ export async function loginGoogle(localhost: boolean, userHint?: string): Promis const port = await getPort(); return await loginWithLocalhostGoogle(port, userHint); } catch { - return await loginWithoutLocalhost(userHint); + return await loginRemotely(userHint); } } - return await loginWithoutLocalhost(userHint); + return await loginRemotely(userHint); } export async function loginGithub(): Promise { diff --git a/src/commands/login.ts b/src/commands/login.ts index 81b84bdf8e7..43e2ecda5fd 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -13,10 +13,7 @@ import { isCloudEnvironment } from "../utils"; module.exports = new Command("login") .description("log the CLI into Firebase") - .option( - "--no-localhost", - "copy and paste a code instead of starting a local server for authentication" - ) + .option("--no-localhost", "login from a device without an accessible localhost") .option("--reauth", "force reauthentication even if already logged in") .action(async (options: any) => { if (options.nonInteractive) { From aa731b8ac27094506bb8be84231c7e6ac9ce7c1d Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 7 Feb 2022 16:23:58 -0600 Subject: [PATCH 0065/1699] better json-parsing-error logging (#4135) --- src/apiv2.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/apiv2.ts b/src/apiv2.ts index a2bd4488cf0..6a9b6db458c 100644 --- a/src/apiv2.ts +++ b/src/apiv2.ts @@ -410,6 +410,8 @@ export class Client { try { body = JSON.parse(text) as ResT; } catch (err: unknown) { + // JSON-parse errors are useless. Log the response for better debugging. + this.logResponse(res, text, options); throw new FirebaseError(`Unable to parse JSON: ${err}`); } } From b0aa4f2f41fe557679c65045af574735a3eaf200 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 7 Feb 2022 16:04:18 -0800 Subject: [PATCH 0066/1699] Fix uploadObject function to make request to correct url. (#4136) This fixes failing installation of local firebase extensions. Bug sneaked in at https://github.com/firebase/firebase-tools/pull/4031. --- CHANGELOG.md | 1 + src/gcp/storage.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b7c242fc16..408b14c6794 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Improves experience for `firebase login --no-localhost`. +- Fixes bug where local extension installation failed to upload source to GCS bucket. diff --git a/src/gcp/storage.ts b/src/gcp/storage.ts index 60fb9c80e17..0a2cac10551 100644 --- a/src/gcp/storage.ts +++ b/src/gcp/storage.ts @@ -182,9 +182,9 @@ export async function uploadObject( if (path.extname(source.file) !== ".zip") { throw new FirebaseError(`Expected a file name ending in .zip, got ${source.file}`); } - const storageAPIClient = new Client({ urlPrefix: storageOrigin, apiVersion: "v1" }); + const localAPIClient = new Client({ urlPrefix: storageOrigin }); const location = `/${bucketName}/${path.basename(source.file)}`; - const res = await storageAPIClient.request({ + const res = await localAPIClient.request({ method: "PUT", path: location, headers: { From 0da1f308e2ef2cc0155696cf2ab92fa07e694bc4 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 7 Feb 2022 16:25:36 -0800 Subject: [PATCH 0067/1699] Revert "Updates --no-localhost to use new Auth proxy service." (#4137) * Revert "Updates --no-localhost to use new Auth proxy service. (#4123)" This reverts commit 8b5590aaef7b3e5c9cfd35c48c3ec5b501484f9b. * formats --- CHANGELOG.md | 3 +- src/api.js | 1 - src/auth.ts | 97 +++++++++++-------------------------------- src/commands/login.ts | 5 ++- 4 files changed, 29 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 408b14c6794..6f3ab754c63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1 @@ -- Improves experience for `firebase login --no-localhost`. -- Fixes bug where local extension installation failed to upload source to GCS bucket. +- Fixes bug where local extension installation and ext:publish failed to upload source to GCS bucket. diff --git a/src/api.js b/src/api.js index 0544644c388..0419a19391c 100644 --- a/src/api.js +++ b/src/api.js @@ -84,7 +84,6 @@ var _appendQueryData = function (path, data) { }; var api = { - authProxyOrigin: utils.envOverride("FIREBASE_AUTHPROXY_URL", "https://auth.firebase.tools"), // "In this context, the client secret is obviously not treated as a secret" // https://developers.google.com/identity/protocols/OAuth2InstalledApp clientId: utils.envOverride( diff --git a/src/auth.ts b/src/auth.ts index d14229f09f6..036ce6f3c72 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -17,10 +17,6 @@ import { logger } from "./logger"; import { promptOnce } from "./prompt"; import * as scopes from "./scopes"; import { clearCredentials } from "./defaultCredentials"; -import { v4 as uuidv4 } from "uuid"; -import { randomBytes, createHash } from "crypto"; -import { bold } from "cli-color"; -import track from "./track"; /* eslint-disable camelcase */ // The wire protocol for an access token returned by Google. @@ -328,32 +324,22 @@ function getLoginUrl(callbackUrl: string, userHint?: string) { ); } -async function getTokensFromAuthorizationCode( - code: string, - callbackUrl: string, - verifier?: string -) { +async function getTokensFromAuthorizationCode(code: string, callbackUrl: string) { let res: { body?: TokensWithTTL; statusCode: number; }; - const params: Record = { - code: code, - client_id: api.clientId, - client_secret: api.clientSecret, - redirect_uri: callbackUrl, - grant_type: "authorization_code", - }; - - if (verifier) { - params["code_verifier"] = verifier; - } - try { res = await api.request("POST", "/o/oauth2/token", { origin: api.authOrigin, - form: params, + form: { + code: code, + client_id: api.clientId, + client_secret: api.clientSecret, + redirect_uri: callbackUrl, + grant_type: "authorization_code", + }, }); } catch (err: any) { if (err instanceof Error) { @@ -420,56 +406,25 @@ async function respondWithFile( req.socket.destroy(); } -function urlsafeBase64(base64string: string) { - return base64string.replace(/\+/g, "-").replace(/=+$/, "").replace(/\//g, "_"); -} - -async function loginRemotely(userHint?: string): Promise { - const authProxyClient = new apiv2.Client({ - urlPrefix: api.authProxyOrigin, - auth: false, - }); - - const sessionId = uuidv4(); - const codeVerifier = randomBytes(32).toString("hex"); - // urlsafe base64 is required for code_challenge in OAuth PKCE - const codeChallenge = urlsafeBase64(createHash("sha256").update(codeVerifier).digest("base64")); - - const attestToken = ( - await authProxyClient.post<{ session_id: string }, { token: string }>("/attest", { - session_id: sessionId, - }) - ).body?.token; - - const loginUrl = `${api.authProxyOrigin}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`; +async function loginWithoutLocalhost(userHint?: string): Promise { + const callbackUrl = getCallbackUrl(); + const authUrl = getLoginUrl(callbackUrl, userHint); logger.info(); - logger.info("To sign in to the Firebase CLI:"); - logger.info(); - logger.info("1. Take note of your session ID:"); - logger.info(); - logger.info(` ${bold(sessionId.substring(0, 5).toUpperCase())}`); - logger.info(); - logger.info("2. Visit the URL below on any device and follow the instructions to get your code:"); - logger.info(); - logger.info(` ${loginUrl}`); - logger.info(); - logger.info("3. Paste or enter the authorization code below once you have it:"); + logger.info("Visit this URL on any device to log in:"); + logger.info(clc.bold.underline(authUrl)); logger.info(); - const code = await promptOnce({ + open(authUrl); + + const code: string = await promptOnce({ type: "input", - message: "Enter authorization code:", + name: "code", + message: "Paste authorization code here:", }); - - const tokens = await getTokensFromAuthorizationCode( - code, - `${api.authProxyOrigin}/complete`, - codeVerifier - ); - - track("login", "google_remote"); - + const tokens = await getTokensFromAuthorizationCode(code, callbackUrl); + // getTokensFromAuthorizationCode doesn't handle the --token case, so we know + // that we'll have a valid id_token. return { user: jwt.decode(tokens.id_token!) as User, tokens: tokens, @@ -488,8 +443,6 @@ async function loginWithLocalhostGoogle(port: number, userHint?: string): Promis successTemplate, getTokensFromAuthorizationCode ); - - track("login", "google_localhost"); // getTokensFromAuthoirzationCode doesn't handle the --token case, so we know we'll // always have an id_token. return { @@ -503,15 +456,13 @@ async function loginWithLocalhostGitHub(port: number): Promise { const callbackUrl = getCallbackUrl(port); const authUrl = getGithubLoginUrl(callbackUrl); const successTemplate = "../templates/loginSuccessGithub.html"; - const tokens = await loginWithLocalhost( + return loginWithLocalhost( port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode ); - track("login", "google_localhost"); - return tokens; } async function loginWithLocalhost( @@ -570,10 +521,10 @@ export async function loginGoogle(localhost: boolean, userHint?: string): Promis const port = await getPort(); return await loginWithLocalhostGoogle(port, userHint); } catch { - return await loginRemotely(userHint); + return await loginWithoutLocalhost(userHint); } } - return await loginRemotely(userHint); + return await loginWithoutLocalhost(userHint); } export async function loginGithub(): Promise { diff --git a/src/commands/login.ts b/src/commands/login.ts index 43e2ecda5fd..81b84bdf8e7 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -13,7 +13,10 @@ import { isCloudEnvironment } from "../utils"; module.exports = new Command("login") .description("log the CLI into Firebase") - .option("--no-localhost", "login from a device without an accessible localhost") + .option( + "--no-localhost", + "copy and paste a code instead of starting a local server for authentication" + ) .option("--reauth", "force reauthentication even if already logged in") .action(async (options: any) => { if (options.nonInteractive) { From 0f8450146bf2622a5715636bee72381ca067f9aa Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 8 Feb 2022 00:33:04 +0000 Subject: [PATCH 0068/1699] 10.1.5 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a0dc970d5a4..841fc6f944d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.1.4", + "version": "10.1.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.1.4", + "version": "10.1.5", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 968502175f7..9b90ea50939 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.1.4", + "version": "10.1.5", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From c1398799daef6a4cec784f5024b4944fb76281f2 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 8 Feb 2022 00:33:29 +0000 Subject: [PATCH 0069/1699] [firebase-release] Removed change log and reset repo after 10.1.5 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f3ab754c63..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes bug where local extension installation and ext:publish failed to upload source to GCS bucket. From 6bc00b4345581fea744651dc15d3b1b3bf32481c Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Wed, 9 Feb 2022 12:41:28 -0500 Subject: [PATCH 0070/1699] Implemented basic project diagnostic check before extension install (#4139) --- src/commands/ext-configure.ts | 3 +- src/commands/ext-install.ts | 2 + src/commands/ext-uninstall.ts | 2 + src/commands/ext-update.ts | 2 + src/extensions/diagnose.ts | 74 +++++++++++++++++++++ src/extensions/extensionsHelper.ts | 9 +++ src/test/extensions/diagnose.spec.ts | 96 ++++++++++++++++++++++++++++ 7 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/extensions/diagnose.ts create mode 100644 src/test/extensions/diagnose.spec.ts diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 21bbf62a751..304a112ace0 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -10,7 +10,7 @@ import { Command } from "../command"; import { FirebaseError } from "../error"; import { needProjectId } from "../projectUtils"; import * as extensionsApi from "../extensions/extensionsApi"; -import { logPrefix } from "../extensions/extensionsHelper"; +import { logPrefix, diagnoseAndFixProject } from "../extensions/extensionsHelper"; import * as paramHelper from "../extensions/paramHelper"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; @@ -32,6 +32,7 @@ export default new Command("ext:configure ") "firebaseextensions.instances.get", ]) .before(checkMinRequiredVersion, "extMinVersion") + .before(diagnoseAndFixProject) .action(async (instanceId: string, options: any) => { const spinner = ora( `Configuring ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...` diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 3c620530c21..6d4fa2d5d85 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -30,6 +30,7 @@ import { promptForRepeatInstance, promptForValidInstanceId, isLocalOrURLPath, + diagnoseAndFixProject, } from "../extensions/extensionsHelper"; import { update } from "../extensions/updateHelper"; import { getRandomString } from "../extensions/utils"; @@ -274,6 +275,7 @@ export default new Command("ext:install [extensionName]") .before(requirePermissions, ["firebaseextensions.instances.create"]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") + .before(diagnoseAndFixProject) .action(async (extensionName: string, options: any) => { const projectId = needProjectId(options); const paramsEnvPath = options.params; diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 1eea467d440..3c3bc5621fe 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -15,6 +15,7 @@ import { ensureExtensionsApiEnabled, logPrefix, resourceTypeToNiceName, + diagnoseAndFixProject, } from "../extensions/extensionsHelper"; import { promptOnce } from "../prompt"; import { requirePermissions } from "../requirePermissions"; @@ -48,6 +49,7 @@ export default new Command("ext:uninstall ") .before(requirePermissions, ["firebaseextensions.instances.delete"]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") + .before(diagnoseAndFixProject) .action(async (instanceId: string, options: any) => { const projectId = needProjectId(options); let instance; diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index e4af5c6c956..8305ab2cc4c 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -20,6 +20,7 @@ import { getSourceOrigin, SourceOrigin, confirm, + diagnoseAndFixProject, } from "../extensions/extensionsHelper"; import * as paramHelper from "../extensions/paramHelper"; import { @@ -69,6 +70,7 @@ export default new Command("ext:update [updateSource]") ]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") + .before(diagnoseAndFixProject) .withForce() .option("--params ", "name of params variables file with .env format.") .action(async (instanceId: string, updateSource: string, options: any) => { diff --git a/src/extensions/diagnose.ts b/src/extensions/diagnose.ts new file mode 100644 index 00000000000..52c6c35457f --- /dev/null +++ b/src/extensions/diagnose.ts @@ -0,0 +1,74 @@ +import { logPrefix } from "./extensionsHelper"; +import { getProjectNumber } from "../getProjectNumber"; +import * as utils from "../utils"; +import * as resourceManager from "../gcp/resourceManager"; +import { listInstances } from "./extensionsApi"; +import { promptOnce } from "../prompt"; +import { logger } from "../logger"; +import { FirebaseError } from "../error"; + +const SERVICE_AGENT_ROLE = "roles/firebasemods.serviceAgent"; + +/** + * Diagnoses and optionally fixes known issues with project configuration, ex. missing Extensions Service Agent permissions. + * @param projectId ID of the project we're querying + */ +export async function diagnose(projectId: string): Promise { + const projectNumber = await getProjectNumber({ projectId }); + const firexSaProjectId = utils.envOverride( + "FIREBASE_EXTENSIONS_SA_PROJECT_ID", + "gcp-sa-firebasemods" + ); + + const saEmail = `service-${projectNumber}@${firexSaProjectId}.iam.gserviceaccount.com`; + + utils.logLabeledBullet(logPrefix, "Checking project IAM policy..."); + + // Call ListExtensionInstances to make sure Extensions Service Agent is provisioned. + await listInstances(projectId); + + let policy; + try { + policy = await resourceManager.getIamPolicy(projectId); + logger.debug(policy); + } catch (e) { + if (e instanceof FirebaseError && e.status === 403) { + throw new FirebaseError( + "Unable to get project IAM policy, permission denied (403). Please " + + "make sure you have sufficient project privileges or if this is a brand new project " + + "try again in a few minutes." + ); + } + throw e; + } + + if ( + policy.bindings.find( + (b) => b.role === SERVICE_AGENT_ROLE && b.members.includes("serviceAccount:" + saEmail) + ) + ) { + utils.logLabeledSuccess(logPrefix, "Project IAM policy OK"); + return true; + } else { + utils.logWarning( + "Firebase Extensions Service Agent is missing a required IAM role " + + "`Firebase Extensions API Service Agent`." + ); + const fix = await promptOnce({ + type: "confirm", + message: + "Would you like to fix the issue by updating IAM policy to include Firebase " + + "Extensions Service Agent with role `Firebase Extensions API Service Agent`", + }); + if (fix) { + policy.bindings.push({ + role: SERVICE_AGENT_ROLE, + members: ["serviceAccount:" + saEmail], + }); + await resourceManager.setIamPolicy(projectId, policy, "bindings"); + utils.logSuccess("Project IAM policy updated successfully"); + return true; + } + return false; + } +} diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 7e82a14c5b8..b68e4119fe0 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -16,6 +16,7 @@ import { convertOfficialExtensionsToList } from "./utils"; import { getFirebaseConfig } from "../functionsConfig"; import { getExtensionRegistry } from "./resolveSource"; import { FirebaseError } from "../error"; +import { diagnose } from "./diagnose"; import { checkResponse } from "./askUserForParam"; import { ensure } from "../ensureApiEnabled"; import { deleteObject, uploadObject } from "../gcp/storage"; @@ -737,3 +738,11 @@ export async function confirm(args: { return true; } } + +export async function diagnoseAndFixProject(options: any): Promise { + const projectId = needProjectId(options); + const ok = await diagnose(projectId); + if (!ok) { + throw new FirebaseError("Unable to proceed until all issues are resolved."); + } +} diff --git a/src/test/extensions/diagnose.spec.ts b/src/test/extensions/diagnose.spec.ts new file mode 100644 index 00000000000..8d8889525ec --- /dev/null +++ b/src/test/extensions/diagnose.spec.ts @@ -0,0 +1,96 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import * as resourceManager from "../../gcp/resourceManager"; +import * as pn from "../../getProjectNumber"; +import * as diagnose from "../../extensions/diagnose"; +import * as extensionsApi from "../../extensions/extensionsApi"; +import * as prompt from "../../prompt"; + +const GOOD_BINDING = { + role: "roles/firebasemods.serviceAgent", + members: ["serviceAccount:service-123456@gcp-sa-firebasemods.iam.gserviceaccount.com"], +}; + +describe("diagnose", () => { + let getIamStub: sinon.SinonStub; + let setIamStub: sinon.SinonStub; + let getProjectNumberStub: sinon.SinonStub; + let promptOnceStub: sinon.SinonStub; + let listInstancesStub: sinon.SinonStub; + + beforeEach(() => { + getIamStub = sinon + .stub(resourceManager, "getIamPolicy") + .throws("unexpected call to resourceManager.getIamStub"); + setIamStub = sinon + .stub(resourceManager, "setIamPolicy") + .throws("unexpected call to resourceManager.setIamPolicy"); + getProjectNumberStub = sinon + .stub(pn, "getProjectNumber") + .throws("unexpected call to pn.getProjectNumber"); + promptOnceStub = sinon + .stub(prompt, "promptOnce") + .throws("unexpected call to prompt.promptOnce"); + listInstancesStub = sinon + .stub(extensionsApi, "listInstances") + .throws("unexpected call to extensionsApi.listInstances"); + + getProjectNumberStub.resolves(123456); + listInstancesStub.resolves([]); + }); + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("should succeed when IAM policy is correct (no fix)", async () => { + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [GOOD_BINDING], + }); + promptOnceStub.resolves(false); + + expect(await diagnose.diagnose("project_id")).to.be.true; + + expect(getIamStub).to.have.been.calledWith("project_id"); + expect(setIamStub).to.not.have.been.called; + }); + + it("should fail when project IAM policy missing extensions service agent (no fix)", async () => { + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [], + }); + promptOnceStub.resolves(false); + + expect(await diagnose.diagnose("project_id")).to.be.false; + + expect(getIamStub).to.have.been.calledWith("project_id"); + expect(setIamStub).to.not.have.been.called; + }); + + it("should fix the project IAM policy by adding missing bindings", async () => { + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [], + }); + setIamStub.resolves(); + promptOnceStub.resolves(true); + + expect(await diagnose.diagnose("project_id")).to.be.true; + + expect(getIamStub).to.have.been.calledWith("project_id"); + expect(setIamStub).to.have.been.calledWith( + "project_id", + { + etag: "etag", + version: 3, + bindings: [GOOD_BINDING], + }, + "bindings" + ); + }); +}); From f0dda1fbdb70d9700d105cf1b63fd62bfaf7317d Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 9 Feb 2022 18:41:53 -0600 Subject: [PATCH 0071/1699] upgrade firepit builder npm to 8; omit dev dependencies (#4151) --- scripts/firepit-builder/Dockerfile | 3 +++ scripts/firepit-builder/pipeline.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/firepit-builder/Dockerfile b/scripts/firepit-builder/Dockerfile index f04522d09e1..a6537ded3d6 100644 --- a/scripts/firepit-builder/Dockerfile +++ b/scripts/firepit-builder/Dockerfile @@ -8,6 +8,9 @@ RUN apt-get update && \ RUN curl -fsSL --output hub.tgz https://github.com/github/hub/releases/download/v2.11.2/hub-linux-amd64-2.11.2.tgz RUN tar --strip-components=2 -C /usr/bin -xf hub.tgz hub-linux-amd64-2.11.2/bin/hub +# Upgrade npm to 8. +RUN npm install --global npm@8 + # Create app directory WORKDIR /usr/src/app diff --git a/scripts/firepit-builder/pipeline.js b/scripts/firepit-builder/pipeline.js index b29e8e4ae23..19983aa3b8d 100755 --- a/scripts/firepit-builder/pipeline.js +++ b/scripts/firepit-builder/pipeline.js @@ -42,7 +42,7 @@ if (fs.existsSync(firebaseToolsPackage)) { npm("install", packedModule); rm(packedModule); } else { - npm("install", firebaseToolsPackage); + npm("install", "--omit=dev", firebaseToolsPackage); } const packageJson = JSON.parse(cat("node_modules/firebase-tools/package.json")); From b03a51b6c9c8b398064177a7506eb93ce7ba0111 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Wed, 9 Feb 2022 16:56:10 -0800 Subject: [PATCH 0072/1699] Improves login experience when no localhost is available. (#4147) * Proof-of-concept for signing in remotely via auth proxy server. * Fix lint error * Update output of --remote * Replace --no-localhost with remote login. * Remove vestiges of --remote * CHANGELOG, fixes emu test. * Fix track import. * Wrap auth code error in better message. Co-authored-by: Bryan Kendall --- CHANGELOG.md | 1 + src/api.js | 1 + src/auth.ts | 111 +++++++++++++++++++++++++++++++----------- src/commands/login.ts | 5 +- 4 files changed, 85 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..0b7c242fc16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Improves experience for `firebase login --no-localhost`. diff --git a/src/api.js b/src/api.js index 0419a19391c..0544644c388 100644 --- a/src/api.js +++ b/src/api.js @@ -84,6 +84,7 @@ var _appendQueryData = function (path, data) { }; var api = { + authProxyOrigin: utils.envOverride("FIREBASE_AUTHPROXY_URL", "https://auth.firebase.tools"), // "In this context, the client secret is obviously not treated as a secret" // https://developers.google.com/identity/protocols/OAuth2InstalledApp clientId: utils.envOverride( diff --git a/src/auth.ts b/src/auth.ts index 036ce6f3c72..b2fc80e19af 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -17,6 +17,10 @@ import { logger } from "./logger"; import { promptOnce } from "./prompt"; import * as scopes from "./scopes"; import { clearCredentials } from "./defaultCredentials"; +import { v4 as uuidv4 } from "uuid"; +import { randomBytes, createHash } from "crypto"; +import { bold } from "cli-color"; +import { track } from "./track"; /* eslint-disable camelcase */ // The wire protocol for an access token returned by Google. @@ -324,22 +328,32 @@ function getLoginUrl(callbackUrl: string, userHint?: string) { ); } -async function getTokensFromAuthorizationCode(code: string, callbackUrl: string) { +async function getTokensFromAuthorizationCode( + code: string, + callbackUrl: string, + verifier?: string +) { let res: { body?: TokensWithTTL; statusCode: number; }; + const params: Record = { + code: code, + client_id: api.clientId, + client_secret: api.clientSecret, + redirect_uri: callbackUrl, + grant_type: "authorization_code", + }; + + if (verifier) { + params["code_verifier"] = verifier; + } + try { res = await api.request("POST", "/o/oauth2/token", { origin: api.authOrigin, - form: { - code: code, - client_id: api.clientId, - client_secret: api.clientSecret, - redirect_uri: callbackUrl, - grant_type: "authorization_code", - }, + form: params, }); } catch (err: any) { if (err instanceof Error) { @@ -406,30 +420,65 @@ async function respondWithFile( req.socket.destroy(); } -async function loginWithoutLocalhost(userHint?: string): Promise { - const callbackUrl = getCallbackUrl(); - const authUrl = getLoginUrl(callbackUrl, userHint); +function urlsafeBase64(base64string: string) { + return base64string.replace(/\+/g, "-").replace(/=+$/, "").replace(/\//g, "_"); +} + +async function loginRemotely(userHint?: string): Promise { + const authProxyClient = new apiv2.Client({ + urlPrefix: api.authProxyOrigin, + auth: false, + }); + + const sessionId = uuidv4(); + const codeVerifier = randomBytes(32).toString("hex"); + // urlsafe base64 is required for code_challenge in OAuth PKCE + const codeChallenge = urlsafeBase64(createHash("sha256").update(codeVerifier).digest("base64")); + + const attestToken = ( + await authProxyClient.post<{ session_id: string }, { token: string }>("/attest", { + session_id: sessionId, + }) + ).body?.token; + + const loginUrl = `${api.authProxyOrigin}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`; logger.info(); - logger.info("Visit this URL on any device to log in:"); - logger.info(clc.bold.underline(authUrl)); + logger.info("To sign in to the Firebase CLI:"); + logger.info(); + logger.info("1. Take note of your session ID:"); + logger.info(); + logger.info(` ${bold(sessionId.substring(0, 5).toUpperCase())}`); + logger.info(); + logger.info("2. Visit the URL below on any device and follow the instructions to get your code:"); + logger.info(); + logger.info(` ${loginUrl}`); + logger.info(); + logger.info("3. Paste or enter the authorization code below once you have it:"); logger.info(); - open(authUrl); - - const code: string = await promptOnce({ + const code = await promptOnce({ type: "input", - name: "code", - message: "Paste authorization code here:", + message: "Enter authorization code:", }); - const tokens = await getTokensFromAuthorizationCode(code, callbackUrl); - // getTokensFromAuthorizationCode doesn't handle the --token case, so we know - // that we'll have a valid id_token. - return { - user: jwt.decode(tokens.id_token!) as User, - tokens: tokens, - scopes: SCOPES, - }; + + try { + const tokens = await getTokensFromAuthorizationCode( + code, + `${api.authProxyOrigin}/complete`, + codeVerifier + ); + + track("login", "google_remote"); + + return { + user: jwt.decode(tokens.id_token!) as User, + tokens: tokens, + scopes: SCOPES, + }; + } catch (e) { + throw new FirebaseError("Unable to authenticate using the provided code. Please try again."); + } } async function loginWithLocalhostGoogle(port: number, userHint?: string): Promise { @@ -443,6 +492,8 @@ async function loginWithLocalhostGoogle(port: number, userHint?: string): Promis successTemplate, getTokensFromAuthorizationCode ); + + track("login", "google_localhost"); // getTokensFromAuthoirzationCode doesn't handle the --token case, so we know we'll // always have an id_token. return { @@ -456,13 +507,15 @@ async function loginWithLocalhostGitHub(port: number): Promise { const callbackUrl = getCallbackUrl(port); const authUrl = getGithubLoginUrl(callbackUrl); const successTemplate = "../templates/loginSuccessGithub.html"; - return loginWithLocalhost( + const tokens = await loginWithLocalhost( port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode ); + track("login", "google_localhost"); + return tokens; } async function loginWithLocalhost( @@ -521,10 +574,10 @@ export async function loginGoogle(localhost: boolean, userHint?: string): Promis const port = await getPort(); return await loginWithLocalhostGoogle(port, userHint); } catch { - return await loginWithoutLocalhost(userHint); + return await loginRemotely(userHint); } } - return await loginWithoutLocalhost(userHint); + return await loginRemotely(userHint); } export async function loginGithub(): Promise { diff --git a/src/commands/login.ts b/src/commands/login.ts index 81b84bdf8e7..43e2ecda5fd 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -13,10 +13,7 @@ import { isCloudEnvironment } from "../utils"; module.exports = new Command("login") .description("log the CLI into Firebase") - .option( - "--no-localhost", - "copy and paste a code instead of starting a local server for authentication" - ) + .option("--no-localhost", "login from a device without an accessible localhost") .option("--reauth", "force reauthentication even if already logged in") .action(async (options: any) => { if (options.nonInteractive) { From 0c747c9e9c50a5185578cfdd1739d891d55e06e3 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 10 Feb 2022 13:01:23 -0800 Subject: [PATCH 0073/1699] Release CF3's support for environment variables and secrets (#4149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use runtime delegate to parse function triggers in the Functions Emulator (#4012) Today, Functions Emulator parses function trigger from source as follows: 1) Spin up an instance of Functions runtime process 2) Invoke a "function" which triggers a path that parses the trigger by calling out to `extractTriggers.js` 3) Send parsed triggers via IPC from runtime to emulator. Emulator now knows about the triggers. This has the advantage of running the trigger parsing in the emulated runtime (which properly mocks out calls to the DB, applies network filtering, uses the same node version when possible, etc.) but has the disadvantage of complicating the runtime implementation as well as diverging from how the triggers are parsed in `firebase deploy`. Using runtime delegate, we have: 1) Use runtime delegate to discover the delegate appropriate for function source (i.e. Node, but in the future can be some other runtime) 2) Spin up a node subprocess to parse trigger. Emulator now knows about the triggers. i.e. the same procedure used during `firebase deploy` By using runtime delegate, we align the function deploy to production and to the emulated environment and simplify the runtime code a bit. This also puts us into a good position in the future when we make the function deploy process a little more complex, e.g. params and secrets support. * Slim down Functions Emulator Runtime (i.e. args sent over to emulated functions) (#4105) Now that we've simplified the Functions Emulator to separate out process for loading triggers from the one running the trigger, we can slim down [`FunctionsRuntimeBundle`](https://github.com/firebase/firebase-tools/blob/2e68803f994dbe4f72eb0965dd6a12e7a043b597/src/emulator/functionsEmulatorShared.ts#L53-L88) that is passed between the Functions Emulator and the Functions Runtime process. This change removes almost all payload attributes in the Functions Runtime Bundle except `proto`. This is nice - we are getting very close to the payload that's passed to a production function instance. We have to leave couple of things like socketpath and debug features - this will probably be removed when we move over to pure-http based protocol (socket) and SDK based debug feature enablement. This could be worked later when I have a little more time! One more change - we pass around the whole trigger definition in the Functions Emulator instead of pieces of it. This makes it easier to do something else I'm doing... (secret emulator) in the subsequent PR. * CF3 Secrets Support (#3959) Support deploying secret environment variables on a function. Prior to deploying functions with secret configuration, the CLI will run somewhat comprehensive validation to ensure that secret config will work when deployed, e.g. 1. Secret version exists. 2. Secret version is in ENABLED state. 3. Secret version can be access by the runtime service account. We do this since the GCF doesn't do the same level of validation and instead repeatedly fail to spin up a new instance with an invalid secret config. This often results on super long deploys (probably until some master timeout is met for function instance deploy). I took the opportunity to refactor the code a little to group various "ensure" and "validate" used in function deploys in their own files. Emulator support for secrets will come in a separate PR. * Add new command (functions:secrets:set) for creating secrets to be used for CF3. (#4021) One of several family of commands to be implemented for managing secrets for CF3. `functions:secrets:set` command is used to create a new secret version in Secret Manager. If a secret doesn't exist, a secret will be created before adding a new version. To guide users to our recommended best practices, we will only allow users to create secrets in `UPPER_SNAKE_CASE` - this makes it more obvious how these secrets can be accessed at runtime (via environment variable of the same name). Usage: ``` $ echo SHHHH > SECRET_FILE $ firebase functions:secrets:set MY_SECRET --data-file=SECRET_FILE ✔ Created a new secret version projects/my-project/secrets/MY_SECRET/versions/0 i Please deploy your functions for the change to take effect by running: firebase deploy --only functions // Calling set on existing secret name will create a new version. $ echo SHHHHHHHH > SECRET_FILEE $ firebase functions:secrets:set MY_SECRET --data-file=SECRET_FILE ✔ Created a new secret version projects/my-project/secrets/MY_SECRET/versions/1 i Please deploy your functions for the change to take effect by running firebase deploy --only functions // "-" as STDIN is supported but discouraged since it will leave the secret in shell history $ echo SHHHHHHHHHH | firebase functions:secrets:set --data-file=- MY_SECRET ✔ Created a new secret version projects/my-project/secrets/MY_SECRET/versions/2 i Please deploy your functions for the change to take effect by running firebase deploy --only functions // Without --data-file flag, begin interactive prompt to take user input $ firebase functions:secrets:set MY_SECRET ? Enter a value for MY_SECRET [input is hidden]: ✔ Created a new secret version projects/my-project/secrets/MY_SECRET/versions/3 i Please deploy your functions for the change to take effect by running: firebase deploy --only functions ``` * Add functions:secrets:{access, destroy, get} commands. (#4026) Follow up https://github.com/firebase/firebase-tools/pull/4021 to add other management commands for CF3 secrets. Note that `destroy` commands can be improved by making sure we don't accidentally delete secrets versions currently in use (which would immediately break the function!). I'll add these feature in a follow up PR when we finish reviewing the PR w/ `prune` command. * Add command to prune unused secrets (#4108) Each active secret version cost money. To help save cost on using Secret Manager, we add `functions:secrets:prune` command which: 1) Looks up all secret versions from secrets marked with label "firebase-managed". All secrets created using the Firebase CLI will have this label. 2) Look up all secret bindings for CF3 function instance. 3) Figure out which secret version isn't currently being used. Since destroying a secret version is irrevocable and immediately breaking for clients that depend on it, we will always ask for a confirmation for the destroy operations (and not support -f flag). Note that we now query `v1` of Secret Manager since `v1beta` does not offer filtering by labels. * Add support for secrets in the Functions Emulator (#4106) Emulator will now recognize function triggers with secret environments and ensure that secret environment variables are populated in the emulated runtime. Secrets in Functions Emulator can come from 2 sources: 1) From local override file (`.secret.local`). 2) From Google Cloud Secret Manager. In this case, default application credentials (i.e. credentials used in Firebase CLI) will be used to fetch the secret from GCP. As suspected, (1) take precedence over (2). If accessing secret from GCP fails for any reason, the Emulator logs, but does not throw, the failed attempt and proceeds to execute the functions code. Some refactoring changes needed to be in the Emulator: * Some functions turned into async. * We pass around the whole trigger in more places. * Remove preview flag, add option to disable dotenv support. (#4022) Preparing for launching dotenv support for CF3. At launch, CF3 environment variables support will default to picking up dotenv file if any, without need for preview flag. * Add EXT_ as a reserved environment variable prefix (#4148) Firebase Extensions use environment variables with `EXT_` for many of their "first class" environment variable keys. We will add it to the reserved prefix list before releasing the dotenv support for all users. --- CHANGELOG.md | 1 + scripts/emulator-tests/fixtures.ts | 71 +--- .../emulator-tests/functionsEmulator.spec.ts | 95 ++++- .../functionsEmulatorRuntime.spec.ts | 90 +++-- src/commands/functions-secrets-access.ts | 19 + src/commands/functions-secrets-destroy.ts | 51 +++ src/commands/functions-secrets-get.ts | 23 ++ src/commands/functions-secrets-prune.ts | 68 ++++ src/commands/functions-secrets-set.ts | 56 +++ src/commands/index.js | 10 +- src/deploy/functions/backend.ts | 21 ++ src/deploy/functions/ensure.ts | 162 +++++++++ .../functions/ensureCloudBuildEnabled.ts | 61 ---- src/deploy/functions/prepare.ts | 46 +-- .../functions/runtimes/discovery/v1alpha1.ts | 1 + src/deploy/functions/runtimes/index.ts | 3 +- .../functions/runtimes/node/parseTriggers.ts | 15 + src/deploy/functions/validate.ts | 88 +++++ src/emulator/controller.ts | 7 +- src/emulator/emulatorLogger.ts | 10 +- src/emulator/functionsEmulator.ts | 304 ++++++++-------- src/emulator/functionsEmulatorRuntime.ts | 249 +++++++------ src/emulator/functionsEmulatorShared.ts | 117 ++++-- src/emulator/functionsEmulatorShell.ts | 14 +- src/emulator/functionsRuntimeWorker.ts | 2 +- src/extensions/askUserForParam.ts | 2 +- src/extensions/secretsUtils.ts | 2 +- src/functions/env.ts | 12 +- src/functions/secrets.ts | 170 +++++++++ src/gcp/cloudfunctions.ts | 6 +- src/gcp/secretManager.ts | 335 ++++++++++++++---- src/previews.ts | 2 - src/serve/functions.ts | 4 +- src/test/deploy/functions/backend.spec.ts | 7 + src/test/deploy/functions/ensure.spec.ts | 267 ++++++++++++++ .../functions/ensureCloudBuildEnabled.ts | 134 ------- src/test/deploy/functions/prepare.spec.ts | 28 +- .../runtimes/node/parseTriggers.spec.ts | 24 ++ .../functions/runtimes/node/validate.spec.ts | 1 - src/test/deploy/functions/validate.spec.ts | 119 +++++++ src/test/emulators/fixtures.ts | 71 +--- .../emulators/functionsRuntimeWorker.spec.ts | 8 +- src/test/extensions/secretUtils.spec.ts | 4 +- src/test/functions/env.spec.ts | 18 +- src/test/functions/secrets.spec.ts | 287 +++++++++++++++ src/test/gcp/secretManager.spec.ts | 131 +++++++ src/utils.ts | 13 + 47 files changed, 2387 insertions(+), 842 deletions(-) create mode 100644 src/commands/functions-secrets-access.ts create mode 100644 src/commands/functions-secrets-destroy.ts create mode 100644 src/commands/functions-secrets-get.ts create mode 100644 src/commands/functions-secrets-prune.ts create mode 100644 src/commands/functions-secrets-set.ts create mode 100644 src/deploy/functions/ensure.ts create mode 100644 src/functions/secrets.ts create mode 100644 src/test/deploy/functions/ensure.spec.ts delete mode 100644 src/test/deploy/functions/ensureCloudBuildEnabled.ts create mode 100644 src/test/functions/secrets.spec.ts create mode 100644 src/test/gcp/secretManager.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b7c242fc16..0a336b2eae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Improves experience for `firebase login --no-localhost`. +- Add support for specifying environment variable of CF3 function using dotenv. diff --git a/scripts/emulator-tests/fixtures.ts b/scripts/emulator-tests/fixtures.ts index 2b991af92c5..8beb968d263 100644 --- a/scripts/emulator-tests/fixtures.ts +++ b/scripts/emulator-tests/fixtures.ts @@ -6,17 +6,6 @@ export const TIMEOUT_MED = 5000; export const MODULE_ROOT = findModuleRoot("firebase-tools", __dirname); export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = { onCreate: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, proto: { data: { value: { @@ -41,22 +30,8 @@ export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = }, }, }, - triggerId: "us-central1-function_id", - targetName: "function_id", - projectId: "fake-project-id", }, onWrite: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, proto: { data: { value: { @@ -81,22 +56,8 @@ export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = }, }, }, - triggerId: "us-central1-function_id", - targetName: "function_id", - projectId: "fake-project-id", }, onDelete: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, proto: { data: { oldValue: { @@ -121,22 +82,8 @@ export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = }, }, }, - triggerId: "us-central1-function_id", - targetName: "function_id", - projectId: "fake-project-id", }, onUpdate: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, proto: { data: { oldValue: { @@ -173,25 +120,9 @@ export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = timestamp: "2019-05-15T16:21:15.148831Z", }, }, - triggerId: "us-central1-function_id", - targetName: "function_id", - projectId: "fake-project-id", }, onRequest: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, - triggerId: "us-central1-function_id", - targetName: "function_id", - projectId: "fake-project-id", + proto: {}, }, }; diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index b565634cfcf..b1495e6a74c 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -1,9 +1,13 @@ +import * as fs from "fs"; + import { expect } from "chai"; import * as express from "express"; import * as sinon from "sinon"; import * as supertest from "supertest"; +import * as winston from "winston"; +import * as logform from "logform"; -import { SignatureType } from "../../src/emulator/functionsEmulatorShared"; +import { EmulatedTriggerDefinition } from "../../src/emulator/functionsEmulatorShared"; import { EmulatableBackend, FunctionsEmulator, @@ -14,8 +18,7 @@ import { RuntimeWorker } from "../../src/emulator/functionsRuntimeWorker"; import { TIMEOUT_LONG, TIMEOUT_MED, MODULE_ROOT } from "./fixtures"; import { logger } from "../../src/logger"; import * as registry from "../../src/emulator/registry"; -import * as winston from "winston"; -import * as logform from "logform"; +import * as secretManager from "../../src/gcp/secretManager"; if ((process.env.DEBUG || "").toLowerCase().includes("spec")) { const dropLogLevels = (info: logform.TransformableInfo) => info.message; @@ -32,6 +35,7 @@ if ((process.env.DEBUG || "").toLowerCase().includes("spec")) { const functionsEmulator = new FunctionsEmulator({ projectId: "fake-project-id", + projectDir: MODULE_ROOT, emulatableBackends: [ { functionsDir: MODULE_ROOT, @@ -96,6 +100,23 @@ functionsEmulator.setTriggersForTesting( httpsTrigger: {}, labels: {}, }, + { + platform: "gcfv1", + name: "secrets_function_id", + id: "us-central1-secrets_function_id", + region: "us-central1", + entryPoint: "secrets_function_id", + secretEnvironmentVariables: [ + { + projectId: "fake-project-id", + secret: "MY_SECRET", + key: "MY_SECRET", + version: "1", + }, + ], + httpsTrigger: {}, + labels: {}, + }, ], testBackend ); @@ -108,13 +129,11 @@ function useFunctions(triggers: () => {}): void { // eslint-disable-next-line @typescript-eslint/unbound-method functionsEmulator.startFunctionRuntime = ( backend: EmulatableBackend, - triggerId: string, - targetName: string, - triggerType: SignatureType, + trigger: EmulatedTriggerDefinition, proto?: any, runtimeOpts?: InvokeRuntimeOpts - ): RuntimeWorker => { - return startFunctionRuntime(testBackend, triggerId, targetName, triggerType, proto, { + ): Promise => { + return startFunctionRuntime(testBackend, trigger, proto, { nodeBinary: process.execPath, serializedTriggers, }); @@ -700,4 +719,64 @@ describe("FunctionsEmulator-Hub", () => { }); }).timeout(TIMEOUT_MED); }); + + describe("secrets", () => { + let readFileSyncStub: sinon.SinonStub; + let accessSecretVersionStub: sinon.SinonStub; + + beforeEach(() => { + readFileSyncStub = sinon.stub(fs, "readFileSync").throws("Unexpected call"); + accessSecretVersionStub = sinon + .stub(secretManager, "accessSecretVersion") + .rejects("Unexpected call"); + }); + + afterEach(() => { + readFileSyncStub.restore(); + accessSecretVersionStub.restore(); + }); + + it("should load secret values from local secrets file if one exists", async () => { + readFileSyncStub.returns("MY_SECRET=local"); + + useFunctions(() => { + return { + secrets_function_id: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ secret: process.env.MY_SECRET }); + } + ), + }; + }); + + await supertest(functionsEmulator.createHubServer()) + .get("/fake-project-id/us-central1/secrets_function_id") + .expect(200) + .then((res) => { + expect(res.body.secret).to.equal("local"); + }); + }).timeout(TIMEOUT_LONG); + + it("should try to access secret values from Secret Manager", async () => { + readFileSyncStub.throws({ code: "ENOENT" }); + accessSecretVersionStub.resolves("secretManager"); + + useFunctions(() => { + return { + secrets_function_id: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ secret: process.env.MY_SECRET }); + } + ), + }; + }); + + await supertest(functionsEmulator.createHubServer()) + .get("/fake-project-id/us-central1/secrets_function_id") + .expect(200) + .then((res) => { + expect(res.body.secret).to.equal("secretManager"); + }); + }).timeout(TIMEOUT_LONG); + }); }); diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index e93fbe0ccbf..ddeaf6ba1e7 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -26,10 +26,15 @@ const testBackend = { }; const functionsEmulator = new FunctionsEmulator({ + projectDir: MODULE_ROOT, projectId: "fake-project-id", emulatableBackends: [testBackend], + adminSdkConfig: { + projectId: "fake-project-id", + databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", + storageBucket: "fake-project-id.appspot.com", + }, }); -(functionsEmulator as any).adminSdkConfig = FunctionRuntimeBundles.onRequest.adminSdkConfig; async function countLogEntries(worker: RuntimeWorker): Promise<{ [key: string]: number }> { const runtime = worker.runtime; @@ -43,23 +48,34 @@ async function countLogEntries(worker: RuntimeWorker): Promise<{ [key: string]: return counts; } -function startRuntimeWithFunctions( +async function startRuntimeWithFunctions( frb: FunctionsRuntimeBundle, triggers: () => {}, signatureType: SignatureType, opts?: InvokeRuntimeOpts -): RuntimeWorker { +): Promise { const serializedTriggers = triggers.toString(); opts = opts || { nodeBinary: process.execPath }; opts.ignore_warnings = true; opts.serializedTriggers = serializedTriggers; + const dummyTriggerDef = { + name: "function_id", + region: "region", + id: "region-function_id", + entryPoint: "function_id", + platform: "gcfv1" as const, + }; return functionsEmulator.startFunctionRuntime( testBackend, - frb.triggerId!, - frb.targetName!, - signatureType, + { + ...dummyTriggerDef, + // Fill in with dummy trigger info based on given signature type. + ...(signatureType === "http" + ? { httpsTrigger: {} } + : { eventTrigger: { eventType: "", resource: "" } }), + }, frb.proto, opts ); @@ -113,7 +129,7 @@ describe("FunctionsEmulator-Runtime", () => { describe("Stubs, Mocks, and Helpers (aka Magic, Glee, and Awesomeness)", () => { describe("_InitializeNetworkFiltering(...)", () => { it("should log outgoing unknown HTTP requests via 'http'", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -136,7 +152,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_LONG); it("should log outgoing unknown HTTP requests via 'https'", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -159,7 +175,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_LONG); it("should log outgoing Google API requests", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -194,7 +210,7 @@ describe("FunctionsEmulator-Runtime", () => { }); it("should provide stubbed default app from initializeApp", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -212,7 +228,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide a stubbed app with custom options", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp({ @@ -242,7 +258,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide non-stubbed non-default app from initializeApp", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); // We still need to initialize default for snapshots @@ -260,7 +276,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should route all sub-fields accordingly", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -292,7 +308,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should expose Firestore prod when the emulator is not running", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { const admin = require("firebase-admin"); @@ -324,7 +340,7 @@ describe("FunctionsEmulator-Runtime", () => { port: 9090, }); - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { const admin = require("firebase-admin"); @@ -351,7 +367,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should expose RTDB prod when the emulator is not running", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { const admin = require("firebase-admin"); @@ -381,7 +397,7 @@ describe("FunctionsEmulator-Runtime", () => { port: 9090, }); - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { const admin = require("firebase-admin"); @@ -411,7 +427,7 @@ describe("FunctionsEmulator-Runtime", () => { port: 9090, }); - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { const admin = require("firebase-admin"); @@ -433,7 +449,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should return a real databaseURL when RTDB emulator is not running", async () => { const frb = _.cloneDeep(FunctionRuntimeBundles.onRequest); - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { const admin = require("firebase-admin"); @@ -450,7 +466,7 @@ describe("FunctionsEmulator-Runtime", () => { const data = await callHTTPSFunction(worker, frb); const info = JSON.parse(data); - expect(info.databaseURL).to.eql(frb.adminSdkConfig.databaseURL!); + expect(info.databaseURL).to.eql("https://fake-project-id-default-rtdb.firebaseio.com"); }).timeout(TIMEOUT_MED); }); }); @@ -468,7 +484,7 @@ describe("FunctionsEmulator-Runtime", () => { }); it("should tell the user if they've accessed a non-existent function field", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -497,7 +513,7 @@ describe("FunctionsEmulator-Runtime", () => { describe("HTTPS", () => { it("should handle a GET request", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -517,7 +533,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request with form data", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -548,7 +564,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request with JSON data", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -579,7 +595,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request with text data", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -610,7 +626,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request with any other type", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -642,7 +658,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request and store rawBody", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -673,7 +689,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should forward request to Express app", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -701,7 +717,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle `x-forwarded-host`", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -725,7 +741,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should report GMT time zone", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { return { @@ -745,7 +761,7 @@ describe("FunctionsEmulator-Runtime", () => { describe("Cloud Firestore", () => { it("should provide Change for firestore.onWrite()", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onWrite, () => { require("firebase-admin").initializeApp(); @@ -779,7 +795,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide Change for firestore.onUpdate()", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onUpdate, () => { require("firebase-admin").initializeApp(); @@ -812,7 +828,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide DocumentSnapshot for firestore.onDelete()", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onDelete, () => { require("firebase-admin").initializeApp(); @@ -844,7 +860,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide DocumentSnapshot for firestore.onCreate()", async () => { - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( FunctionRuntimeBundles.onWrite, () => { require("firebase-admin").initializeApp(); @@ -879,7 +895,7 @@ describe("FunctionsEmulator-Runtime", () => { describe("Error handling", () => { it("Should handle regular functions for Express handlers", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -905,7 +921,7 @@ describe("FunctionsEmulator-Runtime", () => { it("Should handle async functions for Express handlers", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); @@ -934,7 +950,7 @@ describe("FunctionsEmulator-Runtime", () => { it("Should handle async/runWith functions for Express handlers", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = startRuntimeWithFunctions( + const worker = await startRuntimeWithFunctions( frb, () => { require("firebase-admin").initializeApp(); diff --git a/src/commands/functions-secrets-access.ts b/src/commands/functions-secrets-access.ts new file mode 100644 index 00000000000..87a709b4b63 --- /dev/null +++ b/src/commands/functions-secrets-access.ts @@ -0,0 +1,19 @@ +import { Command } from "../command"; +import { logger } from "../logger"; +import { Options } from "../options"; +import { needProjectId } from "../projectUtils"; +import { accessSecretVersion } from "../gcp/secretManager"; + +export default new Command("functions:secrets:access [@version]") + .description( + "Access secret value given secret and its version. Defaults to accessing the latest version." + ) + .action(async (key: string, options: Options) => { + const projectId = needProjectId(options); + let [name, version] = key.split("@"); + if (!version) { + version = "latest"; + } + const value = await accessSecretVersion(projectId, name, version); + logger.info(value); + }); diff --git a/src/commands/functions-secrets-destroy.ts b/src/commands/functions-secrets-destroy.ts new file mode 100644 index 00000000000..eb5a4dac289 --- /dev/null +++ b/src/commands/functions-secrets-destroy.ts @@ -0,0 +1,51 @@ +import { Command } from "../command"; +import { logger } from "../logger"; +import { Options } from "../options"; +import { needProjectId } from "../projectUtils"; +import { + deleteSecret, + destroySecretVersion, + getSecret, + getSecretVersion, + listSecretVersions, +} from "../gcp/secretManager"; +import { promptOnce } from "../prompt"; +import * as secrets from "../functions/secrets"; + +export default new Command("functions:secrets:destroy [@version]") + .description("Destroy a secret. Defaults to destroying the latest version.") + .withForce("Destroys a secret without confirmation.") + .action(async (key: string, options: Options) => { + const projectId = needProjectId(options); + let [name, version] = key.split("@"); + if (!version) { + version = "latest"; + } + const sv = await getSecretVersion(projectId, name, version); + if (!options.force) { + const confirm = await promptOnce( + { + name: "destroy", + type: "confirm", + default: true, + message: `Are you sure you want to destroy ${sv.secret.name}@${sv.versionId}`, + }, + options + ); + if (!confirm) { + return; + } + } + await destroySecretVersion(projectId, name, version); + logger.info(`Destroyed secret version ${name}@${sv.versionId}`); + + const secret = await getSecret(projectId, name); + if (secrets.isFirebaseManaged(secret)) { + const versions = await listSecretVersions(projectId, name); + if (versions.filter((v) => v.state === "ENABLED").length === 0) { + logger.info(`No active secret versions left. Destroying secret ${name}`); + // No active secret version. Remove secret resource. + await deleteSecret(projectId, name); + } + } + }); diff --git a/src/commands/functions-secrets-get.ts b/src/commands/functions-secrets-get.ts new file mode 100644 index 00000000000..fc30e5f41fb --- /dev/null +++ b/src/commands/functions-secrets-get.ts @@ -0,0 +1,23 @@ +import Table = require("cli-table"); + +import { Command } from "../command"; +import { logger } from "../logger"; +import { Options } from "../options"; +import { needProjectId } from "../projectUtils"; +import { listSecretVersions } from "../gcp/secretManager"; + +export default new Command("functions:secrets:get ") + .description("Get metadata for secret and its versions") + .action(async (key: string, options: Options) => { + const projectId = needProjectId(options); + const versions = await listSecretVersions(projectId, key); + + const table = new Table({ + head: ["Version", "State"], + style: { head: ["yellow"] }, + }); + for (const version of versions) { + table.push([version.versionId, version.state]); + } + logger.info(table.toString()); + }); diff --git a/src/commands/functions-secrets-prune.ts b/src/commands/functions-secrets-prune.ts new file mode 100644 index 00000000000..91b072a2d67 --- /dev/null +++ b/src/commands/functions-secrets-prune.ts @@ -0,0 +1,68 @@ +import * as args from "../deploy/functions/args"; +import * as backend from "../deploy/functions/backend"; +import { Command } from "../command"; +import { Options } from "../options"; +import { needProjectId, needProjectNumber } from "../projectUtils"; +import { pruneSecrets } from "../functions/secrets"; +import { requirePermissions } from "../requirePermissions"; +import { isFirebaseManaged } from "../deploymentTool"; +import { logBullet, logSuccess } from "../utils"; +import { promptOnce } from "../prompt"; +import { destroySecretVersion } from "../gcp/secretManager"; + +export default new Command("functions:secrets:prune") + .description("Destroys unused secrets") + .before(requirePermissions, [ + "cloudfunctions.functions.list", + "secretmanager.secrets.list", + "secretmanager.versions.list", + "secretmanager.versions.destroy", + ]) + .action(async (options: Options) => { + const projectNumber = await needProjectNumber(options); + const projectId = needProjectId(options); + + logBullet("Loading secrets..."); + + const haveBackend = await backend.existingBackend({ projectId } as args.Context); + const haveEndpoints = backend + .allEndpoints(haveBackend) + .filter((e) => isFirebaseManaged(e.labels || [])); + + const pruned = await pruneSecrets({ projectNumber, projectId }, haveEndpoints); + + if (pruned.length === 0) { + logBullet("All secrets are in use. Nothing to prune today."); + return; + } + + // prompt to get them all deleted + logBullet( + `Found ${pruned.length} unused active secret versions:\n\t` + + pruned.map((sv) => `${sv.secret}@${sv.version}`).join("\n\t") + ); + + const confirm = await promptOnce( + { + name: "destroy", + type: "confirm", + default: true, + message: `Do you want to destroy unused secret versions?`, + }, + options + ); + + if (!confirm) { + logBullet( + "Run the following commands to destroy each unused secret version:\n\t" + + pruned + .map((sv) => `firebase functions:secrets:destroy ${sv.secret}@${sv.version}`) + .join("\n\t") + ); + return; + } + + await Promise.all(pruned.map((sv) => destroySecretVersion(projectId, sv.secret, sv.version))); + + logSuccess("Destroyed all unused secrets!"); + }); diff --git a/src/commands/functions-secrets-set.ts b/src/commands/functions-secrets-set.ts new file mode 100644 index 00000000000..a6ec2eae397 --- /dev/null +++ b/src/commands/functions-secrets-set.ts @@ -0,0 +1,56 @@ +import * as tty from "tty"; +import * as fs from "fs"; + +import * as clc from "cli-color"; + +import { ensureValidKey, ensureSecret } from "../functions/secrets"; +import { Command } from "../command"; +import { requirePermissions } from "../requirePermissions"; +import { Options } from "../options"; +import { promptOnce } from "../prompt"; +import { logBullet, logSuccess } from "../utils"; +import { needProjectId } from "../projectUtils"; +import { addVersion, toSecretVersionResourceName } from "../gcp/secretManager"; + +export default new Command("functions:secrets:set ") + .description("Create or update a secret for use in Cloud Functions for Firebase") + .withForce( + "Does not ensure input keys are valid or upgrade existing secrets to have Firebase manage them." + ) + .before(requirePermissions, [ + "secretmanager.secrets.create", + "secretmanager.secrets.get", + "secretmanager.secrets.update", + "secretmanager.versions.add", + ]) + .option( + "--data-file ", + 'File path from which to read secret data. Set to "-" to read the secret data from stdin.' + ) + .action(async (unvalidatedKey: string, options: Options) => { + const projectId = needProjectId(options); + const key = await ensureValidKey(unvalidatedKey, options); + const secret = await ensureSecret(projectId, key, options); + let secretValue; + + if ((!options.dataFile || options.dataFile === "-") && tty.isatty(0)) { + secretValue = await promptOnce({ + name: key, + type: "password", + message: `Enter a value for ${key}`, + }); + } else { + let dataFile: string | number = 0; + if (options.dataFile && options.dataFile !== "-") { + dataFile = options.dataFile as string; + } + secretValue = fs.readFileSync(dataFile, "utf-8"); + } + + const secretVersion = await addVersion(projectId, key, secretValue); + logSuccess(`Created a new secret version ${toSecretVersionResourceName(secretVersion)}`); + logBullet( + "Please deploy your functions for the change to take effect by running:\n\t" + + clc.bold("firebase deploy --only functions") + ); + }); diff --git a/src/commands/index.js b/src/commands/index.js index 2c9feb79662..5338eac9d6b 100644 --- a/src/commands/index.js +++ b/src/commands/index.js @@ -95,9 +95,7 @@ module.exports = function (client) { client.functions = {}; client.functions.config = {}; client.functions.config.clone = loadCommand("functions-config-clone"); - if (previews.dotenv) { - client.functions.config.export = loadCommand("functions-config-export"); - } + client.functions.config.export = loadCommand("functions-config-export"); client.functions.config.get = loadCommand("functions-config-get"); client.functions.config.set = loadCommand("functions-config-set"); client.functions.config.unset = loadCommand("functions-config-unset"); @@ -108,6 +106,12 @@ module.exports = function (client) { if (previews.deletegcfartifacts) { client.functions.deletegcfartifacts = loadCommand("functions-deletegcfartifacts"); } + client.functions.secrets = {}; + client.functions.secrets.access = loadCommand("functions-secrets-access"); + client.functions.secrets.destroy = loadCommand("functions-secrets-destroy"); + client.functions.secrets.get = loadCommand("functions-secrets-get"); + client.functions.secrets.prune = loadCommand("functions-secrets-prune"); + client.functions.secrets.set = loadCommand("functions-secrets-set"); client.help = loadCommand("help"); client.hosting = {}; client.hosting.channel = {}; diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 913d56a05b7..3fe7cf5d6a1 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -166,10 +166,20 @@ export interface TargetIds { project: string; } +export interface SecretEnvVar { + key: string; + secret: string; + projectId: string; + + // Internal use only. Users cannot pin secret to a specific version. + version?: string; +} + export interface ServiceConfiguration { concurrency?: number; labels?: Record; environmentVariables?: Record; + secretEnvironmentVariables?: SecretEnvVar[]; availableMemoryMb?: MemoryOptions; timeout?: proto.Duration; maxInstances?: number; @@ -470,6 +480,17 @@ export function someEndpoint( return false; } +/** A helper utility for finding an endpoint that matches the predicate. */ +export function findEndpoint( + backend: Backend, + predicate: (endpoint: Endpoint) => boolean +): Endpoint | undefined { + for (const endpoints of Object.values(backend.endpoints)) { + const endpoint = Object.values(endpoints).find(predicate); + if (endpoint) return endpoint; + } +} + /** A helper utility function that returns a subset of the backend that includes only matching endpoints */ export function matchingBackend( backend: Backend, diff --git a/src/deploy/functions/ensure.ts b/src/deploy/functions/ensure.ts new file mode 100644 index 00000000000..49e86a3327b --- /dev/null +++ b/src/deploy/functions/ensure.ts @@ -0,0 +1,162 @@ +import * as clc from "cli-color"; + +import { ensure } from "../../ensureApiEnabled"; +import { FirebaseError, isBillingError } from "../../error"; +import { logLabeledBullet, logLabeledSuccess } from "../../utils"; +import { ensureServiceAgentRole } from "../../gcp/secretManager"; +import { previews } from "../../previews"; +import { getFirebaseProject } from "../../management/projects"; +import { assertExhaustive } from "../../functional"; +import * as track from "../../track"; +import * as backend from "./backend"; +import * as ensureApiEnabled from "../../ensureApiEnabled"; + +const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime"; +const CLOUD_BUILD_API = "cloudbuild.googleapis.com"; + +/** + * By default: + * 1. GCFv1 uses App Engine default service account. + * 2. GCFv2 (Cloud Run) uses Compute Engine default service account. + */ +export async function defaultServiceAccount(e: backend.Endpoint): Promise { + const metadata = await getFirebaseProject(e.project); + if (e.platform === "gcfv1") { + return `${metadata.projectId}@appspot.gserviceaccount.com`; + } else if (e.platform === "gcfv2") { + return `${metadata.projectNumber}-compute@developer.gserviceaccount.com`; + } + assertExhaustive(e.platform); +} + +function nodeBillingError(projectId: string): FirebaseError { + track("functions_runtime_notices", "nodejs10_billing_error"); + return new FirebaseError( + `Cloud Functions deployment requires the pay-as-you-go (Blaze) billing plan. To upgrade your project, visit the following URL: + +https://console.firebase.google.com/project/${projectId}/usage/details + +For additional information about this requirement, see Firebase FAQs: + +${FAQ_URL}`, + { exit: 1 } + ); +} + +function nodePermissionError(projectId: string): FirebaseError { + track("functions_runtime_notices", "nodejs10_permission_error"); + return new FirebaseError(`Cloud Functions deployment requires the Cloud Build API to be enabled. The current credentials do not have permission to enable APIs for project ${clc.bold( + projectId + )}. + +Please ask a project owner to visit the following URL to enable Cloud Build: + +https://console.cloud.google.com/apis/library/cloudbuild.googleapis.com?project=${projectId} + +For additional information about this requirement, see Firebase FAQs: +${FAQ_URL} +`); +} + +function isPermissionError(e: { context?: { body?: { error?: { status?: string } } } }): boolean { + return e.context?.body?.error?.status === "PERMISSION_DENIED"; +} + +/** + * Checks for various warnings and API enablements needed based on the runtime + * of the deployed functions. + * + * @param projectId Project ID upon which to check enablement. + */ +export async function cloudBuildEnabled(projectId: string): Promise { + try { + await ensure(projectId, CLOUD_BUILD_API, "functions"); + } catch (e: any) { + if (isBillingError(e)) { + throw nodeBillingError(projectId); + } else if (isPermissionError(e)) { + throw nodePermissionError(projectId); + } + + throw e; + } +} + +// We previously force-enabled AR. We want to wait on this to see if we can give +// an upgrade warning in the future. If it already is enabled though we want to +// remember this and still use the cleaner if necessary. +export async function maybeEnableAR(projectId: string): Promise { + if (!previews.artifactregistry) { + return ensureApiEnabled.check( + projectId, + "artifactregistry.googleapis.com", + "functions", + /* silent= */ true + ); + } + await ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions"); + return true; +} + +/** + * Returns a mapping of all secrets declared in a stack to the bound service accounts. + */ +async function secretsToServiceAccounts(b: backend.Backend): Promise>> { + const secretsToSa: Record> = {}; + for (const e of backend.allEndpoints(b)) { + const sa = e.serviceAccountEmail || (await module.exports.defaultServiceAccount(e)); + for (const s of e.secretEnvironmentVariables! || []) { + const serviceAccounts = secretsToSa[s.secret] || new Set(); + serviceAccounts.add(sa); + secretsToSa[s.secret] = serviceAccounts; + } + } + return secretsToSa; +} + +/** + * Ensures that runtime service account has access to the secrets. + * + * To avoid making more than one simultaneous call to setIamPolicy calls per secret, the function batches all + * service account that requires access to it. + */ +export async function secretAccess( + projectId: string, + wantBackend: backend.Backend, + haveBackend: backend.Backend +) { + const ensureAccess = async (secret: string, serviceAccounts: string[]) => { + logLabeledBullet( + "functions", + `ensuring ${clc.bold(serviceAccounts.join(", "))} access to secret ${clc.bold(secret)}.` + ); + await ensureServiceAgentRole( + { name: secret, projectId }, + serviceAccounts, + "roles/secretmanager.secretAccessor" + ); + logLabeledSuccess( + "functions", + `ensured ${clc.bold(serviceAccounts.join(", "))} access to ${clc.bold(secret)}.` + ); + }; + + const wantSecrets = await secretsToServiceAccounts(wantBackend); + const haveSecrets = await secretsToServiceAccounts(haveBackend); + + // Remove secret/service account pairs that already exists to avoid unnecessary IAM calls. + for (const [secret, serviceAccounts] of Object.entries(haveSecrets)) { + for (const serviceAccount of serviceAccounts) { + wantSecrets[secret]?.delete(serviceAccount); + } + if (wantSecrets[secret]?.size == 0) { + delete wantSecrets[secret]; + } + } + + const ensure = []; + for (const [secret, serviceAccounts] of Object.entries(wantSecrets)) { + ensure.push(ensureAccess(secret, Array.from(serviceAccounts))); + } + await Promise.all(ensure); +} diff --git a/src/deploy/functions/ensureCloudBuildEnabled.ts b/src/deploy/functions/ensureCloudBuildEnabled.ts index 2f2c6d3c2d8..e69de29bb2d 100644 --- a/src/deploy/functions/ensureCloudBuildEnabled.ts +++ b/src/deploy/functions/ensureCloudBuildEnabled.ts @@ -1,61 +0,0 @@ -import { bold } from "cli-color"; - -import * as track from "../../track"; -import { ensure } from "../../ensureApiEnabled"; -import { FirebaseError, isBillingError } from "../../error"; - -const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime"; -const CLOUD_BUILD_API = "cloudbuild.googleapis.com"; - -function nodeBillingError(projectId: string): FirebaseError { - track("functions_runtime_notices", "nodejs10_billing_error"); - return new FirebaseError( - `Cloud Functions deployment requires the pay-as-you-go (Blaze) billing plan. To upgrade your project, visit the following URL: - -https://console.firebase.google.com/project/${projectId}/usage/details - -For additional information about this requirement, see Firebase FAQs: - -${FAQ_URL}`, - { exit: 1 } - ); -} - -function nodePermissionError(projectId: string): FirebaseError { - track("functions_runtime_notices", "nodejs10_permission_error"); - return new FirebaseError(`Cloud Functions deployment requires the Cloud Build API to be enabled. The current credentials do not have permission to enable APIs for project ${bold( - projectId - )}. - -Please ask a project owner to visit the following URL to enable Cloud Build: - -https://console.cloud.google.com/apis/library/cloudbuild.googleapis.com?project=${projectId} - -For additional information about this requirement, see Firebase FAQs: -${FAQ_URL} -`); -} - -function isPermissionError(e: { context?: { body?: { error?: { status?: string } } } }): boolean { - return e.context?.body?.error?.status === "PERMISSION_DENIED"; -} - -/** - * Checks for various warnings and API enablements needed based on the runtime - * of the deployed functions. - * - * @param projectId Project ID upon which to check enablement. - */ -export async function ensureCloudBuildEnabled(projectId: string): Promise { - try { - await ensure(projectId, CLOUD_BUILD_API, "functions"); - } catch (e: any) { - if (isBillingError(e)) { - throw nodeBillingError(projectId); - } else if (isPermissionError(e)) { - throw nodePermissionError(projectId); - } - - throw e; - } -} diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index b855b6c350c..8c970a7d79e 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -1,26 +1,24 @@ import * as clc from "cli-color"; -import { Options } from "../../options"; -import { ensureCloudBuildEnabled } from "./ensureCloudBuildEnabled"; -import { functionMatchesAnyGroup, getFilterGroups } from "./functionsDeployHelper"; -import { logBullet } from "../../utils"; -import { getFunctionsConfig, prepareFunctionsUpload } from "./prepareFunctionsUpload"; -import { promptForFailurePolicies, promptForMinInstances } from "./prompts"; import * as args from "./args"; import * as backend from "./backend"; import * as ensureApiEnabled from "../../ensureApiEnabled"; import * as functionsConfig from "../../functionsConfig"; import * as functionsEnv from "../../functions/env"; +import * as runtimes from "./runtimes"; +import * as validate from "./validate"; +import * as ensure from "./ensure"; +import { Options } from "../../options"; +import { functionMatchesAnyGroup, getFilterGroups } from "./functionsDeployHelper"; +import { logBullet } from "../../utils"; +import { getFunctionsConfig, prepareFunctionsUpload } from "./prepareFunctionsUpload"; +import { promptForFailurePolicies, promptForMinInstances } from "./prompts"; import { previews } from "../../previews"; import { needProjectId } from "../../projectUtils"; import { track } from "../../track"; -import * as runtimes from "./runtimes"; -import * as validate from "./validate"; -import * as utils from "../../utils"; import { logger } from "../../logger"; import { ensureTriggerRegions } from "./triggerRegionHelper"; import { ensureServiceAgentRoles } from "./checkIam"; -import { DelegateContext } from "./runtimes"; import { FirebaseError } from "../../error"; function hasUserConfig(config: Record): boolean { @@ -30,23 +28,7 @@ function hasUserConfig(config: Record): boolean { } function hasDotenv(opts: functionsEnv.UserEnvsOpts): boolean { - return previews.dotenv && functionsEnv.hasUserEnvs(opts); -} - -// We previously force-enabled AR. We want to wait on this to see if we can give -// an upgrade warning in the future. If it already is enabled though we want to -// remember this and still use the cleaner if necessary. -async function maybeEnableAR(projectId: string): Promise { - if (!previews.artifactregistry) { - return ensureApiEnabled.check( - projectId, - "artifactregistry.googleapis.com", - "functions", - /* silent= */ true - ); - } - await ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions"); - return true; + return functionsEnv.hasUserEnvs(opts); } export async function prepare( @@ -64,7 +46,7 @@ export async function prepare( } const sourceDir = options.config.path(sourceDirName); - const delegateContext: DelegateContext = { + const delegateContext: runtimes.DelegateContext = { projectId, sourceDir, projectDir: options.config.projectDir, @@ -85,8 +67,8 @@ export async function prepare( "runtimeconfig", /* silent=*/ true ), - ensureCloudBuildEnabled(projectId), - maybeEnableAR(projectId), + ensure.cloudBuildEnabled(projectId), + ensure.maybeEnableAR(projectId), ]); context.runtimeConfigEnabled = checkAPIsEnabled[1]; context.artifactRegistryEnabled = checkAPIsEnabled[3]; @@ -97,7 +79,7 @@ export async function prepare( const runtimeConfig = await getFunctionsConfig(context); const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); - const userEnvOpt = { + const userEnvOpt: functionsEnv.UserEnvsOpts = { functionsSource: sourceDir, projectId: projectId, projectAlias: options.projectAlias, @@ -186,6 +168,8 @@ export async function prepare( await promptForFailurePolicies(options, matchingBackend, haveBackend); await promptForMinInstances(options, matchingBackend, haveBackend); await backend.checkAvailability(context, wantBackend); + await validate.secretsAreValid(projectId, matchingBackend); + await ensure.secretAccess(projectId, matchingBackend, haveBackend); } /** diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 7ddf2d9a6e5..668e6f2ccc3 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -89,6 +89,7 @@ function parseEndpoints( labels: "object", ingressSettings: "string", environmentVariables: "object", + secretEnvironmentVariables: "array", httpsTrigger: "object", eventTrigger: "object", scheduleTrigger: "object", diff --git a/src/deploy/functions/runtimes/index.ts b/src/deploy/functions/runtimes/index.ts index d098b1dd45c..245bd630df3 100644 --- a/src/deploy/functions/runtimes/index.ts +++ b/src/deploy/functions/runtimes/index.ts @@ -2,6 +2,7 @@ import * as backend from "../backend"; import * as golang from "./golang"; import * as node from "./node"; import * as validate from "../validate"; +import * as projectPath from "../../../projectPath"; import { FirebaseError } from "../../../error"; /** Supported runtimes for new Cloud Functions. */ @@ -104,7 +105,7 @@ export interface DelegateContext { projectDir: string; // Absolute path of the source directory. sourceDir: string; - runtime: string; + runtime?: string; } type Factory = (context: DelegateContext) => Promise; diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 8e801a902c2..d916127813d 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -44,6 +44,7 @@ export interface TriggerAnnotation { maxInstances?: number; minInstances?: number; serviceAccountEmail?: string; + secrets?: string[]; httpsTrigger?: { invoker?: string[]; }; @@ -219,6 +220,20 @@ export function addResourcesToBackend( } endpoint.vpcConnector = maybeId; } + + if (annotation.secrets) { + const secretEnvs: backend.SecretEnvVar[] = []; + for (const secret of annotation.secrets) { + const secretEnv: backend.SecretEnvVar = { + secret, + projectId, + key: secret, + }; + secretEnvs.push(secretEnv); + } + endpoint.secretEnvironmentVariables = secretEnvs; + } + proto.copyIfPresent( endpoint, annotation, diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 1ba2a116ad7..66e4370ae11 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -2,8 +2,12 @@ import * as path from "path"; import * as clc from "cli-color"; import { FirebaseError } from "../../error"; +import { getSecretVersion, SecretVersion } from "../../gcp/secretManager"; +import { logger } from "../../logger"; import * as fsutils from "../../fsutils"; import * as backend from "./backend"; +import * as utils from "../../utils"; +import * as secrets from "../../functions/secrets"; /** Validate that the configuration for endpoints are valid. */ export function endpointsAreValid(wantBackend: backend.Backend): void { @@ -84,3 +88,87 @@ export function functionIdsAreValid(functions: { id: string; platform: string }[ throw new FirebaseError(msg); } } + +/** + * Validate secret environment variables setting, if any. + * A bad secret configuration can lead to a significant delay in function deploys. + * + * If validation fails for any secret config, throws a FirebaseError. + */ +export async function secretsAreValid(projectId: string, wantBackend: backend.Backend) { + const endpoints = backend + .allEndpoints(wantBackend) + .filter((e) => e.secretEnvironmentVariables && e.secretEnvironmentVariables.length > 0); + validatePlatformTargets(endpoints); + await validateSecretVersions(projectId, endpoints); +} + +/** + * Ensures that all endpoints specifying secret environment variables target platform that supports the feature. + */ +function validatePlatformTargets(endpoints: backend.Endpoint[]) { + const supportedPlatforms = ["gcfv1"]; + const unsupported = endpoints.filter((e) => !supportedPlatforms.includes(e.platform)); + if (unsupported.length > 0) { + const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`); + throw new FirebaseError( + `Tried to set secret environment variables on ${errs.join(", ")}. ` + + `Only ${supportedPlatforms.join(", ")} support secret environments.` + ); + } +} + +/** + * Validate each secret version referenced in target endpoints. + * + * A secret version is valid if: + * 1) It exists. + * 2) It's in state "enabled". + */ +async function validateSecretVersions(projectId: string, endpoints: backend.Endpoint[]) { + const toResolve: Set = new Set(); + for (const s of secrets.of(endpoints)) { + toResolve.add(s.secret); + } + + const results = await utils.allSettled( + Array.from(toResolve).map(async (secret): Promise => { + // We resolve the secret to its latest version - we do not allow CF3 customers to pin secret versions. + const sv = await getSecretVersion(projectId, secret, "latest"); + logger.debug(`Resolved secret version of ${clc.bold(secret)} to ${clc.bold(sv.versionId)}.`); + return sv; + }) + ); + + const secretVersions: Record = {}; + const errs: FirebaseError[] = []; + for (const result of results) { + if (result.status === "fulfilled") { + const sv = result.value; + if (sv.state != "ENABLED") { + errs.push( + new FirebaseError( + `Expected secret ${sv.secret.name}@${sv.versionId} to be in state ENABLED not ${sv.state}.` + ) + ); + } + secretVersions[sv.secret.name] = sv; + } else { + errs.push(new FirebaseError((result.reason as { message: string }).message)); + } + } + + if (errs.length) { + throw new FirebaseError("Failed to validate secret versions", { children: errs }); + } + + // Fill in versions. + for (const s of secrets.of(endpoints)) { + s.version = secretVersions[s.secret].versionId; + if (!s.version) { + throw new FirebaseError( + "Secret version is unexpectedly undefined. This should never happen." + ); + } + } +} diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 86b6b973539..6ad48266099 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -414,10 +414,8 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) ); utils.assertIsStringOrUndefined(options.extensionDir); - const functionsDir = path.join( - options.extensionDir || options.config.projectDir, - options.config.src.functions.source - ); + const projectDir = options.extensionDir || options.config.projectDir; + const functionsDir = path.join(projectDir, options.config.src.functions.source); let inspectFunctions: number | undefined; if (options.inspectFunctions) { @@ -461,6 +459,7 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) ]; const functionsEmulator = new FunctionsEmulator({ projectId, + projectDir, emulatableBackends, account, host: functionsAddr.host, diff --git a/src/emulator/emulatorLogger.ts b/src/emulator/emulatorLogger.ts index 601892404c5..fdb62805dd9 100644 --- a/src/emulator/emulatorLogger.ts +++ b/src/emulator/emulatorLogger.ts @@ -13,8 +13,9 @@ import { LogData } from "./loggingEmulator"; * USER - logged by user code, always show to humans. * WARN - warnings from our code that humans need. * WARN_ONCE - warnings from our code that humans need, but only once per session. + * ERROR - error from our code that humans need. */ -type LogType = "DEBUG" | "INFO" | "BULLET" | "SUCCESS" | "USER" | "WARN" | "WARN_ONCE"; +type LogType = "DEBUG" | "INFO" | "BULLET" | "SUCCESS" | "USER" | "WARN" | "WARN_ONCE" | "ERROR"; const TYPE_VERBOSITY: { [type in LogType]: number } = { DEBUG: 0, @@ -24,6 +25,7 @@ const TYPE_VERBOSITY: { [type in LogType]: number } = { USER: 2, WARN: 2, WARN_ONCE: 2, + ERROR: 2, }; export enum Verbosity { @@ -112,6 +114,9 @@ export class EmulatorLogger { case "SUCCESS": utils.logSuccess(text, "info", mergedData); break; + case "ERROR": + utils.logBullet(text, "error", mergedData); + break; } } @@ -286,6 +291,9 @@ You can probably fix this by running "npm install ${systemLog.data.name}@latest" EmulatorLogger.warnOnceCache.add(text); } break; + case "ERROR": + utils.logLabeledError(label, text, "error", mergedData); + break; } } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 9bdee6ed3a0..5fb2eda2faf 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -24,7 +24,6 @@ import * as chokidar from "chokidar"; import * as spawn from "cross-spawn"; import { ChildProcess, spawnSync } from "child_process"; import { - emulatedFunctionsByRegion, EmulatedTriggerDefinition, SignatureType, EventSchedule, @@ -37,6 +36,8 @@ import { getSignatureType, HttpConstants, ParsedTriggerDefinition, + emulatedFunctionsFromEndpoints, + emulatedFunctionsByRegion, } from "./functionsEmulatorShared"; import { EmulatorRegistry } from "./registry"; import { EventEmitter } from "events"; @@ -46,18 +47,22 @@ import { RuntimeWorker, RuntimeWorkerPool } from "./functionsRuntimeWorker"; import { PubsubEmulator } from "./pubsubEmulator"; import { FirebaseError } from "../error"; import { WorkQueue } from "./workQueue"; -import { createDestroyer } from "../utils"; +import { allSettled, createDestroyer } from "../utils"; import { getCredentialPathAsync } from "../defaultCredentials"; import { AdminSdkConfig, constructDefaultAdminSdkConfig, getProjectAdminSdkConfigOrCached, } from "./adminSdkConfig"; -import * as functionsEnv from "../functions/env"; import { EventUtils } from "./events/types"; import { functionIdsAreValid } from "../deploy/functions/validate"; +import { getRuntimeDelegate } from "../deploy/functions/runtimes"; +import * as backend from "../deploy/functions/backend"; +import * as functionsEnv from "../functions/env"; +import { accessSecretVersion } from "../gcp/secretManager"; const EVENT_INVOKE = "functions:invoke"; +const LOCAL_SECRETS_FILE = ".secret.local"; /* * The Realtime Database emulator expects the `path` field in its trigger @@ -85,6 +90,7 @@ export interface EmulatableBackend { export interface FunctionsEmulatorArgs { projectId: string; + projectDir: string; emulatableBackends: EmulatableBackend[]; account?: Account; port?: number; @@ -93,6 +99,7 @@ export interface FunctionsEmulatorArgs { disabledRuntimeFeatures?: FunctionsRuntimeFeatures; debugPort?: number; remoteEmulators?: { [key: string]: EmulatorInfo }; + adminSdkConfig?: AdminSdkConfig; } // FunctionsRuntimeInstance is the handler for a running function invocation @@ -103,6 +110,8 @@ export interface FunctionsRuntimeInstance { events: EventEmitter; // A promise which is fulfilled when the runtime has exited exit: Promise; + // A cwd of the process + cwd: string; // A function to manually kill the child process as normal cleanup shutdown(): void; @@ -166,9 +175,7 @@ export class FunctionsEmulator implements EmulatorInstance { this.args.disabledRuntimeFeatures.timeout = true; } - this.adminSdkConfig = { - projectId: this.args.projectId, - }; + this.adminSdkConfig = { ...this.args.adminSdkConfig, projectId: this.args.projectId }; const mode = this.args.debugPort ? FunctionsExecutionMode.SEQUENTIAL @@ -324,41 +331,25 @@ export class FunctionsEmulator implements EmulatorInstance { return hub; } - startFunctionRuntime( + async startFunctionRuntime( backend: EmulatableBackend, - triggerId: string, - targetName: string, - signatureType: SignatureType, + trigger: EmulatedTriggerDefinition, proto?: any, runtimeOpts?: InvokeRuntimeOpts - ): RuntimeWorker { - const bundleTemplate = this.getBaseBundle(backend); + ): Promise { + const bundleTemplate = this.getBaseBundle(); const runtimeBundle: FunctionsRuntimeBundle = { ...bundleTemplate, - emulators: { - firestore: this.getEmulatorInfo(Emulators.FIRESTORE), - database: this.getEmulatorInfo(Emulators.DATABASE), - pubsub: this.getEmulatorInfo(Emulators.PUBSUB), - auth: this.getEmulatorInfo(Emulators.AUTH), - storage: this.getEmulatorInfo(Emulators.STORAGE), - }, - nodeMajorVersion: backend.nodeMajorVersion, proto, - triggerId, - targetName, }; if (!backend.nodeBinary) { - throw new FirebaseError(`No node binary for ${triggerId}. This should never happen.`); + throw new FirebaseError(`No node binary for ${trigger.id}. This should never happen.`); } const opts = runtimeOpts || { nodeBinary: backend.nodeBinary, extensionTriggers: backend.predefinedTriggers, }; - const worker = this.invokeRuntime( - runtimeBundle, - opts, - this.getRuntimeEnvs(backend, { targetName, signatureType }) - ); + const worker = await this.invokeRuntime(backend, trigger, runtimeBundle, opts); return worker; } @@ -371,16 +362,18 @@ export class FunctionsEmulator implements EmulatorInstance { e.env = { ...credentialEnv, ...e.env }; } - const adminSdkConfig = await getProjectAdminSdkConfigOrCached(this.args.projectId); - if (adminSdkConfig) { - this.adminSdkConfig = adminSdkConfig; - } else { - this.logger.logLabeled( - "WARN", - "functions", - "Unable to fetch project Admin SDK configuration, Admin SDK behavior in Cloud Functions emulator may be incorrect." - ); - this.adminSdkConfig = constructDefaultAdminSdkConfig(this.args.projectId); + if (Object.keys(this.adminSdkConfig || {}).length <= 1) { + const adminSdkConfig = await getProjectAdminSdkConfigOrCached(this.args.projectId); + if (adminSdkConfig) { + this.adminSdkConfig = adminSdkConfig; + } else { + this.logger.logLabeled( + "WARN", + "functions", + "Unable to fetch project Admin SDK configuration, Admin SDK behavior in Cloud Functions emulator may be incorrect." + ); + this.adminSdkConfig = constructDefaultAdminSdkConfig(this.args.projectId); + } } const { host, port } = this.getInfo(); @@ -440,14 +433,8 @@ export class FunctionsEmulator implements EmulatorInstance { /** * When a user changes their code, we need to look for triggers defined in their updates sources. - * To do this, we spin up a "diagnostic" runtime invocation. In other words, we pretend we're - * going to invoke a cloud function in the emulator, but stop short of actually running a function. - * Instead, we set up the environment and catch a special "triggers-parsed" log from the runtime - * then exit out. * - * A "diagnostic" FunctionsRuntimeBundle looks just like a normal bundle except triggerId == "". - * - * TODO(abehaskins): Gracefully handle removal of deleted function definitions + * TODO(b/216167890): Gracefully handle removal of deleted function definitions */ async loadTriggers(emulatableBackend: EmulatableBackend, force = false): Promise { // Before loading any triggers we need to make sure there are no 'stale' workers @@ -459,33 +446,34 @@ export class FunctionsEmulator implements EmulatorInstance { `No node binary for ${emulatableBackend.functionsDir}. This should never happen.` ); } - const worker = this.invokeRuntime( - this.getBaseBundle(emulatableBackend), - { - nodeBinary: emulatableBackend.nodeBinary, - extensionTriggers: emulatableBackend.predefinedTriggers, - }, - // Don't include user envs when parsing triggers. - { - ...this.getSystemEnvs(), - ...this.getEmulatorEnvs(), - FIREBASE_CONFIG: this.getFirebaseConfig(), - ...emulatableBackend.env, - } - ); - - const triggerParseEvent = await EmulatorLog.waitForLog( - worker.runtime.events, - "SYSTEM", - "triggers-parsed" - ); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const parsedDefinitions = triggerParseEvent.data - .triggerDefinitions as ParsedTriggerDefinition[]; - - const triggerDefinitions: EmulatedTriggerDefinition[] = - emulatedFunctionsByRegion(parsedDefinitions); + let triggerDefinitions: EmulatedTriggerDefinition[]; + if (emulatableBackend.predefinedTriggers) { + triggerDefinitions = emulatedFunctionsByRegion(emulatableBackend.predefinedTriggers); + } else { + const runtimeDelegate = await getRuntimeDelegate({ + projectId: this.args.projectId, + projectDir: this.args.projectDir, + sourceDir: emulatableBackend.functionsDir, + }); + logger.debug(`Validating ${runtimeDelegate.name} source`); + await runtimeDelegate.validate(); + logger.debug(`Building ${runtimeDelegate.name} source`); + await runtimeDelegate.build(); + logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`); + const discoveredBackend = await runtimeDelegate.discoverSpec( + {}, + // Don't include user envs when parsing triggers. + { + ...this.getSystemEnvs(), + ...this.getEmulatorEnvs(), + FIREBASE_CONFIG: this.getFirebaseConfig(), + ...emulatableBackend.env, + } + ); + const endpoints = backend.allEndpoints(discoveredBackend); + triggerDefinitions = emulatedFunctionsFromEndpoints(endpoints); + } // When force is true we set up all triggers, otherwise we only set up // triggers which have a unique function name const toSetup = triggerDefinitions.filter((definition) => { @@ -584,7 +572,7 @@ export class FunctionsEmulator implements EmulatorInstance { } else { this.logger.log( "WARN", - `Trigger trigger "${definition.name}" has has neither "httpsTrigger" or "eventTrigger" member` + `Unsupported function type on ${definition.name}. Expected either httpsTrigger or eventTrigger.` ); } @@ -621,7 +609,7 @@ export class FunctionsEmulator implements EmulatorInstance { if (result === null || result.length !== 3) { this.logger.log( "WARN", - `Event trigger "${key}" has malformed "resource" member. ` + `${eventTrigger.resource}` + `Event function "${key}" has malformed "resource" member. ` + `${eventTrigger.resource}` ); return Promise.reject(); } @@ -642,7 +630,7 @@ export class FunctionsEmulator implements EmulatorInstance { } else { this.logger.log( "WARN", - `No project in use. Registering function trigger for sentinel namespace '${Constants.DEFAULT_DATABASE_EMULATOR_NAMESPACE}'` + `No project in use. Registering function for sentinel namespace '${Constants.DEFAULT_DATABASE_EMULATOR_NAMESPACE}'` ); } @@ -659,7 +647,7 @@ export class FunctionsEmulator implements EmulatorInstance { return true; }) .catch((err) => { - this.logger.log("WARN", "Error adding trigger: " + err); + this.logger.log("WARN", "Error adding Realtime Database function: " + err); throw err; }); } @@ -674,7 +662,12 @@ export class FunctionsEmulator implements EmulatorInstance { return Promise.resolve(false); } - const bundle = JSON.stringify({ eventTrigger }); + const bundle = JSON.stringify({ + eventTrigger: { + ...eventTrigger, + service: "firestore.googleapis.com", + }, + }); logger.debug(`addFirestoreTrigger`, JSON.stringify(bundle)); return api @@ -687,7 +680,7 @@ export class FunctionsEmulator implements EmulatorInstance { return true; }) .catch((err) => { - this.logger.log("WARN", "Error adding trigger: " + err); + this.logger.log("WARN", "Error adding firestore function: " + err); throw err; }); } @@ -779,7 +772,7 @@ export class FunctionsEmulator implements EmulatorInstance { const record = this.triggers[triggerKey]; if (!record) { logger.debug(`Could not find key=${triggerKey} in ${JSON.stringify(this.triggers)}`); - throw new FirebaseError(`No trigger with key ${triggerKey}`); + throw new FirebaseError(`No function with key ${triggerKey}`); } return record; @@ -816,42 +809,13 @@ export class FunctionsEmulator implements EmulatorInstance { triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false })); } - getBaseBundle(backend: EmulatableBackend): FunctionsRuntimeBundle { + getBaseBundle(): FunctionsRuntimeBundle { return { - cwd: backend.functionsDir, - projectId: this.args.projectId, - triggerId: "", - targetName: "", - emulators: { - firestore: EmulatorRegistry.getInfo(Emulators.FIRESTORE), - database: EmulatorRegistry.getInfo(Emulators.DATABASE), - pubsub: EmulatorRegistry.getInfo(Emulators.PUBSUB), - auth: EmulatorRegistry.getInfo(Emulators.AUTH), - storage: EmulatorRegistry.getInfo(Emulators.STORAGE), - }, - adminSdkConfig: { - databaseURL: this.adminSdkConfig.databaseURL, - storageBucket: this.adminSdkConfig.storageBucket, - }, + proto: {}, disabled_features: this.args.disabledRuntimeFeatures, }; } - /** - * Returns a node major version ("10", "8") or null - * @param frb the current Functions Runtime Bundle - */ - getRequestedNodeRuntimeVersion(frb: FunctionsRuntimeBundle): string | undefined { - const pkg = require(path.join(frb.cwd, "package.json")); - return frb.nodeMajorVersion || (pkg.engines && pkg.engines.node); - } - /** - * Returns the path to a "node" executable to use. - * @param cwd the directory to checkout for a package.json file. - * @param nodeMajorVersion forces the emulator to choose this version. Used when emulating extensions, - * since in production, extensions ignore the node version provided in package.json and use the version - * specified in extension.yaml. This will ALWAYS be populated when emulating extensions, even if they - * are using the default version. - */ + getNodeBinary(backend: EmulatableBackend): string { const pkg = require(path.join(backend.functionsDir, "package.json")); // If the developer hasn't specified a Node to use, inform them that it's an option and use default @@ -925,10 +889,7 @@ export class FunctionsEmulator implements EmulatorInstance { return {}; } - getSystemEnvs(triggerDef?: { - targetName: string; - signatureType: SignatureType; - }): Record { + getSystemEnvs(trigger?: EmulatedTriggerDefinition): Record { const envs: Record = {}; // Env vars guaranteed by GCF platform. @@ -937,11 +898,11 @@ export class FunctionsEmulator implements EmulatorInstance { envs.K_REVISION = "1"; envs.PORT = "80"; - if (triggerDef) { - const service = triggerDef.targetName; + if (trigger) { + const service = trigger.name; const target = service.replace(/-/g, "."); envs.FUNCTION_TARGET = target; - envs.FUNCTION_SIGNATURE_TYPE = triggerDef.signatureType; + envs.FUNCTION_SIGNATURE_TYPE = getSignatureType(trigger); envs.K_SERVICE = service; } return envs; @@ -954,6 +915,7 @@ export class FunctionsEmulator implements EmulatorInstance { envs.TZ = "UTC"; // Fixes https://github.com/firebase/firebase-tools/issues/2253 envs.FIREBASE_DEBUG_MODE = "true"; envs.FIREBASE_DEBUG_FEATURES = JSON.stringify({ skipTokenVerification: true }); + // TODO(danielylee): Support timeouts. Temporarily dropping the feature until we finish refactoring. // Make firebase-admin point at the Firestore emulator const firestoreEmulator = this.getEmulatorInfo(Emulators.FIRESTORE); @@ -1015,28 +977,78 @@ export class FunctionsEmulator implements EmulatorInstance { getRuntimeEnvs( backend: EmulatableBackend, - triggerDef?: { - targetName: string; - signatureType: SignatureType; - } + trigger: EmulatedTriggerDefinition ): Record { return { ...this.getUserEnvs(backend), - ...this.getSystemEnvs(triggerDef), + ...this.getSystemEnvs(trigger), ...this.getEmulatorEnvs(), FIREBASE_CONFIG: this.getFirebaseConfig(), ...backend.env, }; } - invokeRuntime( + async resolveSecretEnvs( + backend: EmulatableBackend, + trigger: EmulatedTriggerDefinition + ): Promise> { + let secretEnvs: Record = {}; + + try { + const data = fs.readFileSync(path.join(backend.functionsDir, LOCAL_SECRETS_FILE), "utf8"); + secretEnvs = functionsEnv.parseStrict(data); + } catch (e: any) { + if (e.code !== "ENOENT") { + this.logger.logLabeled( + "ERROR", + "functions", + `Failed to read local secrets file ${LOCAL_SECRETS_FILE}: ${e.message}` + ); + } + } + + const secrets: backend.SecretEnvVar[] = trigger.secretEnvironmentVariables || []; + const accesses = secrets + .filter((s) => !secretEnvs[s.secret]) + .map(async (s) => { + this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.key}@latest`); + const value = await accessSecretVersion(this.getProjectId(), s.key, "latest"); + return [s.secret, value]; + }); + const accessResults = await allSettled(accesses); + + const errs: string[] = []; + for (const result of accessResults) { + if (result.status === "rejected") { + errs.push(result.reason as string); + } else { + const [k, v] = result.value; + secretEnvs[k] = v; + } + } + + if (errs.length > 0) { + this.logger.logLabeled( + "ERROR", + "functions", + "Unable to access secret environment variables from Google Cloud Secret Manager. " + + "Make sure the credential used for the Functions Emulator have access " + + `or provide override values in ${LOCAL_SECRETS_FILE}:\n\t` + + errs.join("\n\t") + ); + } + return secretEnvs; + } + + async invokeRuntime( + backend: EmulatableBackend, + trigger: EmulatedTriggerDefinition, frb: FunctionsRuntimeBundle, - opts: InvokeRuntimeOpts, - runtimeEnv?: Record - ): RuntimeWorker { + opts: InvokeRuntimeOpts + ): Promise { // If we can use an existing worker there is almost nothing to do. - if (this.workerPool.readyForWork(frb.triggerId)) { - return this.workerPool.submitWork(frb.triggerId, frb, opts); + if (this.workerPool.readyForWork(trigger.id)) { + return this.workerPool.submitWork(trigger.id, frb, opts); } const emitter = new EventEmitter(); @@ -1048,7 +1060,7 @@ export class FunctionsEmulator implements EmulatorInstance { if (this.args.debugPort) { if (process.env.FIREPIT_VERSION && process.execPath == opts.nodeBinary) { - const requestedMajorNodeVersion = this.getRequestedNodeRuntimeVersion(frb); + const requestedMajorNodeVersion = this.getNodeBinary(backend); this.logger.log( "WARN", `To enable function inspection, please run "${process.execPath} is:npm i node@${requestedMajorNodeVersion} --save-dev" in your functions directory` @@ -1063,7 +1075,7 @@ export class FunctionsEmulator implements EmulatorInstance { // module resolution. This feature is mostly incompatible with CF3 (prod or emulated) so // if we detect it we should warn the developer. // See: https://classic.yarnpkg.com/en/docs/pnp/ - const pnpPath = path.join(frb.cwd, ".pnp.js"); + const pnpPath = path.join(backend.functionsDir, ".pnp.js"); if (fs.existsSync(pnpPath)) { EmulatorLogger.forEmulator(Emulators.FUNCTIONS).logLabeled( "WARN_ONCE", @@ -1074,9 +1086,12 @@ export class FunctionsEmulator implements EmulatorInstance { ); } + const runtimeEnv = this.getRuntimeEnvs(backend, trigger); + const secretEnvs = await this.resolveSecretEnvs(backend, trigger); + const childProcess = spawn(opts.nodeBinary, args, { - env: { node: opts.nodeBinary, ...process.env, ...(runtimeEnv ?? {}) }, - cwd: frb.cwd, + cwd: backend.functionsDir, + env: { node: opts.nodeBinary, ...process.env, ...runtimeEnv, ...secretEnvs }, stdio: ["pipe", "pipe", "pipe", "ipc"], }); @@ -1117,6 +1132,7 @@ export class FunctionsEmulator implements EmulatorInstance { childProcess.on("exit", resolve); }), events: emitter, + cwd: backend.functionsDir, shutdown: () => { childProcess.kill(); }, @@ -1129,8 +1145,8 @@ export class FunctionsEmulator implements EmulatorInstance { }, }; - this.workerPool.addWorker(frb.triggerId, runtime); - return this.workerPool.submitWork(frb.triggerId, frb, opts); + this.workerPool.addWorker(trigger.id, runtime); + return this.workerPool.submitWork(trigger.id, frb, opts); } async disableBackgroundTriggers() { @@ -1165,13 +1181,7 @@ export class FunctionsEmulator implements EmulatorInstance { } const trigger = record.def; const service = getFunctionService(trigger); - const worker = this.startFunctionRuntime( - record.backend, - trigger.id, - trigger.name, - getSignatureType(trigger), - proto - ); + const worker = await this.startFunctionRuntime(record.backend, trigger, proto); return new Promise((resolve, reject) => { if (projectId !== this.args.projectId) { @@ -1231,7 +1241,7 @@ export class FunctionsEmulator implements EmulatorInstance { } private tokenFromAuthHeader(authHeader: string) { - const match = authHeader.match(/^Bearer (.*)$/); + const match = /^Bearer (.*)$/.exec(authHeader); if (!match) { return; } @@ -1275,7 +1285,7 @@ export class FunctionsEmulator implements EmulatorInstance { res .status(404) .send( - `Function ${triggerId} does not exist, valid triggers are: ${Object.keys( + `Function ${triggerId} does not exist, valid functions are: ${Object.keys( this.triggers ).join(", ")}` ); @@ -1309,13 +1319,7 @@ export class FunctionsEmulator implements EmulatorInstance { ); } } - const worker = this.startFunctionRuntime( - record.backend, - trigger.id, - trigger.name, - "http", - undefined - ); + const worker = await this.startFunctionRuntime(record.backend, trigger); worker.onLogs((el: EmulatorLog) => { if (el.level === "FATAL") { diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index a7c51aab7b5..4522fe02720 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -1,31 +1,30 @@ import * as fs from "fs"; -import { EmulatorLog } from "./types"; import { CloudFunction, DeploymentOptions, https } from "firebase-functions"; +import * as express from "express"; +import * as path from "path"; +import * as admin from "firebase-admin"; +import * as bodyParser from "body-parser"; +import { pathToFileURL, URL } from "url"; +import * as _ from "lodash"; + +import { EmulatorLog } from "./types"; +import { Constants } from "./constants"; import { - ParsedTriggerDefinition, - EmulatedTrigger, - emulatedFunctionsByRegion, - EmulatedTriggerDefinition, EmulatedTriggerMap, findModuleRoot, FunctionsRuntimeBundle, FunctionsRuntimeFeatures, - getEmulatedTriggersFromDefinitions, FunctionsRuntimeArgs, HttpConstants, - getSignatureType, SignatureType, } from "./functionsEmulatorShared"; import { compareVersionStrings } from "./functionsEmulatorUtils"; -import * as express from "express"; -import * as path from "path"; -import * as admin from "firebase-admin"; -import * as bodyParser from "body-parser"; -import { pathToFileURL, URL } from "url"; -import * as _ from "lodash"; -let triggers: EmulatedTriggerMap | undefined; +let functionTrigger: CloudFunction; +let FUNCTION_TARGET_NAME: string; +let FUNCTION_SIGNATURE: string; + let developerPkgJSON: PackageJSON | undefined; /** @@ -226,7 +225,7 @@ async function resolveDeveloperNodeModule( } // Once we know it's in the package.json, make sure it's actually `npm install`ed - const resolveResult = await requireResolveAsync(name, { paths: [frb.cwd] }).catch(noOp); + const resolveResult = await requireResolveAsync(name, { paths: [process.cwd()] }).catch(noOp); if (!resolveResult) { return { declared: true, installed: false }; } @@ -300,7 +299,7 @@ function requirePackageJson(frb: FunctionsRuntimeBundle): PackageJSON | undefine } try { - const pkg = require(`${frb.cwd}/package.json`); + const pkg = require(`${process.cwd()}/package.json`); developerPkgJSON = { engines: pkg.engines || {}, dependencies: pkg.dependencies || {}, @@ -537,7 +536,7 @@ function initializeRuntimeConfig(frb: FunctionsRuntimeBundle) { // In the future, we will bump up the minimum version of the Firebase Functions SDK // required to run the functions emulator to v3.15.1 and get rid of this workaround. if (!process.env.CLOUD_RUNTIME_CONFIG) { - const configPath = `${frb.cwd}/.runtimeconfig.json`; + const configPath = `${process.cwd()}/.runtimeconfig.json`; try { const configContent = fs.readFileSync(configPath, "utf8"); if (configContent) { @@ -600,7 +599,7 @@ async function initializeFirebaseAdminStubs(frb: FunctionsRuntimeBundle): Promis localFunctionsModule.app.setEmulatedAdminApp(defaultApp); // When the auth emulator is running, try to disable JWT verification. - if (frb.emulators.auth) { + if (process.env[Constants.FIREBASE_AUTH_EMULATOR_HOST]) { if (compareVersionStrings(adminResolution.version, "9.3.0") < 0) { new EmulatorLog( "WARN_ONCE", @@ -624,17 +623,21 @@ async function initializeFirebaseAdminStubs(frb: FunctionsRuntimeBundle): Promis return defaultApp; }) .when("firestore", (target) => { - warnAboutFirestoreProd(frb); + warnAboutFirestoreProd(); return Proxied.getOriginal(target, "firestore"); }) .when("database", (target) => { - warnAboutDatabaseProd(frb); + warnAboutDatabaseProd(); return Proxied.getOriginal(target, "database"); }) .when("auth", (target) => { - warnAboutAuthProd(frb); + warnAboutAuthProd(); return Proxied.getOriginal(target, "auth"); }) + .when("storage", (target) => { + warnAboutStorageProd(); + return Proxied.getOriginal(target, "storage"); + }) .finalize(); // Stub the admin module in the require cache @@ -658,22 +661,26 @@ function makeProxiedFirebaseApp( const appProxy = new Proxied(original); return appProxy .when("firestore", (target: any) => { - warnAboutFirestoreProd(frb); + warnAboutFirestoreProd(); return Proxied.getOriginal(target, "firestore"); }) .when("database", (target: any) => { - warnAboutDatabaseProd(frb); + warnAboutDatabaseProd(); return Proxied.getOriginal(target, "database"); }) .when("auth", (target: any) => { - warnAboutAuthProd(frb); + warnAboutAuthProd(); return Proxied.getOriginal(target, "auth"); }) + .when("storage", (target: any) => { + warnAboutStorageProd(); + return Proxied.getOriginal(target, "storage"); + }) .finalize(); } -function warnAboutFirestoreProd(frb: FunctionsRuntimeBundle): void { - if (frb.emulators.firestore) { +function warnAboutFirestoreProd(): void { + if (process.env[Constants.FIRESTORE_EMULATOR_HOST]) { return; } @@ -684,8 +691,8 @@ function warnAboutFirestoreProd(frb: FunctionsRuntimeBundle): void { ).log(); } -function warnAboutDatabaseProd(frb: FunctionsRuntimeBundle): void { - if (frb.emulators.database) { +function warnAboutDatabaseProd(): void { + if (process.env[Constants.FIREBASE_DATABASE_EMULATOR_HOST]) { return; } @@ -696,8 +703,8 @@ function warnAboutDatabaseProd(frb: FunctionsRuntimeBundle): void { ).log(); } -function warnAboutAuthProd(frb: FunctionsRuntimeBundle): void { - if (frb.emulators.auth) { +function warnAboutAuthProd(): void { + if (process.env[Constants.FIREBASE_AUTH_EMULATOR_HOST]) { return; } @@ -708,6 +715,18 @@ function warnAboutAuthProd(frb: FunctionsRuntimeBundle): void { ).log(); } +function warnAboutStorageProd(): void { + if (process.env[Constants.FIREBASE_STORAGE_EMULATOR_HOST]) { + return; + } + + new EmulatorLog( + "WARN_ONCE", + "runtime-status", + "The Firebase Storage emulator is not running, so calls to Firebase Storage will affect production." + ).log(); +} + async function initializeFunctionsConfigHelper(frb: FunctionsRuntimeBundle): Promise { const functionsResolution = await assertResolveDeveloperNodeModule(frb, "firebase-functions"); const localFunctionsModule = require(functionsResolution.resolution); @@ -759,7 +778,7 @@ function rawBodySaver(req: express.Request, res: express.Response, buf: Buffer): (req as any).rawBody = buf; } -async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigger): Promise { +async function processHTTPS(frb: FunctionsRuntimeBundle): Promise { const ephemeralServer = express(); const functionRouter = express.Router(); // eslint-disable-line new-cap const socketPath = frb.socketPath; @@ -773,7 +792,6 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge const handler = async (req: express.Request, res: express.Response) => { try { logDebug(`Ephemeral server handling ${req.method} request`); - const func = trigger.getRawFunction(); res.on("finish", () => { instance.close((err) => { if (err) { @@ -784,7 +802,7 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge }); }); - await runHTTPS([req, res], func); + await runHTTPS([req, res]); } catch (err: any) { rejectEphemeralServer(err); } @@ -833,14 +851,13 @@ async function processHTTPS(frb: FunctionsRuntimeBundle, trigger: EmulatedTrigge async function processBackground( frb: FunctionsRuntimeBundle, - trigger: EmulatedTrigger, signature: SignatureType ): Promise { const proto = frb.proto; logDebug("ProcessBackground", proto); if (signature === "cloudevent") { - return runCloudEvent(proto, trigger.getRawFunction()); + return runCloudEvent(proto); } // All formats of the payload should carry a "data" property. The "context" property does @@ -858,7 +875,7 @@ async function processBackground( } } - await runBackground({ data, context }, trigger.getRawFunction()); + await runBackground({ data, context }); } /** @@ -878,32 +895,29 @@ async function runFunction(func: () => Promise): Promise { } } -async function runBackground(proto: any, func: CloudFunction): Promise { +async function runBackground(proto: any): Promise { logDebug("RunBackground", proto); await runFunction(() => { - return func(proto.data, proto.context); + return functionTrigger(proto.data, proto.context); }); } -async function runCloudEvent(event: unknown, func: CloudFunction): Promise { +async function runCloudEvent(event: unknown): Promise { logDebug("RunCloudEvent", event); await runFunction(() => { - return func(event); + return functionTrigger(event); }); } -async function runHTTPS( - args: any[], - func: (a: express.Request, b: express.Response) => Promise -): Promise { +async function runHTTPS(args: any[]): Promise { if (args.length < 2) { throw new Error("Function must be passed 2 args."); } await runFunction(() => { - return func(args[0], args[1]); + return functionTrigger(args[0], args[1]); }); } @@ -917,8 +931,8 @@ async function moduleResolutionDetective(frb: FunctionsRuntimeBundle, error: Err falsey, so we just catch to keep from throwing. */ const clues = { - tsconfigJSON: await requireAsync("./tsconfig.json", { paths: [frb.cwd] }).catch(noOp), - packageJSON: await requireAsync("./package.json", { paths: [frb.cwd] }).catch(noOp), + tsconfigJSON: await requireAsync("./tsconfig.json", { paths: [process.cwd()] }).catch(noOp), + packageJSON: await requireAsync("./package.json", { paths: [process.cwd()] }).catch(noOp), }; const isPotentially = { @@ -941,23 +955,12 @@ function logDebug(msg: string, data?: any): void { new EmulatorLog("DEBUG", "runtime-status", `[${process.pid}] ${msg}`, data).log(); } -async function invokeTrigger( - frb: FunctionsRuntimeBundle, - triggers: EmulatedTriggerMap -): Promise { - if (!frb.triggerId) { - throw new Error("frb.triggerId unexpectedly null"); - } - - new EmulatorLog("INFO", "runtime-status", `Beginning execution of "${frb.triggerId}"`, { +async function invokeTrigger(frb: FunctionsRuntimeBundle): Promise { + new EmulatorLog("INFO", "runtime-status", `Beginning execution of "${FUNCTION_TARGET_NAME}"`, { frb, }).log(); - const trigger = triggers[frb.triggerId]; - logDebug("triggerDefinition", trigger.definition); - const signature = getSignatureType(trigger.definition); - - logDebug(`Running ${frb.triggerId} in signature ${signature}`); + logDebug(`Running ${FUNCTION_TARGET_NAME} in signature ${FUNCTION_SIGNATURE}`); let seconds = 0; const timerId = setInterval(() => { @@ -966,26 +969,29 @@ async function invokeTrigger( let timeoutId; if (isFeatureEnabled(frb, "timeout")) { + let timeout = process.env.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS || "60"; + if (timeout.endsWith("s")) { + timeout = timeout.slice(0, -1); + } + const timeoutMs = parseInt(timeout, 10) * 1000; timeoutId = setTimeout(() => { new EmulatorLog( "WARN", "runtime-status", - `Your function timed out after ~${ - trigger.definition.timeout || "60s" - }. To configure this timeout, see + `Your function timed out after ~${timeout}s. To configure this timeout, see https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation.` ).log(); throw new Error("Function timed out."); - }, trigger.timeoutMs); + }, timeoutMs); } - switch (signature) { + switch (FUNCTION_SIGNATURE) { case "event": case "cloudevent": - await processBackground(frb, triggers[frb.triggerId], signature); + await processBackground(frb, FUNCTION_SIGNATURE); break; case "http": - await processHTTPS(frb, triggers[frb.triggerId]); + await processHTTPS(frb); break; } @@ -997,15 +1003,33 @@ async function invokeTrigger( new EmulatorLog( "INFO", "runtime-status", - `Finished "${frb.triggerId}" in ~${Math.max(seconds, 1)}s` + `Finished "${FUNCTION_TARGET_NAME}" in ~${Math.max(seconds, 1)}s` ).log(); } async function initializeRuntime( - frb: FunctionsRuntimeBundle, - serializedFunctionTrigger?: string, - extensionTriggers?: ParsedTriggerDefinition[] + frb: FunctionsRuntimeBundle ): Promise { + FUNCTION_TARGET_NAME = process.env.FUNCTION_TARGET || ""; + if (!FUNCTION_TARGET_NAME) { + new EmulatorLog( + "FATAL", + "runtime-status", + `Environment variable FUNCTION_TARGET cannot be empty. This shouldn't happen.` + ).log(); + await flushAndExit(1); + } + + FUNCTION_SIGNATURE = process.env.FUNCTION_SIGNATURE_TYPE || ""; + if (!FUNCTION_SIGNATURE) { + new EmulatorLog( + "FATAL", + "runtime-status", + `Environment variable FUNCTION_SIGNATURE_TYPE cannot be empty. This shouldn't happen.` + ).log(); + await flushAndExit(1); + } + logDebug(`Disabled runtime features: ${JSON.stringify(frb.disabled_features)}`); const verified = await verifyDeveloperNodeModules(frb); @@ -1024,40 +1048,39 @@ async function initializeRuntime( await initializeFunctionsConfigHelper(frb); await initializeFirebaseFunctionsStubs(frb); await initializeFirebaseAdminStubs(frb); +} - let parsedDefinitions: ParsedTriggerDefinition[] = []; +async function loadTrigger( + frb: FunctionsRuntimeBundle, + functionTarget: string, + serializedFunctionTrigger?: string +): Promise> { let triggerModule; - if (serializedFunctionTrigger) { /* tslint:disable:no-eval */ triggerModule = eval(serializedFunctionTrigger)(); } else { try { - triggerModule = require(frb.cwd); + triggerModule = require(process.cwd()); } catch (err: any) { if (err.code !== "ERR_REQUIRE_ESM") { + // Try to run diagnostics to see what could've gone wrong before rethrowing the error. await moduleResolutionDetective(frb, err); - return; + throw err; } - const modulePath = require.resolve(frb.cwd); + const modulePath = require.resolve(process.cwd()); // Resolve module path to file:// URL. Required for windows support. const moduleURL = pathToFileURL(modulePath).href; triggerModule = await dynamicImport(moduleURL); } } - if (extensionTriggers) { - parsedDefinitions = extensionTriggers; - } else { - require("../deploy/functions/runtimes/node/extractTriggers")(triggerModule, parsedDefinitions); + const maybeTrigger = functionTarget.split(".").reduce((mod, functionTargetPart) => { + return mod?.[functionTargetPart]; + }, triggerModule); + if (!maybeTrigger) { + throw new Error(`Failed to find function ${functionTarget} in the loaded module`); } - - const triggerDefinitions: EmulatedTriggerDefinition[] = - emulatedFunctionsByRegion(parsedDefinitions); - - const triggers = getEmulatedTriggersFromDefinitions(triggerDefinitions, triggerModule); - - new EmulatorLog("SYSTEM", "triggers-parsed", "", { triggers, triggerDefinitions }).log(); - return triggers; + return maybeTrigger; } async function flushAndExit(code: number) { @@ -1080,38 +1103,32 @@ async function handleMessage(message: string) { return; } - if (!triggers) { - const serializedTriggers = runtimeArgs.opts ? runtimeArgs.opts.serializedTriggers : undefined; - const extensionTriggers = runtimeArgs.opts ? runtimeArgs.opts.extensionTriggers : undefined; - triggers = await initializeRuntime(runtimeArgs.frb, serializedTriggers, extensionTriggers); - } - - // If we don't have triggers by now, we can't run. - if (!triggers) { - await flushAndExit(1); - return; + if (!functionTrigger) { + try { + await initializeRuntime(runtimeArgs.frb); + const serializedTriggers = runtimeArgs.opts ? runtimeArgs.opts.serializedTriggers : undefined; + functionTrigger = await loadTrigger( + runtimeArgs.frb, + FUNCTION_TARGET_NAME, + serializedTriggers + ); + } catch (e: any) { + logDebug(e); + new EmulatorLog( + "FATAL", + "runtime-status", + `Failed to initialize and load trigger. This shouldn't happen: ${e.message}` + ).log(); + await flushAndExit(1); + return; + } } // If there's no trigger id it's just a diagnostic call. We can go idle right away. - if (!runtimeArgs.frb.triggerId) { - await goIdle(); - return; - } - - if (!triggers[runtimeArgs.frb.triggerId]) { - new EmulatorLog( - "FATAL", - "runtime-status", - `Could not find trigger "${runtimeArgs.frb.triggerId}" in your functions directory.` - ).log(); - return; - } else { - logDebug(`Trigger "${runtimeArgs.frb.triggerId}" has been found, beginning invocation!`); - } + logDebug(`Beginning invocation function ${FUNCTION_TARGET_NAME}!`); try { - await invokeTrigger(runtimeArgs.frb, triggers); - + await invokeTrigger(runtimeArgs.frb); // If we were passed serialized triggers we have to exit the runtime after, // otherwise we can go IDLE and await another request. if (runtimeArgs.opts && runtimeArgs.opts.serializedTriggers) { diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 2a752a64151..31d808a63c5 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -7,7 +7,15 @@ import * as fs from "fs"; import { Constants } from "./constants"; import { InvokeRuntimeOpts } from "./functionsEmulator"; -import { FunctionsPlatform } from "../deploy/functions/backend"; +import { + Endpoint, + FunctionsPlatform, + isEventTriggered, + isHttpsTriggered, + isScheduleTriggered, + SecretEnvVar, +} from "../deploy/functions/backend"; +import { copyIfPresent } from "../gcp/proto"; export type SignatureType = "http" | "event" | "cloudevent"; @@ -27,6 +35,7 @@ export interface ParsedTriggerDefinition { export interface EmulatedTriggerDefinition extends ParsedTriggerDefinition { id: string; // An unique-id per-function, generated from the name and the region. region: string; + secretEnvironmentVariables?: SecretEnvVar[]; // Secret env vars needs to be specially loaded in the Emulator. } export interface EventSchedule { @@ -51,40 +60,14 @@ export interface FunctionsRuntimeArgs { } export interface FunctionsRuntimeBundle { - projectId: string; - proto?: any; - triggerId?: string; - targetName?: string; - emulators: { - firestore?: { - host: string; - port: number; - }; - database?: { - host: string; - port: number; - }; - pubsub?: { - host: string; - port: number; - }; - auth?: { - host: string; - port: number; - }; - storage?: { - host: string; - port: number; - }; - }; - adminSdkConfig: { - databaseURL?: string; - storageBucket?: string; - }; + proto: any; + // TODO(danielylee): One day, we hope to get rid of all of the following properties. + // Our goal is for the emulator environment to mimic the production environment as much + // as possible, and that includes how the emulated functions are called. In prod, + // the calls are made over HTTP which provides only the uri path, payload, headers, etc + // and none of these extra properties. socketPath?: string; disabled_features?: FunctionsRuntimeFeatures; - nodeMajorVersion?: number; - cwd: string; } export interface FunctionsRuntimeFeatures { @@ -135,6 +118,74 @@ export class EmulatedTrigger { } } +/** + * Creates a unique trigger definition from Endpoints. + * @param Endpoints A list of all CloudFunctions in the deployment. + * @return A list of all CloudFunctions in the deployment. + */ +export function emulatedFunctionsFromEndpoints(endpoints: Endpoint[]): EmulatedTriggerDefinition[] { + const regionDefinitions: EmulatedTriggerDefinition[] = []; + for (const endpoint of endpoints) { + if (!endpoint.region) { + endpoint.region = "us-central1"; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const def: EmulatedTriggerDefinition = { + entryPoint: endpoint.entryPoint, + platform: endpoint.platform, + region: endpoint.region, + // TODO: Difference in use of name/id in Endpoint vs Emulator is subtle and confusing. + // We should later refactor the emulator to stop using a custom trigger definition. + name: endpoint.id, + id: `${endpoint.region}-${endpoint.id}`, + }; + copyIfPresent( + def, + endpoint, + "timeout", + "availableMemoryMb", + "labels", + "platform", + "secretEnvironmentVariables" + ); + // TODO: This transformation is confusing but must be kept since the Firestore/RTDB trigger registration + // process requires it in this form. Need to work in Firestore emulator for a proper fix... + if (isHttpsTriggered(endpoint)) { + def.httpsTrigger = endpoint.httpsTrigger; + } else if (isEventTriggered(endpoint)) { + const eventTrigger = endpoint.eventTrigger; + if (endpoint.platform === "gcfv1") { + def.eventTrigger = { + eventType: eventTrigger.eventType, + resource: eventTrigger.eventFilters.resource, + }; + } else { + // Only pubsub and storage events are supported for gcfv2. + const { resource, topic, bucket } = endpoint.eventTrigger.eventFilters; + const eventResource = resource || topic || bucket; + if (!eventResource) { + // Unsupported event type for GCFv2 + continue; + } + def.eventTrigger = { + eventType: eventTrigger.eventType, + resource: eventResource, + }; + } + } else if (isScheduleTriggered(endpoint)) { + // TODO: This is an awkward transformation. Emulator does not understand scheduled triggers - maybe it should? + def.eventTrigger = { eventType: "pubsub", resource: "" }; + def.schedule = endpoint.scheduleTrigger as EventSchedule; + } else { + // All other trigger types are not supported by the emulator + // We leave both eventTrigger and httpTrigger attributes empty + // and let the caller deal with invalid triggers. + } + regionDefinitions.push(def); + } + return regionDefinitions; +} + /** * Creates a unique trigger definition for each region a function is defined in. * @param definitions A list of all CloudFunctions in the deployment. diff --git a/src/emulator/functionsEmulatorShell.ts b/src/emulator/functionsEmulatorShell.ts index d5b743b7028..3e0010576bc 100644 --- a/src/emulator/functionsEmulatorShell.ts +++ b/src/emulator/functionsEmulatorShell.ts @@ -1,10 +1,6 @@ import * as uuid from "uuid"; import { EmulatableBackend, FunctionsEmulator } from "./functionsEmulator"; -import { - EmulatedTriggerDefinition, - getSignatureType, - SignatureType, -} from "./functionsEmulatorShared"; +import { EmulatedTriggerDefinition, getSignatureType } from "./functionsEmulatorShared"; import * as utils from "../utils"; import { logger } from "../logger"; import { FirebaseError } from "../error"; @@ -68,13 +64,7 @@ export class FunctionsEmulatorShell implements FunctionsShellController { data, }; - this.emu.startFunctionRuntime( - this.backend, - trigger.id, - trigger.name, - getSignatureType(trigger), - proto - ); + this.emu.startFunctionRuntime(this.backend, trigger, proto); } private getTrigger(name: string): EmulatedTriggerDefinition { diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index 7eca3b642e7..9b18ac16802 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -69,7 +69,7 @@ export class RuntimeWorker { // TODO(samstern): I would like to do this elsewhere... if (!execFrb.socketPath) { - execFrb.socketPath = getTemporarySocketPath(this.runtime.pid, execFrb.cwd); + execFrb.socketPath = getTemporarySocketPath(this.runtime.pid, this.runtime.cwd); this.log(`Assigning socketPath: ${execFrb.socketPath}`); } diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index 030a4f3e955..ba1b0e441f7 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -240,7 +240,7 @@ async function addNewSecretVersion( paramSpec: Param, secretValue: string ) { - const version = await secretManagerApi.addVersion(secret, secretValue); + const version = await secretManagerApi.addVersion(projectId, secret.name, secretValue); await secretsUtils.grantFirexServiceAgentSecretAdminRole(secret); return `projects/${version.secret.projectId}/secrets/${version.secret.name}/versions/${version.versionId}`; } diff --git a/src/extensions/secretsUtils.ts b/src/extensions/secretsUtils.ts index 2aee6959c82..66e008021f9 100644 --- a/src/extensions/secretsUtils.ts +++ b/src/extensions/secretsUtils.ts @@ -27,7 +27,7 @@ export async function grantFirexServiceAgentSecretAdminRole( ); const saEmail = `service-${projectNumber}@${firexSaProjectId}.iam.gserviceaccount.com`; - return secretManagerApi.grantServiceAgentRole(secret, saEmail, "roles/secretmanager.admin"); + return secretManagerApi.ensureServiceAgentRole(secret, [saEmail], "roles/secretmanager.admin"); } export async function getManagedSecrets( diff --git a/src/functions/env.ts b/src/functions/env.ts index fb5feca6e1f..5e403aff1d2 100644 --- a/src/functions/env.ts +++ b/src/functions/env.ts @@ -4,11 +4,11 @@ import * as path from "path"; import { FirebaseError } from "../error"; import { logger } from "../logger"; -import { previews } from "../previews"; import { logBullet } from "../utils"; const FUNCTIONS_EMULATOR_DOTENV = ".env.local"; +const RESERVED_PREFIXES = ["X_GOOGLE_", "FIREBASE_", "EXT_"]; const RESERVED_KEYS = [ // Cloud Functions for Firebase "FIREBASE_CONFIG", @@ -149,10 +149,10 @@ export function validateKey(key: string): void { ", and then consist of uppercase ASCII letters, digits, and underscores." ); } - if (key.startsWith("X_GOOGLE_") || key.startsWith("FIREBASE_")) { + if (RESERVED_PREFIXES.some((prefix) => key.startsWith(prefix))) { throw new KeyValidationError( key, - `Key ${key} starts with a reserved prefix (X_GOOGLE_ or FIREBASE_)` + `Key ${key} starts with a reserved prefix (${RESERVED_PREFIXES.join(" ")})` ); } } @@ -160,7 +160,7 @@ export function validateKey(key: string): void { // Parse dotenv file, but throw errors if: // 1. Input has any invalid lines. // 2. Any env key fails validation. -function parseStrict(data: string): Record { +export function parseStrict(data: string): Record { const { envs, errors } = parse(data); if (errors.length) { @@ -252,10 +252,6 @@ export function loadUserEnvs({ projectAlias, isEmulator, }: UserEnvsOpts): Record { - if (!previews.dotenv) { - return {}; - } - const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator); if (envFiles.length == 0) { return {}; diff --git a/src/functions/secrets.ts b/src/functions/secrets.ts new file mode 100644 index 00000000000..926eca36606 --- /dev/null +++ b/src/functions/secrets.ts @@ -0,0 +1,170 @@ +import * as backend from "../deploy/functions/backend"; +import { + createSecret, + getSecret, + getSecretVersion, + listSecrets, + listSecretVersions, + parseSecretResourceName, + patchSecret, + Secret, +} from "../gcp/secretManager"; +import { Options } from "../options"; +import { FirebaseError } from "../error"; +import { logWarning } from "../utils"; +import { promptOnce } from "../prompt"; +import { validateKey } from "./env"; + +const FIREBASE_MANGED = "firebase-managed"; + +/** + * Returns true if secret is managed by Firebase. + */ +export function isFirebaseManaged(secret: Secret): boolean { + return Object.keys(secret.labels || []).includes(FIREBASE_MANGED); +} + +/** + * Return labels to mark secret as managed by Firebase. + * @internal + */ +export function labels(): Record { + return { [FIREBASE_MANGED]: "true" }; +} + +function toUpperSnakeCase(key: string): string { + return key + .replace("-", "_") + .replace(".", "_") + .replace(/([a-z])([A-Z])/g, "$1_$2") + .toUpperCase(); +} + +/** + * Validate and transform keys to match the convention recommended by Firebase. + */ +export async function ensureValidKey(key: string, options: Options): Promise { + const transformedKey = toUpperSnakeCase(key); + if (transformedKey !== key) { + if (options.force) { + throw new FirebaseError("Secret key must be in UPPER_SNAKE_CASE."); + } + logWarning(`By convention, secret key must be in UPPER_SNAKE_CASE.`); + const confirm = await promptOnce( + { + name: "updateKey", + type: "confirm", + default: true, + message: `Would you like to use ${transformedKey} as key instead?`, + }, + options + ); + if (!confirm) { + throw new FirebaseError("Secret key must be in UPPER_SNAKE_CASE."); + } + } + try { + validateKey(transformedKey); + } catch (err: any) { + throw new FirebaseError(`Invalid secret key ${transformedKey}`, { children: [err] }); + } + return transformedKey; +} + +/** + * Ensure secret exists. Optionally prompt user to have non-Firebase managed keys be managed by Firebase. + */ +export async function ensureSecret( + projectId: string, + name: string, + options: Options +): Promise { + try { + const secret = await getSecret(projectId, name); + if (!isFirebaseManaged(secret)) { + if (!options.force) { + logWarning( + "Your secret is not managed by Firebase. " + + "Firebase managed secrets are automatically pruned to reduce your monthly cost for using Secret Manager. " + ); + const confirm = await promptOnce( + { + name: "updateLabels", + type: "confirm", + default: true, + message: `Would you like to have your secret ${secret.name} managed by Firebase?`, + }, + options + ); + if (confirm) { + return patchSecret(projectId, secret.name, { + ...secret.labels, + ...labels(), + }); + } + } + } + return secret; + } catch (err: any) { + if (err.status !== 404) { + throw err; + } + } + return await createSecret(projectId, name, labels()); +} + +/** + * Collects all secret environment variables of endpoints. + */ +export function of(endpoints: backend.Endpoint[]): backend.SecretEnvVar[] { + return endpoints.reduce( + (envs, endpoint) => [...envs, ...(endpoint.secretEnvironmentVariables || [])], + [] as backend.SecretEnvVar[] + ); +} + +/** + * Returns all secret versions from Firebase managed secrets unused in the given list of endpoints. + */ +export async function pruneSecrets( + projectInfo: { projectNumber: string; projectId: string }, + endpoints: backend.Endpoint[] +): Promise[]> { + const { projectId, projectNumber } = projectInfo; + const pruneKey = (name: string, version: string) => `${name}@${version}`; + const prunedSecrets: Set = new Set(); + + // Collect all Firebase managed secret versions + const haveSecrets = await listSecrets(projectId, `labels.${FIREBASE_MANGED}=true`); + for (const secret of haveSecrets) { + const versions = await listSecretVersions(projectId, secret.name, `state: ENABLED`); + for (const version of versions) { + prunedSecrets.add(pruneKey(secret.name, version.versionId)); + } + } + + // Prune all project-scoped secrets in use. + const sevs = of(endpoints).filter( + (sev) => sev.projectId === projectId || sev.projectId === projectNumber + ); + for (const sev of sevs) { + let name = sev.secret; + if (name.includes("/")) { + const secret = parseSecretResourceName(name); + name = secret.name; + } + + let version = sev.version; + if (version === "latest") { + // We need to figure out what "latest" resolves to. + const resolved = await getSecretVersion(projectId, name, version); + version = resolved.versionId; + } + + prunedSecrets.delete(pruneKey(name, version!)); + } + + return Array.from(prunedSecrets) + .map((key) => key.split("@")) + .map(([secret, version]) => ({ projectId, version, secret, key: secret })); +} diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 4f18d5071cc..6ae493c1b42 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -10,6 +10,8 @@ import * as runtimes from "../deploy/functions/runtimes"; import * as iam from "./iam"; import { Client } from "../apiv2"; import { functionsOrigin } from "../api"; +import { getFirebaseProject } from "../management/projects"; +import { assertExhaustive } from "../functional"; export const API_VERSION = "v1"; const client = new Client({ urlPrefix: functionsOrigin, apiVersion: API_VERSION }); @@ -515,6 +517,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi "ingressSettings", "labels", "environmentVariables", + "secretEnvironmentVariables", "sourceUploadUrl" ); @@ -585,7 +588,8 @@ export function functionFromEndpoint( "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", - "environmentVariables" + "environmentVariables", + "secretEnvironmentVariables" ); return gcfFunction; diff --git a/src/gcp/secretManager.ts b/src/gcp/secretManager.ts index b617d516118..0a7a8eea1bf 100644 --- a/src/gcp/secretManager.ts +++ b/src/gcp/secretManager.ts @@ -1,6 +1,22 @@ +import * as iam from "./iam"; + import { logLabeledSuccess } from "../utils"; -import { secretManagerOrigin } from "../api"; +import { FirebaseError } from "../error"; import { Client } from "../apiv2"; +import { secretManagerOrigin } from "../api"; + +// Matches projects/{PROJECT}/secrets/{SECRET} +const SECRET_NAME_REGEX = new RegExp( + "projects\\/" + + "(?(?:\\d+)|(?:[A-Za-z]+[A-Za-z\\d-]*[A-Za-z\\d]?))\\/" + + "secrets\\/" + + "(?[A-Za-z\\d\\-_]+)" +); + +// Matches projects/{PROJECT}/secrets/{SECRET}/versions/{latest|VERSION} +const SECRET_VERSION_NAME_REGEX = new RegExp( + SECRET_NAME_REGEX.source + "\\/versions\\/" + "(?latest|[0-9]+)" +); export const secretManagerConsoleUri = (projectId: string) => `https://console.cloud.google.com/security/secret-manager?project=${projectId}`; @@ -12,38 +28,169 @@ export interface Secret { labels?: Record; } +type SecretVersionState = "STATE_UNSPECIFIED" | "ENABLED" | "DISABLED" | "DESTROYED"; + export interface SecretVersion { secret: Secret; versionId: string; + + // Output-only fields + readonly state?: SecretVersionState; +} + +interface CreateSecretRequest { + name: string; + replication: { automatic: {} }; + labels: Record; +} + +interface AddVersionRequest { + payload: { data: string }; } -const apiClient = new Client({ urlPrefix: secretManagerOrigin, apiVersion: "v1beta1" }); +interface SecretVersionResponse { + name: string; + state: SecretVersionState; +} -export async function listSecrets(projectId: string): Promise { - const listRes = await apiClient.get<{ secrets: any[] }>(`/projects/${projectId}/secrets`); - return listRes.body.secrets.map((s) => parseSecretResourceName(s.name)); +interface AccessSecretVersionResponse { + name: string; + payload: { + data: string; + }; } +const API_VERSION = "v1"; + +const client = new Client({ urlPrefix: secretManagerOrigin, apiVersion: API_VERSION }); + +/** + * Returns secret resource of given name in the project. + */ export async function getSecret(projectId: string, name: string): Promise { - const getRes = await apiClient.get<{ name: string; labels: Record }>( - `/projects/${projectId}/secrets/${name}` - ); + const getRes = await client.get(`projects/${projectId}/secrets/${name}`); const secret = parseSecretResourceName(getRes.body.name); secret.labels = getRes.body.labels ?? {}; return secret; } +/** + * Lists all secret resources associated with a project. + */ +export async function listSecrets(projectId: string, filter?: string): Promise { + type Response = { secrets: Secret[]; nextPageToken?: string }; + const secrets: Secret[] = []; + const path = `projects/${projectId}/secrets`; + const baseOpts = filter ? { queryParams: { filter } } : {}; + + let pageToken = ""; + while (true) { + const opts = + pageToken === "" + ? baseOpts + : { ...baseOpts, queryParams: { ...baseOpts?.queryParams, pageToken } }; + const res = await client.get(path, opts); + + for (const s of res.body.secrets) { + secrets.push({ + ...parseSecretResourceName(s.name), + labels: s.labels ?? {}, + }); + } + + if (!res.body.nextPageToken) { + break; + } + pageToken = res.body.nextPageToken; + } + return secrets; +} + +/** + * List all secret versions associated with a secret. + */ +export async function listSecretVersions( + projectId: string, + name: string, + filter?: string +): Promise> { + type Response = { versions: SecretVersionResponse[]; nextPageToken?: string }; + const secrets: Required = []; + const path = `projects/${projectId}/secrets/${name}/versions`; + const baseOpts = filter ? { queryParams: { filter } } : {}; + + let pageToken = ""; + while (true) { + const opts = + pageToken === "" + ? baseOpts + : { ...baseOpts, queryParams: { ...baseOpts?.queryParams, pageToken } }; + const res = await client.get(path, opts); + + for (const s of res.body.versions || []) { + secrets.push({ + ...parseSecretVersionResourceName(s.name), + state: s.state, + }); + } + + if (!res.body.nextPageToken) { + break; + } + pageToken = res.body.nextPageToken; + } + return secrets; +} + +/** + * Returns secret version resource of given name and version in the project. + */ export async function getSecretVersion( projectId: string, name: string, version: string -): Promise { - const getRes = await apiClient.get<{ name: string }>( - `/projects/${projectId}/secrets/${name}/versions/${version}` +): Promise> { + const getRes = await client.get( + `projects/${projectId}/secrets/${name}/versions/${version}` ); - return parseSecretVersionResourceName(getRes.body.name); + return { + ...parseSecretVersionResourceName(getRes.body.name), + state: getRes.body.state, + }; } +/** + * Access secret value of a given secret version. + */ +export async function accessSecretVersion( + projectId: string, + name: string, + version: string +): Promise { + const res = await client.get( + `projects/${projectId}/secrets/${name}/versions/${version}:access` + ); + return Buffer.from(res.body.payload.data, "base64").toString(); +} + +/** + * Change state of secret version to destroyed. + */ +export async function destroySecretVersion( + projectId: string, + name: string, + version: string +): Promise { + if (version === "latest") { + const sv = await getSecretVersion(projectId, name, "latest"); + version = sv.versionId; + } + await client.post(`projects/${projectId}/secrets/${name}/versions/${version}:destroy`); +} + +/** + * Returns true if secret resource of given name exists on the project. + */ export async function secretExists(projectId: string, name: string): Promise { try { await getSecret(projectId, name); @@ -56,40 +203,56 @@ export async function secretExists(projectId: string, name: string): Promise ): Promise { - const createRes = await apiClient.post< - { replication: { automatic: {} }; labels: Record }, - { name: string } - >( - `/projects/${projectId}/secrets`, + const createRes = await client.post( + `projects/${projectId}/secrets`, { + name, replication: { automatic: {}, }, @@ -100,64 +263,112 @@ export async function createSecret( return parseSecretResourceName(createRes.body.name); } -export async function addVersion(secret: Secret, payloadData: string): Promise { - const res = await apiClient.post<{ payload: { data: string } }, { name: string }>( - `/projects/${secret.projectId}/secrets/${secret.name}:addVersion`, +/** + * Update metadata associated with a secret. + */ +export async function patchSecret( + projectId: string, + name: string, + labels: Record +): Promise { + const fullName = `projects/${projectId}/secrets/${name}`; + const res = await client.patch, Secret>( + fullName, + { name: fullName, labels }, + { queryParams: { updateMask: "labels" } } // Only allow patching labels for now. + ); + return parseSecretResourceName(res.body.name); +} + +/** + * Delete secret resource. + */ +export async function deleteSecret(projectId: string, name: string): Promise { + const path = `projects/${projectId}/secrets/${name}`; + await client.delete(path); +} + +/** + * Add new version the payload as value on the given secret. + */ +export async function addVersion( + projectId: string, + name: string, + payloadData: string +): Promise> { + const res = await client.post( + `projects/${projectId}/secrets/${name}:addVersion`, { payload: { data: Buffer.from(payloadData).toString("base64"), }, } ); - const nameTokens = res.body.name.split("/"); return { - secret: { - projectId: nameTokens[1], - name: nameTokens[3], - }, - versionId: nameTokens[5], + ...parseSecretVersionResourceName(res.body.name), + state: res.body.state, }; } -export async function grantServiceAgentRole( - secret: Secret, - serviceAccountEmail: string, - role: string -): Promise { - const getPolicyRes = await apiClient.get<{ bindings: any[] }>( - `/projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy` +/** + * Returns IAM policy of a secret resource. + */ +export async function getIamPolicy(secret: Secret): Promise { + const res = await client.get( + `projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy` ); + return res.body; +} - const bindings = getPolicyRes.body.bindings || []; - if ( - bindings.find( - (b: any) => - b.role == role && - b.members.find((m: string) => m == `serviceAccount:${serviceAccountEmail}`) - ) - ) { - // binding already exists, short-circuit. - return; - } - bindings.push({ - role: role, - members: [`serviceAccount:${serviceAccountEmail}`], - }); - await apiClient.post<{ policy: { bindings: unknown }; updateMask: { paths: string } }, void>( - `/projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, +/** + * Sets IAM policy on a secret resource. + */ +export async function setIamPolicy(secret: Secret, bindings: iam.Binding[]): Promise { + await client.post<{ policy: Partial; updateMask: string }, iam.Policy>( + `projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, { policy: { bindings, }, - updateMask: { - paths: "bindings", - }, + updateMask: "bindings", } ); +} + +/** + * Ensure that given service agents have the given IAM role on the secret resource. + */ +export async function ensureServiceAgentRole( + secret: Secret, + serviceAccountEmails: string[], + role: string +): Promise { + const policy = await module.exports.getIamPolicy(secret); + const bindings: iam.Binding[] = policy.bindings || []; + let binding = bindings.find((b) => b.role === role); + if (!binding) { + binding = { role, members: [] }; + bindings.push(binding); + } + + let shouldShortCircuit = true; + for (const serviceAccount of serviceAccountEmails) { + if (!binding.members.find((m) => m === `serviceAccount:${serviceAccount}`)) { + binding.members.push(`serviceAccount:${serviceAccount}`); + shouldShortCircuit = false; + } + } + + if (shouldShortCircuit) return; + + await module.exports.setIamPolicy(secret, bindings); + // SecretManager would like us to _always_ inform users when we grant access to one of their secrets. // As a safeguard against forgetting to do so, we log it here. logLabeledSuccess( - "SecretManager", - `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmail}` + "secretmanager", + `Granted ${role} on projects/${secret.projectId}/secrets/${ + secret.name + } to ${serviceAccountEmails.join(", ")}` ); } diff --git a/src/previews.ts b/src/previews.ts index f135f667665..8fc141b566c 100644 --- a/src/previews.ts +++ b/src/previews.ts @@ -9,7 +9,6 @@ interface PreviewFlags { functionsv2: boolean; golang: boolean; deletegcfartifacts: boolean; - dotenv: boolean; artifactregistry: boolean; } @@ -22,7 +21,6 @@ export const previews: PreviewFlags = { functionsv2: false, golang: false, deletegcfartifacts: false, - dotenv: false, artifactregistry: false, ...(configstore.get("previews") as Partial), diff --git a/src/serve/functions.ts b/src/serve/functions.ts index c05579f4dd6..f5f0c4c42c0 100644 --- a/src/serve/functions.ts +++ b/src/serve/functions.ts @@ -9,7 +9,6 @@ import { parseRuntimeVersion } from "../emulator/functionsEmulatorUtils"; import { needProjectId } from "../projectUtils"; import { getProjectDefaultAccount } from "../auth"; import { Options } from "../options"; -import { Config } from "../config"; import * as utils from "../utils"; // TODO(samstern): It would be better to convert this to an EmulatorServer @@ -37,14 +36,15 @@ export class FunctionsServer { const nodeMajorVersion = parseRuntimeVersion(options.config.get("functions.runtime")); this.backend = { functionsDir, - env: {}, nodeMajorVersion, + env: {}, }; // Normally, these two fields are included in args (and typed as such). // However, some poorly-typed tests may not have them and we need to provide // default values for those tests to work properly. const args: FunctionsEmulatorArgs = { projectId, + projectDir: options.config.projectDir, emulatableBackends: [this.backend], account, ...partialArgs, diff --git a/src/test/deploy/functions/backend.spec.ts b/src/test/deploy/functions/backend.spec.ts index 0cd049fc4bd..db4229f9530 100644 --- a/src/test/deploy/functions/backend.spec.ts +++ b/src/test/deploy/functions/backend.spec.ts @@ -550,6 +550,13 @@ describe("Backend", () => { expect(backend.someEndpoint(bkend, (fn) => fn.id === "missing")).to.be.false; }); + it("findEndpoint", () => { + expect(backend.findEndpoint(bkend, (fn) => fn.id === "endpointUS")).to.be.deep.equal( + endpointUS + ); + expect(backend.findEndpoint(bkend, (fn) => fn.id === "missing")).to.be.undefined; + }); + it("regionalEndpoints", () => { const have = backend.regionalEndpoints(bkend, endpointUS.region); const want = [endpointUS]; diff --git a/src/test/deploy/functions/ensure.spec.ts b/src/test/deploy/functions/ensure.spec.ts new file mode 100644 index 00000000000..2c01873b212 --- /dev/null +++ b/src/test/deploy/functions/ensure.spec.ts @@ -0,0 +1,267 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import * as nock from "nock"; + +import { FirebaseError } from "../../../error"; +import { logger } from "../../../logger"; +import { configstore } from "../../../configstore"; +import { POLL_SETTINGS } from "../../../ensureApiEnabled"; +import * as api from "../../../api"; +import * as backend from "../../../deploy/functions/backend"; +import * as ensure from "../../../deploy/functions/ensure"; +import * as secretManager from "../../../gcp/secretManager"; + +describe("ensureCloudBuildEnabled()", () => { + let restoreInterval: number; + before(() => { + restoreInterval = POLL_SETTINGS.pollInterval; + POLL_SETTINGS.pollInterval = 0; + }); + after(() => { + POLL_SETTINGS.pollInterval = restoreInterval; + }); + + let sandbox: sinon.SinonSandbox; + let logStub: sinon.SinonStub | null; + beforeEach(() => { + sandbox = sinon.createSandbox(); + logStub = sandbox.stub(logger, "warn"); + }); + + afterEach(() => { + expect(nock.isDone()).to.be.true; + sandbox.restore(); + timeStub = null; + logStub = null; + }); + + function mockServiceCheck(isEnabled = false): void { + nock(api.serviceUsageOrigin) + .get("/v1/projects/test-project/services/cloudbuild.googleapis.com") + .reply(200, { state: isEnabled ? "ENABLED" : "DISABLED" }); + } + + function mockServiceEnableSuccess(): void { + nock(api.serviceUsageOrigin) + .post("/v1/projects/test-project/services/cloudbuild.googleapis.com:enable") + .reply(200, {}); + } + + function mockServiceEnableBillingError(): void { + nock(api.serviceUsageOrigin) + .post("/v1/projects/test-project/services/cloudbuild.googleapis.com:enable") + .reply(403, { + error: { + details: [{ violations: [{ type: "serviceusage/billing-enabled" }] }], + }, + }); + } + + function mockServiceEnablePermissionError(): void { + nock(api.serviceUsageOrigin) + .post("/v1/projects/test-project/services/cloudbuild.googleapis.com:enable") + .reply(403, { + error: { + status: "PERMISSION_DENIED", + }, + }); + } + + let timeStub: sinon.SinonStub | null; + function stubTimes(warnAfter: number, errorAfter: number): void { + timeStub = sandbox.stub(configstore, "get"); + timeStub.withArgs("motd.cloudBuildWarnAfter").returns(warnAfter); + timeStub.withArgs("motd.cloudBuildErrorAfter").returns(errorAfter); + } + + describe("with cloudbuild service enabled", () => { + beforeEach(() => { + mockServiceCheck(true); + }); + + it("should succeed", async () => { + stubTimes(Date.now() - 10000, Date.now() - 5000); + + await expect(ensure.cloudBuildEnabled("test-project")).to.eventually.be.fulfilled; + expect(logStub?.callCount).to.eq(0); + }); + }); + + describe("with cloudbuild service disabled, but enabling succeeds", () => { + beforeEach(() => { + mockServiceCheck(false); + mockServiceEnableSuccess(); + mockServiceCheck(true); + }); + + it("should succeed", async () => { + stubTimes(Date.now() - 10000, Date.now() - 5000); + + await expect(ensure.cloudBuildEnabled("test-project")).to.eventually.be.fulfilled; + expect(logStub?.callCount).to.eq(1); // enabling an api logs a warning + }); + }); + + describe("with cloudbuild service disabled, but enabling fails with billing error", () => { + beforeEach(() => { + mockServiceCheck(false); + mockServiceEnableBillingError(); + }); + + it("should error", async () => { + stubTimes(Date.now() - 10000, Date.now() - 5000); + + await expect(ensure.cloudBuildEnabled("test-project")).to.eventually.be.rejectedWith( + FirebaseError, + /must be on the Blaze \(pay-as-you-go\) plan to complete this command/ + ); + }); + }); + + describe("with cloudbuild service disabled, but enabling fails with permission error", () => { + beforeEach(() => { + mockServiceCheck(false); + mockServiceEnablePermissionError(); + }); + + it("should error", async () => { + stubTimes(Date.now() - 10000, Date.now() - 5000); + + await expect(ensure.cloudBuildEnabled("test-project")).to.eventually.be.rejectedWith( + FirebaseError, + /Please ask a project owner to visit the following URL to enable Cloud Build/ + ); + }); + }); +}); + +describe("ensureSecretAccess", () => { + const DEFAULT_SA = "default-sa@google.com"; + const ENDPOINT_BASE: Omit = { + project: "project", + platform: "gcfv2", + id: "id", + region: "region", + entryPoint: "entry", + runtime: "nodejs16", + }; + const ENDPOINT: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + }; + + const projectId = "project-0"; + const secret0: backend.SecretEnvVar = { + projectId: "project", + key: "MY_SECRET_0", + secret: "MY_SECRET_0", + version: "2", + }; + const secret1: backend.SecretEnvVar = { + projectId: "project", + key: "ANOTHER_SECRET", + secret: "ANOTHER_SECRET", + version: "1", + }; + const e: backend.Endpoint = { + ...ENDPOINT, + project: projectId, + platform: "gcfv1", + secretEnvironmentVariables: [], + }; + + let defaultServiceAccountStub: sinon.SinonStub; + let secretManagerMock: sinon.SinonMock; + + beforeEach(() => { + defaultServiceAccountStub = sinon.stub(ensure, "defaultServiceAccount").resolves(DEFAULT_SA); + secretManagerMock = sinon.mock(secretManager); + }); + + afterEach(() => { + defaultServiceAccountStub.restore(); + secretManagerMock.verify(); + secretManagerMock.restore(); + }); + + it("ensures access to default service account", async () => { + const b = backend.of({ + ...e, + secretEnvironmentVariables: [secret0], + }); + secretManagerMock + .expects("ensureServiceAgentRole") + .once() + .withExactArgs( + { name: secret0.secret, projectId: projectId }, + [DEFAULT_SA], + "roles/secretmanager.secretAccessor" + ); + await ensure.secretAccess(projectId, b, backend.empty()); + }); + + it("ensures access to all secrets", async () => { + const b = backend.of({ + ...e, + secretEnvironmentVariables: [secret0, secret1], + }); + secretManagerMock.expects("ensureServiceAgentRole").twice(); + await ensure.secretAccess(projectId, b, backend.empty()); + }); + + it("combines service account to make one call per secret", async () => { + const b = backend.of( + { + ...e, + secretEnvironmentVariables: [secret0], + }, + { + ...e, + id: "another-id", + serviceAccountEmail: "foo@bar.com", + secretEnvironmentVariables: [secret0], + } + ); + secretManagerMock + .expects("ensureServiceAgentRole") + .once() + .withExactArgs( + { name: secret0.secret, projectId: projectId }, + [DEFAULT_SA, "foo@bar.com"], + "roles/secretmanager.secretAccessor" + ); + await ensure.secretAccess(projectId, b, backend.empty()); + }); + + it("skips calling IAM if secret is already bound to a service account", async () => { + const b = backend.of({ + ...e, + secretEnvironmentVariables: [secret0], + }); + secretManagerMock.expects("ensureServiceAgentRole").never(); + await ensure.secretAccess(projectId, b, b); + }); + + it("does not include service account already bounud to a secret", async () => { + const haveEndpoint = { + ...e, + secretEnvironmentVariables: [secret0], + }; + const haveBackend = backend.of(haveEndpoint); + const wantBackend = backend.of(haveEndpoint, { + ...e, + id: "another-id", + serviceAccountEmail: "foo@bar.com", + secretEnvironmentVariables: [secret0], + }); + secretManagerMock + .expects("ensureServiceAgentRole") + .once() + .withExactArgs( + { name: secret0.secret, projectId: projectId }, + ["foo@bar.com"], + "roles/secretmanager.secretAccessor" + ); + await ensure.secretAccess(projectId, wantBackend, haveBackend); + }); +}); diff --git a/src/test/deploy/functions/ensureCloudBuildEnabled.ts b/src/test/deploy/functions/ensureCloudBuildEnabled.ts deleted file mode 100644 index 8f50ece8794..00000000000 --- a/src/test/deploy/functions/ensureCloudBuildEnabled.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { expect } from "chai"; -import * as sinon from "sinon"; -import * as nock from "nock"; - -import { FirebaseError } from "../../../error"; -import { logger } from "../../../logger"; -import { configstore } from "../../../configstore"; -import { ensureCloudBuildEnabled } from "../../../deploy/functions/ensureCloudBuildEnabled"; -import { POLL_SETTINGS } from "../../../ensureApiEnabled"; -import * as api from "../../../api"; - -describe("ensureCloudBuildEnabled()", () => { - let restoreInterval: number; - before(() => { - restoreInterval = POLL_SETTINGS.pollInterval; - POLL_SETTINGS.pollInterval = 0; - }); - after(() => { - POLL_SETTINGS.pollInterval = restoreInterval; - }); - - let sandbox: sinon.SinonSandbox; - let logStub: sinon.SinonStub | null; - beforeEach(() => { - sandbox = sinon.createSandbox(); - logStub = sandbox.stub(logger, "warn"); - }); - - afterEach(() => { - expect(nock.isDone()).to.be.true; - sandbox.restore(); - timeStub = null; - logStub = null; - }); - - function mockServiceCheck(isEnabled = false): void { - nock(api.serviceUsageOrigin) - .get("/v1/projects/test-project/services/cloudbuild.googleapis.com") - .reply(200, { state: isEnabled ? "ENABLED" : "DISABLED" }); - } - - function mockServiceEnableSuccess(): void { - nock(api.serviceUsageOrigin) - .post("/v1/projects/test-project/services/cloudbuild.googleapis.com:enable") - .reply(200, {}); - } - - function mockServiceEnableBillingError(): void { - nock(api.serviceUsageOrigin) - .post("/v1/projects/test-project/services/cloudbuild.googleapis.com:enable") - .reply(403, { - error: { - details: [{ violations: [{ type: "serviceusage/billing-enabled" }] }], - }, - }); - } - - function mockServiceEnablePermissionError(): void { - nock(api.serviceUsageOrigin) - .post("/v1/projects/test-project/services/cloudbuild.googleapis.com:enable") - .reply(403, { - error: { - status: "PERMISSION_DENIED", - }, - }); - } - - let timeStub: sinon.SinonStub | null; - function stubTimes(warnAfter: number, errorAfter: number): void { - timeStub = sandbox.stub(configstore, "get"); - timeStub.withArgs("motd.cloudBuildWarnAfter").returns(warnAfter); - timeStub.withArgs("motd.cloudBuildErrorAfter").returns(errorAfter); - } - - describe("with cloudbuild service enabled", () => { - beforeEach(() => { - mockServiceCheck(true); - }); - - it("should succeed", async () => { - stubTimes(Date.now() - 10000, Date.now() - 5000); - - await expect(ensureCloudBuildEnabled("test-project")).to.eventually.be.fulfilled; - expect(logStub?.callCount).to.eq(0); - }); - }); - - describe("with cloudbuild service disabled, but enabling succeeds", () => { - beforeEach(() => { - mockServiceCheck(false); - mockServiceEnableSuccess(); - mockServiceCheck(true); - }); - - it("should succeed", async () => { - stubTimes(Date.now() - 10000, Date.now() - 5000); - - await expect(ensureCloudBuildEnabled("test-project")).to.eventually.be.fulfilled; - expect(logStub?.callCount).to.eq(1); // enabling an api logs a warning - }); - }); - - describe("with cloudbuild service disabled, but enabling fails with billing error", () => { - beforeEach(() => { - mockServiceCheck(false); - mockServiceEnableBillingError(); - }); - - it("should error", async () => { - stubTimes(Date.now() - 10000, Date.now() - 5000); - - await expect(ensureCloudBuildEnabled("test-project")).to.eventually.be.rejectedWith( - FirebaseError, - /must be on the Blaze \(pay-as-you-go\) plan to complete this command/ - ); - }); - }); - - describe("with cloudbuild service disabled, but enabling fails with permission error", () => { - beforeEach(() => { - mockServiceCheck(false); - mockServiceEnablePermissionError(); - }); - - it("should error", async () => { - stubTimes(Date.now() - 10000, Date.now() - 5000); - - await expect(ensureCloudBuildEnabled("test-project")).to.eventually.be.rejectedWith( - FirebaseError, - /Please ask a project owner to visit the following URL to enable Cloud Build/ - ); - }); - }); -}); diff --git a/src/test/deploy/functions/prepare.spec.ts b/src/test/deploy/functions/prepare.spec.ts index b8a15ab946c..24a00941c65 100644 --- a/src/test/deploy/functions/prepare.spec.ts +++ b/src/test/deploy/functions/prepare.spec.ts @@ -4,21 +4,21 @@ import * as backend from "../../../deploy/functions/backend"; import * as prepare from "../../../deploy/functions/prepare"; describe("prepare", () => { - describe("inferDetailsFromExisting", () => { - const ENDPOINT_BASE: Omit = { - platform: "gcfv2", - id: "id", - region: "region", - project: "project", - entryPoint: "entry", - runtime: "nodejs16", - }; - - const ENDPOINT: backend.Endpoint = { - ...ENDPOINT_BASE, - httpsTrigger: {}, - }; + const ENDPOINT_BASE: Omit = { + platform: "gcfv2", + id: "id", + region: "region", + project: "project", + entryPoint: "entry", + runtime: "nodejs16", + }; + + const ENDPOINT: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + }; + describe("inferDetailsFromExisting", () => { it("merges env vars if .env is not used", () => { const oldE = { ...ENDPOINT, diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index 675c8157de8..1cca607f427 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -308,4 +308,28 @@ describe("addResourcesToBackend", () => { expect(result).to.deep.equal(expected); }); + + it("should parse secret", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + httpsTrigger: {}, + secrets: ["MY_SECRET"], + }; + + const expected: backend.Backend = backend.of({ + ...BASIC_ENDPOINT, + httpsTrigger: {}, + secretEnvironmentVariables: [ + { + projectId: "project", + secret: "MY_SECRET", + key: "MY_SECRET", + }, + ], + }); + + const result = backend.empty(); + parseTriggers.addResourcesToBackend("project", "nodejs16", trigger, result); + expect(result).to.deep.equal(expected); + }); }); diff --git a/src/test/deploy/functions/runtimes/node/validate.spec.ts b/src/test/deploy/functions/runtimes/node/validate.spec.ts index dba30e61e5b..ab1b9c410e7 100644 --- a/src/test/deploy/functions/runtimes/node/validate.spec.ts +++ b/src/test/deploy/functions/runtimes/node/validate.spec.ts @@ -2,7 +2,6 @@ import { expect } from "chai"; import * as sinon from "sinon"; import { FirebaseError } from "../../../../../error"; -import { RUNTIME_NOT_SET } from "../../../../../deploy/functions/runtimes/node/parseRuntimeAndValidateSDK"; import * as validate from "../../../../../deploy/functions/runtimes/node/validate"; import * as fsutils from "../../../../../fsutils"; diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index 82d6f583f44..a474f9afe27 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -5,6 +5,7 @@ import { FirebaseError } from "../../../error"; import * as fsutils from "../../../fsutils"; import * as validate from "../../../deploy/functions/validate"; import * as projectPath from "../../../projectPath"; +import * as secretManager from "../../../gcp/secretManager"; import * as backend from "../../../deploy/functions/backend"; describe("validate", () => { @@ -198,4 +199,122 @@ describe("validate", () => { ); }); }); + + describe("secretsAreValid", () => { + const project = "project"; + + const ENDPOINT_BASE: Omit = { + project, + platform: "gcfv2", + id: "id", + region: "region", + entryPoint: "entry", + runtime: "nodejs16", + }; + const ENDPOINT: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + }; + + const secret: secretManager.Secret = { projectId: project, name: "MY_SECRET" }; + + let secretVersionStub: sinon.SinonStub; + + beforeEach(() => { + secretVersionStub = sinon.stub(secretManager, "getSecretVersion").rejects("Unexpected call"); + }); + + afterEach(() => { + secretVersionStub.restore(); + }); + + it("passes validation with empty backend", () => { + expect(validate.secretsAreValid(project, backend.empty())).to.not.be.rejected; + }); + + it("passes validation with no secret env vars", () => { + const b = backend.of({ + ...ENDPOINT, + platform: "gcfv2", + }); + expect(validate.secretsAreValid(project, b)).to.not.be.rejected; + }); + + it("fails validation given endpoint with secrets targeting unsupported platform", () => { + const b = backend.of({ + ...ENDPOINT, + platform: "gcfv2", + secretEnvironmentVariables: [ + { + projectId: project, + secret: "MY_SECRET", + key: "MY_SECRET", + }, + ], + }); + + expect(validate.secretsAreValid(project, b)).to.be.rejectedWith(FirebaseError); + }); + + it("fails validation given non-existent secret version", () => { + secretVersionStub.rejects({ reason: "Secret version does not exist" }); + + const b = backend.of({ + ...ENDPOINT, + platform: "gcfv1", + secretEnvironmentVariables: [ + { + projectId: project, + secret: "MY_SECRET", + key: "MY_SECRET", + }, + ], + }); + expect(validate.secretsAreValid(project, b)).to.be.rejectedWith(FirebaseError); + }); + + it("fails validation given disabled secret version", () => { + secretVersionStub.resolves({ + secret, + versionId: "1", + state: "DISABLED", + }); + + const b = backend.of({ + ...ENDPOINT, + platform: "gcfv1", + secretEnvironmentVariables: [ + { + projectId: project, + secret: "MY_SECRET", + key: "MY_SECRET", + }, + ], + }); + expect(validate.secretsAreValid(project, b)).to.be.rejected; + }); + + it("passes validation and resolves latest version given valid secret config", async () => { + secretVersionStub.withArgs(project, secret.name, "latest").resolves({ + secret, + versionId: "2", + state: "ENABLED", + }); + + const b = backend.of({ + ...ENDPOINT, + platform: "gcfv1", + secretEnvironmentVariables: [ + { + projectId: project, + secret: "MY_SECRET", + key: "MY_SECRET", + }, + ], + }); + + await validate.secretsAreValid(project, b); + expect(backend.allEndpoints(b)[0].secretEnvironmentVariables![0].version).to.equal("2"); + }); + }); }); diff --git a/src/test/emulators/fixtures.ts b/src/test/emulators/fixtures.ts index c1ed615257f..b2e99928425 100644 --- a/src/test/emulators/fixtures.ts +++ b/src/test/emulators/fixtures.ts @@ -6,17 +6,6 @@ export const TIMEOUT_MED = 5000; export const MODULE_ROOT = findModuleRoot("firebase-tools", __dirname); export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = { onCreate: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, proto: { data: { value: { @@ -41,22 +30,8 @@ export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = }, }, }, - triggerId: "region-function_id", - targetName: "function_id", - projectId: "fake-project-id", }, onWrite: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, proto: { data: { value: { @@ -81,22 +56,8 @@ export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = }, }, }, - triggerId: "region-function_id", - targetName: "function_id", - projectId: "fake-project-id", }, onDelete: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, proto: { data: { oldValue: { @@ -121,22 +82,8 @@ export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = }, }, }, - triggerId: "region-function_id", - targetName: "function_id", - projectId: "fake-project-id", }, onUpdate: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, proto: { data: { oldValue: { @@ -173,25 +120,9 @@ export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = timestamp: "2019-05-15T16:21:15.148831Z", }, }, - triggerId: "region-function_id", - targetName: "function_id", - projectId: "fake-project-id", }, onRequest: { - adminSdkConfig: { - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", - }, - emulators: { - firestore: { - host: "localhost", - port: 8080, - }, - }, - cwd: MODULE_ROOT, - triggerId: "region-function_id", - targetName: "function_id", - projectId: "fake-project-id", + proto: {}, }, }; diff --git a/src/test/emulators/functionsRuntimeWorker.spec.ts b/src/test/emulators/functionsRuntimeWorker.spec.ts index edc7c7b68ee..3a58e96710a 100644 --- a/src/test/emulators/functionsRuntimeWorker.spec.ts +++ b/src/test/emulators/functionsRuntimeWorker.spec.ts @@ -21,6 +21,7 @@ class MockRuntimeInstance implements FunctionsRuntimeInstance { metadata: { [key: string]: any } = {}; events: EventEmitter = new EventEmitter(); exit: Promise; + cwd = "/home/users/dir"; constructor(private success: boolean) { this.exit = new Promise((res) => { @@ -90,13 +91,8 @@ class WorkerStateCounter { class MockRuntimeBundle implements FunctionsRuntimeBundle { projectId = "project-1234"; - cwd = "/home/users/dir"; emulators = {}; - adminSdkConfig = { - projectId: "project-1234", - datbaseURL: "https://project-1234-default-rtdb.firebaseio.com", - storageBucket: "project-1234.appspot.com", - }; + proto = {}; constructor(public triggerId: string, public targetName: string) {} } diff --git a/src/test/extensions/secretUtils.spec.ts b/src/test/extensions/secretUtils.spec.ts index 10b44fceffa..97b8422844a 100644 --- a/src/test/extensions/secretUtils.spec.ts +++ b/src/test/extensions/secretUtils.spec.ts @@ -60,13 +60,13 @@ describe("secretsUtils", () => { describe("getManagedSecrets", () => { it("only returns secrets that have labels set", async () => { nock(api.secretManagerOrigin) - .get(`/v1beta1/projects/${PROJECT_ID}/secrets/secret1`) + .get(`/v1/projects/${PROJECT_ID}/secrets/secret1`) .reply(200, { name: `projects/${PROJECT_ID}/secrets/secret1`, labels: { "firebase-extensions-managed": "true" }, }); nock(api.secretManagerOrigin) - .get(`/v1beta1/projects/${PROJECT_ID}/secrets/secret2`) + .get(`/v1/projects/${PROJECT_ID}/secrets/secret2`) .reply(200, { name: `projects/${PROJECT_ID}/secrets/secret2`, }); // no labels diff --git a/src/test/functions/env.spec.ts b/src/test/functions/env.spec.ts index 8679bfee782..a489f87d962 100644 --- a/src/test/functions/env.spec.ts +++ b/src/test/functions/env.spec.ts @@ -5,7 +5,6 @@ import { sync as rimraf } from "rimraf"; import { expect } from "chai"; import * as env from "../../functions/env"; -import { previews } from "../../previews"; describe("functions/env", () => { describe("parse", () => { @@ -239,6 +238,10 @@ FOO=foo expect(() => { env.validateKey("FIREBASE_FOOBAR"); }).to.throw("starts with a reserved prefix"); + + expect(() => { + env.validateKey("EXT_INSTANCE_ID"); + }).to.throw("starts with a reserved prefix"); }); }); @@ -248,17 +251,12 @@ FOO=foo fs.writeFileSync(path.join(sourceDir, filename), data); } }; - const projectInfo = { projectId: "my-project", projectAlias: "dev" }; + const projectInfo: Omit = { + projectId: "my-project", + projectAlias: "dev", + }; let tmpdir: string; - before(() => { - previews.dotenv = true; - }); - - after(() => { - previews.dotenv = false; - }); - beforeEach(() => { tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "test")); }); diff --git a/src/test/functions/secrets.spec.ts b/src/test/functions/secrets.spec.ts new file mode 100644 index 00000000000..152b9de31ca --- /dev/null +++ b/src/test/functions/secrets.spec.ts @@ -0,0 +1,287 @@ +import * as sinon from "sinon"; +import { expect } from "chai"; + +import * as secretManager from "../../gcp/secretManager"; +import * as secrets from "../../functions/secrets"; +import * as utils from "../../utils"; +import * as prompt from "../../prompt"; +import * as backend from "../../deploy/functions/backend"; +import { Options } from "../../options"; +import { FirebaseError } from "../../error"; + +describe("functions/secret", () => { + const options = { force: false } as Options; + + describe("ensureValidKey", () => { + let warnStub: sinon.SinonStub; + let promptStub: sinon.SinonStub; + + beforeEach(() => { + warnStub = sinon.stub(utils, "logWarning").resolves(undefined); + promptStub = sinon.stub(prompt, "promptOnce").resolves(true); + }); + + afterEach(() => { + warnStub.restore(); + promptStub.restore(); + }); + + it("returns the original key if it follows convention", async () => { + expect(await secrets.ensureValidKey("MY_KEY", options)).to.equal("MY_KEY"); + expect(warnStub).to.not.have.been.called; + }); + + it("returns the transformed key (with warning) if with dashses", async () => { + expect(await secrets.ensureValidKey("MY-KEY", options)).to.equal("MY_KEY"); + expect(warnStub).to.have.been.calledOnce; + }); + + it("returns the transformed key (with warning) if with lower cases", async () => { + expect(await secrets.ensureValidKey("my_key", options)).to.equal("MY_KEY"); + expect(warnStub).to.have.been.calledOnce; + }); + + it("returns the transformed key (with warning) if camelCased", async () => { + expect(await secrets.ensureValidKey("myKey", options)).to.equal("MY_KEY"); + expect(warnStub).to.have.been.calledOnce; + }); + + it("throws error if given non-conventional key w/ forced option", () => { + expect(secrets.ensureValidKey("throwError", { ...options, force: true })).to.be.rejectedWith( + FirebaseError + ); + }); + + it("throws error if given reserved key", () => { + expect(secrets.ensureValidKey("FIREBASE_CONFIG", options)).to.be.rejectedWith(FirebaseError); + }); + }); + + describe("ensureSecret", () => { + const secret: secretManager.Secret = { + projectId: "project-id", + name: "MY_SECRET", + labels: secrets.labels(), + }; + + let sandbox: sinon.SinonSandbox; + let getStub: sinon.SinonStub; + let createStub: sinon.SinonStub; + let patchStub: sinon.SinonStub; + let promptStub: sinon.SinonStub; + let warnStub: sinon.SinonStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + + getStub = sandbox.stub(secretManager, "getSecret").rejects("Unexpected call"); + createStub = sandbox.stub(secretManager, "createSecret").rejects("Unexpected call"); + patchStub = sandbox.stub(secretManager, "patchSecret").rejects("Unexpected call"); + + promptStub = sandbox.stub(prompt, "promptOnce").resolves(true); + warnStub = sandbox.stub(utils, "logWarning").resolves(undefined); + }); + + afterEach(() => { + sandbox.verifyAndRestore(); + }); + + it("returns existing secret if we have one", async () => { + getStub.resolves(secret); + + await expect( + secrets.ensureSecret("project-id", "MY_SECRET", options) + ).to.eventually.deep.equal(secret); + expect(getStub).to.have.been.calledOnce; + }); + + it("prompt user to have Firebase manage the secret if not managed by Firebase", async () => { + getStub.resolves({ ...secret, labels: [] }); + patchStub.resolves(secret); + + await expect( + secrets.ensureSecret("project-id", "MY_SECRET", options) + ).to.eventually.deep.equal(secret); + expect(warnStub).to.have.been.calledOnce; + expect(promptStub).to.have.been.calledOnce; + }); + + it("creates a new secret if it doesn't exists", async () => { + getStub.rejects({ status: 404 }); + createStub.resolves(secret); + + await expect( + secrets.ensureSecret("project-id", "MY_SECRET", options) + ).to.eventually.deep.equal(secret); + }); + + it("throws if it cannot reach Secret Manager", async () => { + getStub.rejects({ status: 500 }); + + await expect(secrets.ensureSecret("project-id", "MY_SECRET", options)).to.eventually.be + .rejected; + }); + }); + + describe("of", () => { + const ENDPOINT = { + id: "id", + region: "region", + project: "project", + entryPoint: "id", + runtime: "nodejs16", + platform: "gcfv1" as const, + httpsTrigger: {}, + }; + + function makeSecret(name: string, version?: string): backend.SecretEnvVar { + return { + projectId: "project", + key: name, + secret: name, + version: version ?? "1", + }; + } + + it("returns empty list given empty list", () => { + expect(secrets.of([])).to.be.empty; + }); + + it("collects all secret environment variables", () => { + const secret1 = makeSecret("SECRET1"); + const secret2 = makeSecret("SECRET2"); + const secret3 = makeSecret("SECRET3"); + + const endpoints: backend.Endpoint[] = [ + { + ...ENDPOINT, + secretEnvironmentVariables: [secret1], + }, + ENDPOINT, + { + ...ENDPOINT, + secretEnvironmentVariables: [secret2, secret3], + }, + ]; + expect(secrets.of(endpoints)).to.have.members([secret1, secret2, secret3]); + expect(secrets.of(endpoints)).to.have.length(3); + }); + }); + + describe("pruneSecrets", () => { + const ENDPOINT = { + id: "id", + region: "region", + project: "project", + entryPoint: "id", + runtime: "nodejs16", + platform: "gcfv1" as const, + httpsTrigger: {}, + }; + + let listSecretsStub: sinon.SinonStub; + let listSecretVersionsStub: sinon.SinonStub; + let getSecretVersionStub: sinon.SinonStub; + + const secret1: secretManager.Secret = { + projectId: "project", + name: "MY_SECRET1", + }; + const secretVersion11: secretManager.SecretVersion = { + secret: secret1, + versionId: "1", + }; + const secretVersion12: secretManager.SecretVersion = { + secret: secret1, + versionId: "2", + }; + + const secret2: secretManager.Secret = { + projectId: "project", + name: "MY_SECRET2", + }; + const secretVersion21: secretManager.SecretVersion = { + secret: secret2, + versionId: "1", + }; + + function toSecretEnvVar(sv: secretManager.SecretVersion): backend.SecretEnvVar { + return { + projectId: "project", + version: sv.versionId, + secret: sv.secret.name, + key: sv.secret.name, + }; + } + + beforeEach(() => { + listSecretsStub = sinon.stub(secretManager, "listSecrets").rejects("Unexpected call"); + listSecretVersionsStub = sinon + .stub(secretManager, "listSecretVersions") + .rejects("Unexpected call"); + getSecretVersionStub = sinon + .stub(secretManager, "getSecretVersion") + .rejects("Unexpected call"); + }); + + afterEach(() => { + listSecretsStub.restore(); + listSecretVersionsStub.restore(); + getSecretVersionStub.restore(); + }); + + it("returns nothing if unused", async () => { + listSecretsStub.resolves([]); + + await expect( + secrets.pruneSecrets({ projectId: "project", projectNumber: "12345" }, []) + ).to.eventually.deep.equal([]); + }); + + it("returns all secrets given no endpoints", async () => { + listSecretsStub.resolves([secret1, secret2]); + listSecretVersionsStub.onFirstCall().resolves([secretVersion11, secretVersion12]); + listSecretVersionsStub.onSecondCall().resolves([secretVersion21]); + + const pruned = await secrets.pruneSecrets( + { projectId: "project", projectNumber: "12345" }, + [] + ); + + expect(pruned).to.have.deep.members( + [secretVersion11, secretVersion12, secretVersion21].map(toSecretEnvVar) + ); + expect(pruned).to.have.length(3); + }); + + it("does not include secret version in use", async () => { + listSecretsStub.resolves([secret1, secret2]); + listSecretVersionsStub.onFirstCall().resolves([secretVersion11, secretVersion12]); + listSecretVersionsStub.onSecondCall().resolves([secretVersion21]); + + const pruned = await secrets.pruneSecrets({ projectId: "project", projectNumber: "12345" }, [ + { ...ENDPOINT, secretEnvironmentVariables: [toSecretEnvVar(secretVersion12)] }, + ]); + + expect(pruned).to.have.deep.members([secretVersion11, secretVersion21].map(toSecretEnvVar)); + expect(pruned).to.have.length(2); + }); + + it("resolves 'latest' secrets and properly prunes it", async () => { + listSecretsStub.resolves([secret1, secret2]); + listSecretVersionsStub.onFirstCall().resolves([secretVersion11, secretVersion12]); + listSecretVersionsStub.onSecondCall().resolves([secretVersion21]); + getSecretVersionStub.resolves(secretVersion12); + + const pruned = await secrets.pruneSecrets({ projectId: "project", projectNumber: "12345" }, [ + { + ...ENDPOINT, + secretEnvironmentVariables: [{ ...toSecretEnvVar(secretVersion12), version: "latest" }], + }, + ]); + + expect(pruned).to.have.deep.members([secretVersion11, secretVersion21].map(toSecretEnvVar)); + expect(pruned).to.have.length(2); + }); + }); +}); diff --git a/src/test/gcp/secretManager.spec.ts b/src/test/gcp/secretManager.spec.ts new file mode 100644 index 00000000000..cb0aea38d94 --- /dev/null +++ b/src/test/gcp/secretManager.spec.ts @@ -0,0 +1,131 @@ +import * as sinon from "sinon"; +import { expect } from "chai"; + +import * as iam from "../../gcp/iam"; +import * as secretManager from "../../gcp/secretManager"; +import { FirebaseError } from "../../error"; +import { ensureServiceAgentRole } from "../../gcp/secretManager"; + +describe("secretManager", () => { + describe("parseSecretResourceName", () => { + it("parses valid secret resource name", () => { + expect( + secretManager.parseSecretResourceName("projects/my-project/secrets/my-secret") + ).to.deep.equal({ projectId: "my-project", name: "my-secret" }); + }); + + it("throws given invalid resource name", () => { + expect(() => secretManager.parseSecretResourceName("foo/bar")).to.throw(FirebaseError); + }); + + it("throws given incomplete resource name", () => { + expect(() => secretManager.parseSecretResourceName("projects/my-project")).to.throw( + FirebaseError + ); + }); + + it("parse secret version resource name", () => { + expect( + secretManager.parseSecretResourceName("projects/my-project/secrets/my-secret/versions/8") + ).to.deep.equal({ projectId: "my-project", name: "my-secret" }); + }); + }); + + describe("parseSecretVersionResourceName", () => { + it("parses valid secret resource name", () => { + expect( + secretManager.parseSecretVersionResourceName( + "projects/my-project/secrets/my-secret/versions/7" + ) + ).to.deep.equal({ secret: { projectId: "my-project", name: "my-secret" }, versionId: "7" }); + }); + + it("throws given invalid resource name", () => { + expect(() => secretManager.parseSecretVersionResourceName("foo/bar")).to.throw(FirebaseError); + }); + + it("throws given incomplete resource name", () => { + expect(() => secretManager.parseSecretVersionResourceName("projects/my-project")).to.throw( + FirebaseError + ); + }); + + it("throws given secret resource name", () => { + expect(() => + secretManager.parseSecretVersionResourceName("projects/my-project/secrets/my-secret") + ).to.throw(FirebaseError); + }); + }); + + describe("ensureServiceAgentRole", () => { + const projectId = "my-project"; + const secret: secretManager.Secret = { projectId, name: "my-secret" }; + const role = "test-role"; + + let getIamPolicyStub: sinon.SinonStub; + let setIamPolicyStub: sinon.SinonStub; + + beforeEach(() => { + getIamPolicyStub = sinon.stub(secretManager, "getIamPolicy").rejects("Unexpected call"); + setIamPolicyStub = sinon.stub(secretManager, "setIamPolicy").rejects("Unexpected call"); + }); + + afterEach(() => { + getIamPolicyStub.restore(); + setIamPolicyStub.restore(); + }); + + function setupStubs(existing: iam.Binding[], expected?: iam.Binding[]) { + getIamPolicyStub.withArgs(secret).resolves({ bindings: existing }); + if (expected) { + setIamPolicyStub.withArgs(secret, expected).resolves({ body: { bindings: expected } }); + } + } + + it("adds new binding for each member", async () => { + const existing: iam.Binding[] = []; + const expected: iam.Binding[] = [ + { role, members: ["serviceAccount:1@foobar.com", "serviceAccount:2@foobar.com"] }, + ]; + + setupStubs(existing, expected); + await ensureServiceAgentRole(secret, ["1@foobar.com", "2@foobar.com"], role); + }); + + it("adds bindings only for missing members", async () => { + const existing: iam.Binding[] = [{ role, members: ["serviceAccount:1@foobar.com"] }]; + const expected: iam.Binding[] = [ + { role, members: ["serviceAccount:1@foobar.com", "serviceAccount:2@foobar.com"] }, + ]; + + setupStubs(existing, expected); + await ensureServiceAgentRole(secret, ["1@foobar.com", "2@foobar.com"], role); + }); + + it("keeps bindings that already exists", async () => { + const existing: iam.Binding[] = [ + { role: "another-role", members: ["serviceAccount:3@foobar.com"] }, + ]; + const expected: iam.Binding[] = [ + { + role: "another-role", + members: ["serviceAccount:3@foobar.com"], + }, + { + role, + members: ["serviceAccount:1@foobar.com", "serviceAccount:2@foobar.com"], + }, + ]; + + setupStubs(existing, expected); + await ensureServiceAgentRole(secret, ["1@foobar.com", "2@foobar.com"], role); + }); + + it("does nothing if the binding already exists", async () => { + const existing: iam.Binding[] = [{ role, members: ["serviceAccount:1@foobar.com"] }]; + + setupStubs(existing); + await ensureServiceAgentRole(secret, ["1@foobar.com"], role); + }); + }); +}); diff --git a/src/utils.ts b/src/utils.ts index 732157dcb76..34e06f36589 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,6 +19,7 @@ import { Socket } from "net"; const IS_WINDOWS = process.platform === "win32"; const SUCCESS_CHAR = IS_WINDOWS ? "+" : "✔"; const WARNING_CHAR = IS_WINDOWS ? "!" : "⚠"; +const ERROR_CHAR = IS_WINDOWS ? "!!" : "⬢"; const THIRTY_DAYS_IN_MILLISECONDS = 30 * 24 * 60 * 60 * 1000; export const envOverrides: string[] = []; @@ -191,6 +192,18 @@ export function logLabeledWarning( logger[type](clc.yellow.bold(`${WARNING_CHAR} ${label}:`), message, data); } +/** + * Log an rror statement with a red bullet at the start of the line. + */ +export function logLabeledError( + label: string, + message: string, + type: LogLevel = "error", + data: LogDataOrUndefined = undefined +): void { + logger[type](clc.red.bold(`${ERROR_CHAR} ${label}:`), message, data); +} + /** * Return a promise that rejects with a FirebaseError. */ From 5f97f8e340a4c89a80726fa144d0505d4e5bd11c Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 10 Feb 2022 23:37:40 +0000 Subject: [PATCH 0074/1699] 10.2.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 841fc6f944d..f1a998fae5d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.1.5", + "version": "10.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.1.5", + "version": "10.2.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 9b90ea50939..182087dc028 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.1.5", + "version": "10.2.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 8d452949e01d0cacbbbb224fc7dd03945558d1dc Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 10 Feb 2022 23:38:07 +0000 Subject: [PATCH 0075/1699] [firebase-release] Removed change log and reset repo after 10.2.0 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a336b2eae3..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Improves experience for `firebase login --no-localhost`. -- Add support for specifying environment variable of CF3 function using dotenv. From a7d8d4ce5fc43fc402c5532820d7d7765a95c77c Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 11 Feb 2022 14:51:57 -0800 Subject: [PATCH 0076/1699] Fix an issue where ext:list was not displaying any info about instances (#4156) * Fix an issue where ext:list was not displaying any info about instances * add changelog --- CHANGELOG.md | 1 + src/extensions/listExtensions.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..9e7a500e055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes an issue where ext:list was not printing out information about installed Extension instances. diff --git a/src/extensions/listExtensions.ts b/src/extensions/listExtensions.ts index 2e6d3d29c97..065291a0332 100644 --- a/src/extensions/listExtensions.ts +++ b/src/extensions/listExtensions.ts @@ -57,5 +57,6 @@ export async function listExtensions(projectId: string): Promise { }); utils.logLabeledBullet(logPrefix, `list of extensions installed in ${clc.bold(projectId)}:`); + logger.info(table.toString()); return formatted; } From c07e9ff6d8e5b8b4cc66e8d91286e7de807e68ae Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 14 Feb 2022 11:19:01 -0600 Subject: [PATCH 0077/1699] update dependencies for audit issues (#4161) --- npm-shrinkwrap.json | 145 +++++++++++++++++++++++++++++++++++++------- package.json | 4 +- 2 files changed, 124 insertions(+), 25 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f1a998fae5d..a4b299ec0c2 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -43,7 +43,7 @@ "mime": "^2.5.2", "minimatch": "^3.0.4", "morgan": "^1.10.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.7", "open": "^6.3.0", "ora": "^5.4.1", "portfinder": "^1.0.23", @@ -99,7 +99,7 @@ "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", "@types/node": "^12.20.39", - "@types/node-fetch": "^2.5.7", + "@types/node-fetch": "^2.5.12", "@types/progress": "^2.0.3", "@types/puppeteer": "^5.4.2", "@types/request": "^2.48.1", @@ -835,6 +835,15 @@ "tslib": "^1.11.1" } }, + "node_modules/@firebase/firestore/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@firebase/functions": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.5.1.tgz", @@ -877,6 +886,15 @@ "tslib": "^1.11.1" } }, + "node_modules/@firebase/functions/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@firebase/installations": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.17.tgz", @@ -2120,9 +2138,9 @@ "integrity": "sha512-U7PMwkDmc3bnL0e4U8oA0POpi1vfsYDc+DEUS2+rPxm9NlLcW1dBa5JcRhO633PoPUcCSWMNXrMsqhmAVEo+IQ==" }, "node_modules/@types/node-fetch": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", - "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", "dev": true, "dependencies": { "@types/node": "*", @@ -6780,11 +6798,11 @@ } }, "node_modules/google-p12-pem": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", - "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", "dependencies": { - "node-forge": "^0.10.0" + "node-forge": "^1.0.0" }, "bin": { "gp12-pem": "build/src/bin/gp12-pem.js" @@ -6793,6 +6811,14 @@ "node": ">=10" } }, + "node_modules/google-p12-pem/node_modules/node-forge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", + "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -9164,11 +9190,22 @@ } }, "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "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-fetch-h2": { @@ -9187,6 +9224,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "dev": true, "engines": { "node": ">= 6.0.0" } @@ -12379,6 +12417,11 @@ "lodash": "^4.17.10" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "node_modules/traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -13062,6 +13105,11 @@ "defaults": "^1.0.3" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, "node_modules/websocket-driver": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", @@ -13091,6 +13139,15 @@ "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", "dev": true }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -14048,6 +14105,12 @@ "requires": { "tslib": "^1.11.1" } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true } } }, @@ -14089,6 +14152,12 @@ "requires": { "tslib": "^1.11.1" } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true } } }, @@ -15189,9 +15258,9 @@ "integrity": "sha512-U7PMwkDmc3bnL0e4U8oA0POpi1vfsYDc+DEUS2+rPxm9NlLcW1dBa5JcRhO633PoPUcCSWMNXrMsqhmAVEo+IQ==" }, "@types/node-fetch": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", - "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", "dev": true, "requires": { "@types/node": "*", @@ -18861,11 +18930,18 @@ } }, "google-p12-pem": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", - "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", "requires": { - "node-forge": "^0.10.0" + "node-forge": "^1.0.0" + }, + "dependencies": { + "node-forge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", + "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==" + } } }, "got": { @@ -20757,9 +20833,12 @@ } }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } }, "node-fetch-h2": { "version": "2.3.0", @@ -20773,7 +20852,8 @@ "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "dev": true }, "node-gyp": { "version": "8.4.1", @@ -23281,6 +23361,11 @@ "lodash": "^4.17.10" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -23783,6 +23868,11 @@ "defaults": "^1.0.3" } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, "websocket-driver": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", @@ -23806,6 +23896,15 @@ "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", "dev": true }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 182087dc028..f1dc8c52e03 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "mime": "^2.5.2", "minimatch": "^3.0.4", "morgan": "^1.10.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.7", "open": "^6.3.0", "ora": "^5.4.1", "portfinder": "^1.0.23", @@ -172,7 +172,7 @@ "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", "@types/node": "^12.20.39", - "@types/node-fetch": "^2.5.7", + "@types/node-fetch": "^2.5.12", "@types/progress": "^2.0.3", "@types/puppeteer": "^5.4.2", "@types/request": "^2.48.1", From eca1b4dc7392657b35a28149f74b2d15b4830951 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 15 Feb 2022 09:02:41 -0800 Subject: [PATCH 0078/1699] Load runtime config when loading triggers in the Functions Emulator (#4162) Addresses https://github.com/firebase/firebase-tools/issues/4158. I've tried to find a way to add a test for this usecase, but there isn't a framework where I can easily hook in a change like this without big investments. I'm leaning towards releasing this bugfix as is, and finding a less urgent time to add regression test. --- CHANGELOG.md | 1 + src/emulator/functionsEmulator.ts | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e7a500e055..e71c935bbae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Fixes an issue where ext:list was not printing out information about installed Extension instances. +- Fixes issue where Functions Emulator crashed when parsing triggers if accessing functions config values. diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 5fb2eda2faf..4c58bfe26cf 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -451,6 +451,7 @@ export class FunctionsEmulator implements EmulatorInstance { if (emulatableBackend.predefinedTriggers) { triggerDefinitions = emulatedFunctionsByRegion(emulatableBackend.predefinedTriggers); } else { + const runtimeConfig = this.getRuntimeConfig(emulatableBackend); const runtimeDelegate = await getRuntimeDelegate({ projectId: this.args.projectId, projectDir: this.args.projectDir, @@ -462,7 +463,7 @@ export class FunctionsEmulator implements EmulatorInstance { await runtimeDelegate.build(); logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`); const discoveredBackend = await runtimeDelegate.discoverSpec( - {}, + runtimeConfig, // Don't include user envs when parsing triggers. { ...this.getSystemEnvs(), @@ -871,6 +872,17 @@ export class FunctionsEmulator implements EmulatorInstance { return process.execPath; } + getRuntimeConfig(backend: EmulatableBackend): Record { + const configPath = `${backend.functionsDir}/.runtimeconfig.json`; + try { + const configContent = fs.readFileSync(configPath, "utf8"); + return JSON.parse(configContent.toString()); + } catch (e) { + // This is fine - runtime config is optional. + } + return {}; + } + getUserEnvs(backend: EmulatableBackend): Record { const projectInfo = { functionsSource: backend.functionsDir, From 8a874e1de4d95f8ac5cc22fec61f99783b7803f7 Mon Sep 17 00:00:00 2001 From: Murilo Oliveira de Araujo Date: Tue, 15 Feb 2022 14:31:05 -0300 Subject: [PATCH 0079/1699] Helping prevention of accidental directory exclusion when using the --export-on-exit option (#4127) * Adding condition to prevent the user from setting the export data folder to the CWD, inflicting in directory exclusion after stopping the emulators * Added another test case when the user sets the --export-on-exit implicitly via the set of --import * Improved the message presented to user Fixed a cenario where the user could ingress a pattern of cwd and it would fail incorrectly Co-authored-by: Yuchen Shi --- src/emulator/commandUtils.ts | 6 +++ src/test/emulators/commandUtils.spec.ts | 61 ++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index 992995d053e..0ce2c66231c 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -45,6 +45,8 @@ export const EXPORT_ON_EXIT_USAGE_ERROR = `"${FLAG_EXPORT_ON_EXIT_NAME}" must be used with "${FLAG_IMPORT}"` + ` or provide a dir directly to "${FLAG_EXPORT_ON_EXIT}"`; +export const EXPORT_ON_EXIT_CWD_DANGER = `"${FLAG_EXPORT_ON_EXIT_NAME}" must not point to the current directory or parents. Please choose a new/dedicated directory for exports.`; + export const FLAG_UI = "--ui"; export const DESC_UI = "run the Emulator UI"; @@ -203,6 +205,10 @@ export function setExportOnExitOptions(options: any) { // firebase emulators:start --debug --import '' --export-on-exit '' throw new FirebaseError(EXPORT_ON_EXIT_USAGE_ERROR); } + + if (path.resolve(".").startsWith(path.resolve(options.exportOnExit))) { + throw new FirebaseError(EXPORT_ON_EXIT_CWD_DANGER); + } } return; } diff --git a/src/test/emulators/commandUtils.spec.ts b/src/test/emulators/commandUtils.spec.ts index b3336794a86..b3606481466 100644 --- a/src/test/emulators/commandUtils.spec.ts +++ b/src/test/emulators/commandUtils.spec.ts @@ -1,7 +1,9 @@ import * as commandUtils from "../../emulator/commandUtils"; import { expect } from "chai"; import { FirebaseError } from "../../error"; -import { EXPORT_ON_EXIT_USAGE_ERROR } from "../../emulator/commandUtils"; +import { EXPORT_ON_EXIT_USAGE_ERROR, EXPORT_ON_EXIT_CWD_DANGER } from "../../emulator/commandUtils"; +import { delimiter, join, resolve } from "path"; +import * as sinon from "sinon"; describe("commandUtils", () => { const testSetExportOnExitOptions = (options: any): any => { @@ -9,6 +11,63 @@ describe("commandUtils", () => { return options; }; + describe("Mocked path resolve", () => { + const mockCWD = "/a/resolved/path/example"; + const mockDestinationDir = "/path/example"; + + let pathStub: sinon.SinonStub; + beforeEach(() => { + pathStub = sinon.stub(require("path"), "resolve").callsFake((path) => { + return path === "." ? mockCWD : mockDestinationDir; + }); + }); + + afterEach(() => { + pathStub.reset(); + }); + + it("Should not block if destination contains a match to the CWD", () => { + const directoryToAllow = mockDestinationDir; + expect(testSetExportOnExitOptions({ exportOnExit: directoryToAllow }).exportOnExit).to.equal( + directoryToAllow + ); + }); + }); + + /** + * Currently, setting the --export-on-exit as the current CWD can inflict on + * full directory deletion + */ + const directoriesThatShouldFail = [ + ".", // The current dir + "./", // The current dir with / + resolve("."), // An absolute path + resolve(".."), // A folder that directs to the CWD + resolve("../.."), // Another folder that directs to the CWD + ]; + + directoriesThatShouldFail.forEach((dir) => { + it(`Should disallow the user to set the current folder (ex: ${dir}) as --export-on-exit option`, () => { + expect(() => testSetExportOnExitOptions({ exportOnExit: dir })).to.throw( + EXPORT_ON_EXIT_CWD_DANGER + ); + const cwdSubDir = join(dir, "some-dir"); + expect(testSetExportOnExitOptions({ exportOnExit: cwdSubDir }).exportOnExit).to.equal( + cwdSubDir + ); + }); + }); + + it("Should disallow the user to set the current folder via the --import flag", () => { + expect(() => testSetExportOnExitOptions({ import: ".", exportOnExit: true })).to.throw( + EXPORT_ON_EXIT_CWD_DANGER + ); + const cwdSubDir = join(".", "some-dir"); + expect( + testSetExportOnExitOptions({ import: cwdSubDir, exportOnExit: true }).exportOnExit + ).to.equal(cwdSubDir); + }); + it("should validate --export-on-exit options", () => { expect(testSetExportOnExitOptions({ import: "./data" }).exportOnExit).to.be.undefined; expect( From 02941bda04656ebe3445e2dba7780b26d0de18bb Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Tue, 15 Feb 2022 11:09:55 -0800 Subject: [PATCH 0080/1699] Add CHANGELOG entry for #4127. (#4167) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e71c935bbae..720d91f617c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Fixes an issue where ext:list was not printing out information about installed Extension instances. - Fixes issue where Functions Emulator crashed when parsing triggers if accessing functions config values. +- `firebase emulators:start --export-on-exit
` now rejects overwriting the current directory or parents (#4127). From 1be06d3861206042040a33b36c6eeec4a29f17b3 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 16 Feb 2022 13:24:35 -0800 Subject: [PATCH 0081/1699] Fix bug where migration of config-clone command to TS broke the command. (#4173) While migrating the command to TS in https://github.com/firebase/firebase-tools/pull/4025, we introduced a small bug that effectively broke the `functions:config:clone` command that did not pass the `--only` flag. Fixes https://github.com/firebase/firebase-tools/issues/4112 --- CHANGELOG.md | 5 +++-- src/commands/functions-config-clone.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 720d91f617c..15b27a24397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ -- Fixes an issue where ext:list was not printing out information about installed Extension instances. -- Fixes issue where Functions Emulator crashed when parsing triggers if accessing functions config values. +- Fixes an issue where ext:list was not printing out information about installed Extension instances (#4156) +- Fixes issue where Functions Emulator crashed when parsing triggers if accessing functions config values (#4162). - `firebase emulators:start --export-on-exit ` now rejects overwriting the current directory or parents (#4127). +- Fixes broken functions:config:clone command (#4173). diff --git a/src/commands/functions-config-clone.ts b/src/commands/functions-config-clone.ts index 1a4d41af4fa..bd77ef91e8c 100644 --- a/src/commands/functions-config-clone.ts +++ b/src/commands/functions-config-clone.ts @@ -39,7 +39,7 @@ export default new Command("functions:config:clone") throw new FirebaseError("Cannot use both --only and --except at the same time."); } - let only: string[] = []; + let only: string[] | undefined; let except: string[] = []; if (options.only) { only = options.only.split(","); From 0a10d100b21467bea8059a6e4cc09d41150b6c27 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 16 Feb 2022 16:16:43 -0600 Subject: [PATCH 0082/1699] fix issue in auth:import with JSON files (#4172) --- CHANGELOG.md | 1 + src/commands/auth-import.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b27a24397..c1f5dcfea2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Fixes issue where Functions Emulator crashed when parsing triggers if accessing functions config values (#4162). - `firebase emulators:start --export-on-exit ` now rejects overwriting the current directory or parents (#4127). - Fixes broken functions:config:clone command (#4173). +- Fixes issue where `auth:import` would fail when reading a JSON file. (#4157) diff --git a/src/commands/auth-import.ts b/src/commands/auth-import.ts index 2a8e6f44d4a..ef1d9e76882 100644 --- a/src/commands/auth-import.ts +++ b/src/commands/auth-import.ts @@ -114,7 +114,7 @@ module.exports = new Command("auth:import [dataFile]") if (err) { throw new FirebaseError(`Validation Error: ${err}`); } - currentBatch.push(user); + currentBatch.push(value); if (currentBatch.length === MAX_BATCH_SIZE) { batches.push(currentBatch); currentBatch = []; From 02bcc0402a39d2cf335c879933df1e1a15174829 Mon Sep 17 00:00:00 2001 From: Kiana McNellis Date: Wed, 16 Feb 2022 17:02:46 -0600 Subject: [PATCH 0083/1699] Allow specifying the region for Hosting functions rewrites, defaulting to us-central1 if not provided (#3388) * Add region option to Hosting functions rewrites, defaulting to us-central1 if not provided Co-authored-by: Bryan Kendall --- schema/firebase-config.json | 27 +++++++++++ src/deploy/hosting/convertConfig.ts | 5 ++ src/firebaseConfig.ts | 2 +- src/hosting/functionsProxy.ts | 30 +++++++----- src/serve/hosting.ts | 2 +- src/test/deploy/hosting/convertConfig.spec.ts | 13 +++-- src/test/emulators/emulatorServer.spec.ts | 4 +- src/test/emulators/registry.spec.ts | 9 ++-- src/test/fixtures/valid-config/firebase.json | 7 +++ src/test/hosting/functionsProxy.spec.ts | 47 +++++++++++++++++-- 10 files changed, 119 insertions(+), 27 deletions(-) diff --git a/schema/firebase-config.json b/schema/firebase-config.json index d82b69a9d9b..0727f46fb9e 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -622,6 +622,9 @@ }, "glob": { "type": "string" + }, + "region": { + "type": "string" } }, "required": [ @@ -696,6 +699,9 @@ "function": { "type": "string" }, + "region": { + "type": "string" + }, "source": { "type": "string" } @@ -774,6 +780,9 @@ }, "regex": { "type": "string" + }, + "region": { + "type": "string" } }, "required": [ @@ -1095,6 +1104,9 @@ }, "glob": { "type": "string" + }, + "region": { + "type": "string" } }, "required": [ @@ -1169,6 +1181,9 @@ "function": { "type": "string" }, + "region": { + "type": "string" + }, "source": { "type": "string" } @@ -1247,6 +1262,9 @@ }, "regex": { "type": "string" + }, + "region": { + "type": "string" } }, "required": [ @@ -1568,6 +1586,9 @@ }, "glob": { "type": "string" + }, + "region": { + "type": "string" } }, "required": [ @@ -1642,6 +1663,9 @@ "function": { "type": "string" }, + "region": { + "type": "string" + }, "source": { "type": "string" } @@ -1720,6 +1744,9 @@ }, "regex": { "type": "string" + }, + "region": { + "type": "string" } }, "required": [ diff --git a/src/deploy/hosting/convertConfig.ts b/src/deploy/hosting/convertConfig.ts index e8d9f851265..b7983cbf6f7 100644 --- a/src/deploy/hosting/convertConfig.ts +++ b/src/deploy/hosting/convertConfig.ts @@ -58,6 +58,11 @@ export function convertConfig(config?: HostingConfig): { [k: string]: any } { vRewrite.path = rewrite.destination; } else if ("function" in rewrite) { vRewrite.function = rewrite.function; + if (rewrite.region) { + vRewrite.functionRegion = rewrite.region; + } else { + vRewrite.functionRegion = "us-central1"; + } } else if ("dynamicLinks" in rewrite) { vRewrite.dynamicLinks = rewrite.dynamicLinks; } else if ("run" in rewrite) { diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index 1db9a6e2a50..92d6418eb62 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -40,7 +40,7 @@ type HostingRedirects = HostingSource & { export type HostingRewrites = HostingSource & ( | { destination: string } - | { function: string } + | { function: string; region?: string } | { run: { serviceId: string; diff --git a/src/hosting/functionsProxy.ts b/src/hosting/functionsProxy.ts index 582790131a4..62d7861fe64 100644 --- a/src/hosting/functionsProxy.ts +++ b/src/hosting/functionsProxy.ts @@ -6,6 +6,8 @@ import { needProjectId } from "../projectUtils"; import { EmulatorRegistry } from "../emulator/registry"; import { Emulators } from "../emulator/types"; import { FunctionsEmulator } from "../emulator/functionsEmulator"; +import { HostingRewrites } from "../firebaseConfig"; +import { FirebaseError } from "../error"; export interface FunctionsProxyOptions { port: number; @@ -13,24 +15,26 @@ export interface FunctionsProxyOptions { targets: string[]; } -export interface FunctionProxyRewrite { - function: string; -} - /** * Returns a function which, given a FunctionProxyRewrite, returns a Promise * that resolves with a middleware-like function that proxies the request to a * hosted or live function. */ -export default function ( +export function functionsProxy( options: FunctionsProxyOptions -): (r: FunctionProxyRewrite) => Promise { - return (rewrite: FunctionProxyRewrite) => { +): (r: HostingRewrites) => Promise { + return (rewrite: HostingRewrites) => { return new Promise((resolve) => { - // TODO(samstern): This proxy assumes all functions are in the default region, but this is - // not a safe assumption. const projectId = needProjectId(options); - let url = `https://us-central1-${projectId}.cloudfunctions.net/${rewrite.function}`; + if (!("function" in rewrite)) { + throw new FirebaseError(`A non-function rewrite cannot be used in functionsProxy`, { + exit: 2, + }); + } + if (!rewrite.region) { + rewrite.region = "us-central1"; + } + let url = `https://${rewrite.region}-${projectId}.cloudfunctions.net/${rewrite.function}`; let destLabel = "live"; if (includes(options.targets, "functions")) { @@ -45,12 +49,14 @@ export default function ( functionsEmu.getInfo().port, projectId, rewrite.function, - "us-central1" + rewrite.region ); } } - resolve(proxyRequestHandler(url, `${destLabel} Function ${rewrite.function}`)); + resolve( + proxyRequestHandler(url, `${destLabel} Function ${rewrite.region}/${rewrite.function}`) + ); }); }; } diff --git a/src/serve/hosting.ts b/src/serve/hosting.ts index 0920fe5ec1a..4dc009ea00f 100644 --- a/src/serve/hosting.ts +++ b/src/serve/hosting.ts @@ -9,7 +9,7 @@ import { implicitInit, TemplateServerResponse } from "../hosting/implicitInit"; import { initMiddleware } from "../hosting/initMiddleware"; import { normalizedHostingConfigs } from "../hosting/normalizedHostingConfigs"; import cloudRunProxy from "../hosting/cloudRunProxy"; -import functionsProxy from "../hosting/functionsProxy"; +import { functionsProxy } from "../hosting/functionsProxy"; import { NextFunction, Request, Response } from "express"; import { Writable } from "stream"; import { EmulatorLogger } from "../emulator/emulatorLogger"; diff --git a/src/test/deploy/hosting/convertConfig.spec.ts b/src/test/deploy/hosting/convertConfig.spec.ts index c4a1925a0e3..b6fd9f7f906 100644 --- a/src/test/deploy/hosting/convertConfig.spec.ts +++ b/src/test/deploy/hosting/convertConfig.spec.ts @@ -22,14 +22,19 @@ describe("convertConfig", () => { want: { rewrites: [{ glob: "/foo$", path: "https://example.com" }] }, }, { - name: "returns rewrites for glob CF3", + name: "defaults to us-central1 for CF3", input: { rewrites: [{ glob: "/foo", function: "foofn" }] }, - want: { rewrites: [{ glob: "/foo", function: "foofn" }] }, + want: { rewrites: [{ glob: "/foo", function: "foofn", functionRegion: "us-central1" }] }, + }, + { + name: "returns rewrites for glob CF3", + input: { rewrites: [{ glob: "/foo", function: "foofn", region: "europe-west2" }] }, + want: { rewrites: [{ glob: "/foo", function: "foofn", functionRegion: "europe-west2" }] }, }, { name: "returns rewrites for regex CF3", - input: { rewrites: [{ regex: "/foo$", function: "foofn" }] }, - want: { rewrites: [{ regex: "/foo$", function: "foofn" }] }, + input: { rewrites: [{ regex: "/foo$", function: "foofn", region: "us-central1" }] }, + want: { rewrites: [{ regex: "/foo$", function: "foofn", functionRegion: "us-central1" }] }, }, { name: "returns rewrites for glob Run", diff --git a/src/test/emulators/emulatorServer.spec.ts b/src/test/emulators/emulatorServer.spec.ts index d1c28d54c6f..8531821fd30 100644 --- a/src/test/emulators/emulatorServer.spec.ts +++ b/src/test/emulators/emulatorServer.spec.ts @@ -3,11 +3,13 @@ import { EmulatorRegistry } from "../../emulator/registry"; import { expect } from "chai"; import { FakeEmulator } from "./fakeEmulator"; import { EmulatorServer } from "../../emulator/emulatorServer"; +import { findAvailablePort } from "../../emulator/portUtils"; describe("EmulatorServer", () => { it("should correctly start and stop an emulator", async () => { const name = Emulators.FUNCTIONS; - const emulator = new FakeEmulator(name, "localhost", 5000); + const port = await findAvailablePort("localhost", 5000); + const emulator = new FakeEmulator(name, "localhost", port); const server = new EmulatorServer(emulator); await server.start(); diff --git a/src/test/emulators/registry.spec.ts b/src/test/emulators/registry.spec.ts index 4fd31a9c9d4..f5c3603a3e6 100644 --- a/src/test/emulators/registry.spec.ts +++ b/src/test/emulators/registry.spec.ts @@ -2,6 +2,7 @@ import { ALL_EMULATORS, Emulators } from "../../emulator/types"; import { EmulatorRegistry } from "../../emulator/registry"; import { expect } from "chai"; import { FakeEmulator } from "./fakeEmulator"; +import { findAvailablePort } from "../../emulator/portUtils"; describe("EmulatorRegistry", () => { afterEach(async () => { @@ -18,7 +19,8 @@ describe("EmulatorRegistry", () => { it("should correctly return information about a running emulator", async () => { const name = Emulators.FUNCTIONS; - const emu = new FakeEmulator(name, "localhost", 5000); + const port = await findAvailablePort("localhost", 5000); + const emu = new FakeEmulator(name, "localhost", port); expect(EmulatorRegistry.isRunning(name)).to.be.false; @@ -27,12 +29,13 @@ describe("EmulatorRegistry", () => { expect(EmulatorRegistry.isRunning(name)).to.be.true; expect(EmulatorRegistry.listRunning()).to.eql([name]); expect(EmulatorRegistry.get(name)).to.eql(emu); - expect(EmulatorRegistry.getPort(name)).to.eql(5000); + expect(EmulatorRegistry.getPort(name)).to.eql(port); }); it("once stopped, an emulator is no longer running", async () => { const name = Emulators.FUNCTIONS; - const emu = new FakeEmulator(name, "localhost", 5000); + const port = await findAvailablePort("localhost", 5000); + const emu = new FakeEmulator(name, "localhost", port); expect(EmulatorRegistry.isRunning(name)).to.be.false; await EmulatorRegistry.start(emu); diff --git a/src/test/fixtures/valid-config/firebase.json b/src/test/fixtures/valid-config/firebase.json index 3122016789d..9d34cd25c9c 100644 --- a/src/test/fixtures/valid-config/firebase.json +++ b/src/test/fixtures/valid-config/firebase.json @@ -17,6 +17,13 @@ "type" : 302 } ], "rewrites": [ { + "source": "/region", + "function": "/foobar", + "region": "us-central1" + }, { + "source": "/default", + "function": "/foobar" + }, { "source": "**", "destination": "/index.html" } ], diff --git a/src/test/hosting/functionsProxy.spec.ts b/src/test/hosting/functionsProxy.spec.ts index 948d1763791..f86efb10e53 100644 --- a/src/test/hosting/functionsProxy.spec.ts +++ b/src/test/hosting/functionsProxy.spec.ts @@ -5,13 +5,11 @@ import * as nock from "nock"; import * as sinon from "sinon"; import * as supertest from "supertest"; -import functionsProxy, { - FunctionProxyRewrite, - FunctionsProxyOptions, -} from "../../hosting/functionsProxy"; +import { functionsProxy, FunctionsProxyOptions } from "../../hosting/functionsProxy"; import { EmulatorRegistry } from "../../emulator/registry"; import { Emulators } from "../../emulator/types"; import { FakeEmulator } from "../emulators/fakeEmulator"; +import { HostingRewrites } from "../../firebaseConfig"; describe("functionsProxy", () => { const fakeOptions: FunctionsProxyOptions = { @@ -20,7 +18,11 @@ describe("functionsProxy", () => { targets: [], }; - const fakeRewrite: FunctionProxyRewrite = { function: "bar" }; + const fakeRewrite = { function: "bar", region: "us-central1" } as HostingRewrites; + const fakeRewriteEurope = { + function: "bar", + region: "europe-west3", + } as HostingRewrites; beforeEach(async () => { const fakeFunctionsEmulator = new FakeEmulator(Emulators.FUNCTIONS, "localhost", 7778); @@ -49,6 +51,23 @@ describe("functionsProxy", () => { }); }); + it("should resolve a function returns middleware that proxies to the live version in another region", async () => { + nock("https://europe-west3-project-foo.cloudfunctions.net") + .get("/bar/") + .reply(200, "live version"); + + const mwGenerator = functionsProxy(fakeOptions); + const mw = await mwGenerator(fakeRewriteEurope); + const spyMw = sinon.spy(mw); + + return supertest(spyMw) + .get("/") + .expect(200, "live version") + .then(() => { + expect(spyMw.calledOnce).to.be.true; + }); + }); + it("should resolve a function that returns middleware that proxies to a local version", async () => { nock("http://localhost:7778").get("/project-foo/us-central1/bar/").reply(200, "local version"); @@ -67,6 +86,24 @@ describe("functionsProxy", () => { }); }); + it("should resolve a function that returns middleware that proxies to a local version in another region", async () => { + nock("http://localhost:7778").get("/project-foo/europe-west3/bar/").reply(200, "local version"); + + const options = cloneDeep(fakeOptions); + options.targets = ["functions"]; + + const mwGenerator = functionsProxy(options); + const mw = await mwGenerator(fakeRewriteEurope); + const spyMw = sinon.spy(mw); + + return supertest(spyMw) + .get("/") + .expect(200, "local version") + .then(() => { + expect(spyMw.calledOnce).to.be.true; + }); + }); + it("should maintain the location header as returned by the function", async () => { nock("http://localhost:7778") .get("/project-foo/us-central1/bar/") From e70ab30b4740fc2dd8d9b673f2bd8b103e6114c7 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 16 Feb 2022 15:32:38 -0800 Subject: [PATCH 0084/1699] Release Emulator UI v1.6.5. (#4175) * Release Emulator UI v1.6.5. * Add flag for emulatoruisnapshot. --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 43 +++++++++++++++++++-------- src/previews.ts | 2 ++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f5dcfea2b..4cd799e3280 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - `firebase emulators:start --export-on-exit ` now rejects overwriting the current directory or parents (#4127). - Fixes broken functions:config:clone command (#4173). - Fixes issue where `auth:import` would fail when reading a JSON file. (#4157) +- Fixes issue where custom claims added in Auth Emulator UI was not properly shown. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 476d1701812..72d96b76ffa 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -62,19 +62,36 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe namePrefix: "cloud-storage-rules-emulator", }, }, - ui: { - version: "1.6.4", - downloadPath: path.join(CACHE_DIR, "ui-v1.6.4.zip"), - unzipDir: path.join(CACHE_DIR, "ui-v1.6.4"), - binaryPath: path.join(CACHE_DIR, "ui-v1.6.4", "server.bundle.js"), - opts: { - cacheDir: CACHE_DIR, - remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.4.zip", - expectedSize: 3757300, - expectedChecksum: "20d4ee71e4ff7527b1843b6a8636142e", - namePrefix: "ui", - }, - }, + ui: previews.emulatoruisnapshot + ? { + version: "SNAPSHOT", + downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"), + unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"), + binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.bundle.js"), + opts: { + cacheDir: CACHE_DIR, + remoteUrl: + "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip", + expectedSize: -1, + expectedChecksum: "", + skipCache: true, + skipChecksumAndSize: true, + namePrefix: "ui", + }, + } + : { + version: "1.6.5", + downloadPath: path.join(CACHE_DIR, "ui-v1.6.5.zip"), + unzipDir: path.join(CACHE_DIR, "ui-v1.6.5"), + binaryPath: path.join(CACHE_DIR, "ui-v1.6.5", "server.bundle.js"), + opts: { + cacheDir: CACHE_DIR, + remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.5.zip", + expectedSize: 3816994, + expectedChecksum: "92dfff4b2ef8ab616e8a60cc93e0a00b", + namePrefix: "ui", + }, + }, pubsub: { downloadPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0.zip"), version: "0.1.0", diff --git a/src/previews.ts b/src/previews.ts index 8fc141b566c..a63e44e3170 100644 --- a/src/previews.ts +++ b/src/previews.ts @@ -10,6 +10,7 @@ interface PreviewFlags { golang: boolean; deletegcfartifacts: boolean; artifactregistry: boolean; + emulatoruisnapshot: boolean; } export const previews: PreviewFlags = { @@ -22,6 +23,7 @@ export const previews: PreviewFlags = { golang: false, deletegcfartifacts: false, artifactregistry: false, + emulatoruisnapshot: false, ...(configstore.get("previews") as Partial), }; From 5b6071fe3a47d4f7f106cc103439bea52f39dfde Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 17 Feb 2022 07:39:24 -0800 Subject: [PATCH 0085/1699] Fixing leaky fake (#4178) --- src/test/emulators/commandUtils.spec.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/emulators/commandUtils.spec.ts b/src/test/emulators/commandUtils.spec.ts index b3606481466..51454eaf0ce 100644 --- a/src/test/emulators/commandUtils.spec.ts +++ b/src/test/emulators/commandUtils.spec.ts @@ -2,7 +2,7 @@ import * as commandUtils from "../../emulator/commandUtils"; import { expect } from "chai"; import { FirebaseError } from "../../error"; import { EXPORT_ON_EXIT_USAGE_ERROR, EXPORT_ON_EXIT_CWD_DANGER } from "../../emulator/commandUtils"; -import { delimiter, join, resolve } from "path"; +import * as path from "path"; import * as sinon from "sinon"; describe("commandUtils", () => { @@ -17,13 +17,13 @@ describe("commandUtils", () => { let pathStub: sinon.SinonStub; beforeEach(() => { - pathStub = sinon.stub(require("path"), "resolve").callsFake((path) => { + pathStub = sinon.stub(path, "resolve").callsFake((path) => { return path === "." ? mockCWD : mockDestinationDir; }); }); afterEach(() => { - pathStub.reset(); + pathStub.restore(); }); it("Should not block if destination contains a match to the CWD", () => { @@ -41,9 +41,9 @@ describe("commandUtils", () => { const directoriesThatShouldFail = [ ".", // The current dir "./", // The current dir with / - resolve("."), // An absolute path - resolve(".."), // A folder that directs to the CWD - resolve("../.."), // Another folder that directs to the CWD + path.resolve("."), // An absolute path + path.resolve(".."), // A folder that directs to the CWD + path.resolve("../.."), // Another folder that directs to the CWD ]; directoriesThatShouldFail.forEach((dir) => { @@ -51,7 +51,7 @@ describe("commandUtils", () => { expect(() => testSetExportOnExitOptions({ exportOnExit: dir })).to.throw( EXPORT_ON_EXIT_CWD_DANGER ); - const cwdSubDir = join(dir, "some-dir"); + const cwdSubDir = path.join(dir, "some-dir"); expect(testSetExportOnExitOptions({ exportOnExit: cwdSubDir }).exportOnExit).to.equal( cwdSubDir ); @@ -62,7 +62,7 @@ describe("commandUtils", () => { expect(() => testSetExportOnExitOptions({ import: ".", exportOnExit: true })).to.throw( EXPORT_ON_EXIT_CWD_DANGER ); - const cwdSubDir = join(".", "some-dir"); + const cwdSubDir = path.join(".", "some-dir"); expect( testSetExportOnExitOptions({ import: cwdSubDir, exportOnExit: true }).exportOnExit ).to.equal(cwdSubDir); From 520ae1f9816cefe8cbbf11f69c5e806dcf262ad4 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 17 Feb 2022 10:59:21 -0600 Subject: [PATCH 0086/1699] Hosting deploy migration to apiv2, typescript, project-scoped (#3989) * migrate the hosting deploy to typescript and the new api client * remove extra lines * remove warning supressions --- CHANGELOG.md | 1 + src/deploy/hosting/client.ts | 7 ++++ src/deploy/hosting/index.ts | 8 ++--- src/deploy/hosting/prepare.js | 59 ---------------------------------- src/deploy/hosting/prepare.ts | 60 +++++++++++++++++++++++++++++++++++ src/deploy/hosting/release.js | 50 ----------------------------- src/deploy/hosting/release.ts | 48 ++++++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 114 deletions(-) create mode 100644 src/deploy/hosting/client.ts delete mode 100644 src/deploy/hosting/prepare.js create mode 100644 src/deploy/hosting/prepare.ts delete mode 100644 src/deploy/hosting/release.js create mode 100644 src/deploy/hosting/release.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cd799e3280..d80789ce8cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - Fixes broken functions:config:clone command (#4173). - Fixes issue where `auth:import` would fail when reading a JSON file. (#4157) - Fixes issue where custom claims added in Auth Emulator UI was not properly shown. +- Updates the underlying request library in Hosting deploys and uses project-scoped URLs. (#2558) diff --git a/src/deploy/hosting/client.ts b/src/deploy/hosting/client.ts new file mode 100644 index 00000000000..4acd4920e2f --- /dev/null +++ b/src/deploy/hosting/client.ts @@ -0,0 +1,7 @@ +import { hostingApiOrigin } from "../../api"; +import { Client } from "../../apiv2"; + +export const client = new Client({ + urlPrefix: hostingApiOrigin, + apiVersion: "v1beta1", +}); diff --git a/src/deploy/hosting/index.ts b/src/deploy/hosting/index.ts index 7fc7e8a3393..05e0bf0d871 100644 --- a/src/deploy/hosting/index.ts +++ b/src/deploy/hosting/index.ts @@ -1,5 +1,3 @@ -import * as prepare from "./prepare"; -import { deploy } from "./deploy"; -import * as release from "./release"; - -export { prepare, deploy, release }; +export { prepare } from "./prepare"; +export { deploy } from "./deploy"; +export { release } from "./release"; diff --git a/src/deploy/hosting/prepare.js b/src/deploy/hosting/prepare.js deleted file mode 100644 index f586eebad00..00000000000 --- a/src/deploy/hosting/prepare.js +++ /dev/null @@ -1,59 +0,0 @@ -"use strict"; - -const _ = require("lodash"); - -const api = require("../../api"); -const { convertConfig } = require("./convertConfig"); -const deploymentTool = require("../../deploymentTool"); -const { FirebaseError } = require("../../error"); -const { normalizedHostingConfigs } = require("../../hosting/normalizedHostingConfigs"); -const { validateDeploy } = require("./validate"); - -module.exports = function (context, options) { - // Allow the public directory to be overridden by the --public flag - if (options.public) { - if (_.isArray(options.config.get("hosting"))) { - throw new FirebaseError("Cannot specify --public option with multi-site configuration."); - } - - options.config.set("hosting.public", options.public); - } - - const configs = normalizedHostingConfigs(options, { resolveTargets: true }); - if (configs.length === 0) { - return Promise.resolve(); - } - - context.hosting = { - deploys: configs.map(function (cfg) { - return { config: cfg, site: cfg.site }; - }), - }; - - const versionCreates = []; - - _.each(context.hosting.deploys, function (deploy) { - const cfg = deploy.config; - - validateDeploy(deploy, options); - - const data = { - config: convertConfig(cfg), - labels: deploymentTool.labels(), - }; - - versionCreates.push( - api - .request("POST", `/v1beta1/sites/${deploy.site}/versions`, { - origin: api.hostingApiOrigin, - auth: true, - data, - }) - .then(function (result) { - deploy.version = result.body.name; - }) - ); - }); - - return Promise.all(versionCreates); -}; diff --git a/src/deploy/hosting/prepare.ts b/src/deploy/hosting/prepare.ts new file mode 100644 index 00000000000..3bc394d923f --- /dev/null +++ b/src/deploy/hosting/prepare.ts @@ -0,0 +1,60 @@ +import { FirebaseError } from "../../error"; +import { client } from "./client"; +import { needProjectNumber } from "../../projectUtils"; +import { normalizedHostingConfigs } from "../../hosting/normalizedHostingConfigs"; +import { validateDeploy } from "./validate"; +import { convertConfig } from "./convertConfig"; +import * as deploymentTool from "../../deploymentTool"; + +/** + * Prepare creates versions for each Hosting site to be deployed. + */ +export async function prepare(context: any, options: any): Promise { + // Allow the public directory to be overridden by the --public flag + if (options.public) { + if (Array.isArray(options.config.get("hosting"))) { + throw new FirebaseError("Cannot specify --public option with multi-site configuration."); + } + + options.config.set("hosting.public", options.public); + } + + const projectNumber = await needProjectNumber(options); + + const configs = normalizedHostingConfigs(options, { resolveTargets: true }); + if (configs.length === 0) { + return Promise.resolve(); + } + + context.hosting = { + deploys: configs.map((cfg) => { + return { config: cfg, site: cfg.site }; + }), + }; + + const versionCreates: unknown[] = []; + + for (const deploy of context.hosting.deploys) { + const cfg = deploy.config; + + validateDeploy(deploy, options); + + const data = { + config: convertConfig(cfg), + labels: deploymentTool.labels(), + }; + + versionCreates.push( + client + .post<{ config: unknown; labels: { [k: string]: string } }, { name: string }>( + `/projects/${projectNumber}/sites/${deploy.site}/versions`, + data + ) + .then((res) => { + deploy.version = res.body.name; + }) + ); + } + + await Promise.all(versionCreates); +} diff --git a/src/deploy/hosting/release.js b/src/deploy/hosting/release.js deleted file mode 100644 index 76135e826f0..00000000000 --- a/src/deploy/hosting/release.js +++ /dev/null @@ -1,50 +0,0 @@ -const api = require("../../api"); -const utils = require("../../utils"); -const { logger } = require("../../logger"); - -module.exports = function (context, options) { - if (!context.hosting || !context.hosting.deploys) { - return Promise.resolve(); - } - - logger.debug(JSON.stringify(context.hosting.deploys, null, 2)); - return Promise.all( - context.hosting.deploys.map(async function (deploy) { - utils.logLabeledBullet("hosting[" + deploy.site + "]", "finalizing version..."); - const finalizeResult = await api.request( - "PATCH", - `/v1beta1/${deploy.version}?updateMask=status`, - { - origin: api.hostingApiOrigin, - auth: true, - data: { status: "FINALIZED" }, - } - ); - - logger.debug("[hosting] finalized version for " + deploy.site + ":", finalizeResult.body); - utils.logLabeledSuccess("hosting[" + deploy.site + "]", "version finalized"); - utils.logLabeledBullet("hosting[" + deploy.site + "]", "releasing new version..."); - - // TODO: We should deploy to the resource we're given rather than have to check for a channel here. - const channelSegment = - context.hostingChannel && context.hostingChannel !== "live" - ? `/channels/${context.hostingChannel}` - : ""; - if (channelSegment) { - logger.debug("[hosting] releasing to channel:", context.hostingChannel); - } - - const releaseResult = await api.request( - "POST", - `/v1beta1/sites/${deploy.site}${channelSegment}/releases?version_name=${deploy.version}`, - { - auth: true, - origin: api.hostingApiOrigin, - data: { message: options.message || null }, - } - ); - logger.debug("[hosting] release:", releaseResult.body); - utils.logLabeledSuccess("hosting[" + deploy.site + "]", "release complete"); - }) - ); -}; diff --git a/src/deploy/hosting/release.ts b/src/deploy/hosting/release.ts new file mode 100644 index 00000000000..5826f05fa98 --- /dev/null +++ b/src/deploy/hosting/release.ts @@ -0,0 +1,48 @@ +import { client } from "./client"; +import { logger } from "../../logger"; +import { needProjectNumber } from "../../projectUtils"; +import * as utils from "../../utils"; + +/** + * Release finalized a Hosting release. + */ +export async function release(context: any, options: any): Promise { + if (!context.hosting || !context.hosting.deploys) { + return; + } + + const projectNumber = await needProjectNumber(options); + + logger.debug(JSON.stringify(context.hosting.deploys, null, 2)); + await Promise.all( + context.hosting.deploys.map(async (deploy: any) => { + utils.logLabeledBullet(`hosting[${deploy.site}]`, "finalizing version..."); + const finalizeResult = await client.patch( + `/${deploy.version}`, + { status: "FINALIZED" }, + { queryParams: { updateMask: "status" } } + ); + + logger.debug(`[hosting] finalized version for ${deploy.site}:${finalizeResult.body}`); + utils.logLabeledSuccess(`hosting[${deploy.site}]`, "version finalized"); + utils.logLabeledBullet(`hosting[${deploy.site}]`, "releasing new version..."); + + // TODO: We should deploy to the resource we're given rather than have to check for a channel here. + const channelSegment = + context.hostingChannel && context.hostingChannel !== "live" + ? `/channels/${context.hostingChannel}` + : ""; + if (channelSegment) { + logger.debug("[hosting] releasing to channel:", context.hostingChannel); + } + + const releaseResult = await client.post( + `/projects/${projectNumber}/sites/${deploy.site}${channelSegment}/releases`, + { message: options.message || null }, + { queryParams: { versionName: deploy.version } } + ); + logger.debug("[hosting] release:", releaseResult.body); + utils.logLabeledSuccess(`hosting[${deploy.site}]`, "release complete"); + }) + ); +} From ebd3323cf981404d3fed9f19b33e159fe0243b41 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 17 Feb 2022 10:30:16 -0800 Subject: [PATCH 0087/1699] Preserve response body on api requests with XML response types. (#4180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Today, apiv2 will try to parse XML responses as JSON with poor results, e.g. ``` âš  functions: Upload Error: Unable to parse JSON: SyntaxError: Unexpected token < in JSON at position 0 Error: Unable to parse JSON: SyntaxError: Unexpected token < in JSON at position 0 ``` Here, we explicitly add support for `xml` as a responseType and deal with it as a string. E.g. ``` âš  functions: Upload Error: HTTP Error: 400, EntityTooLargeYour proposed upload is larger than the maximum object size specified in your Policy Document.
Content-length exceeds upper bound on range
Error: HTTP Error: 400, EntityTooLargeYour proposed upload is larger than the maximum object size specified in your Policy Document.
Content-length exceeds upper bound on range
``` Fixes https://github.com/firebase/firebase-tools/issues/4146 --- CHANGELOG.md | 1 + src/apiv2.ts | 4 +++- src/gcp/storage.ts | 1 + src/responseToError.js | 24 ++++++++++++++++-------- src/test/apiv2.spec.ts | 30 ++++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d80789ce8cf..9699a6edfee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,5 @@ - Fixes broken functions:config:clone command (#4173). - Fixes issue where `auth:import` would fail when reading a JSON file. (#4157) - Fixes issue where custom claims added in Auth Emulator UI was not properly shown. +- Improves handling of API requests with XML responses (#4180). - Updates the underlying request library in Hosting deploys and uses project-scoped URLs. (#2558) diff --git a/src/apiv2.ts b/src/apiv2.ts index 6a9b6db458c..5ba339c2709 100644 --- a/src/apiv2.ts +++ b/src/apiv2.ts @@ -23,7 +23,7 @@ interface BaseRequestOptions extends VerbOptions { method: HttpMethod; path: string; body?: T | string | NodeJS.ReadableStream; - responseType?: "json" | "stream"; + responseType?: "json" | "stream" | "xml"; redirect?: "error" | "follow" | "manual"; compress?: boolean; } @@ -415,6 +415,8 @@ export class Client { throw new FirebaseError(`Unable to parse JSON: ${err}`); } } + } else if (options.responseType === "xml") { + body = (await res.text()) as unknown as ResT; } else if (options.responseType === "stream") { body = res.body as unknown as ResT; } else { diff --git a/src/gcp/storage.ts b/src/gcp/storage.ts index 0a2cac10551..4d28f0b1a8e 100644 --- a/src/gcp/storage.ts +++ b/src/gcp/storage.ts @@ -158,6 +158,7 @@ export async function upload( method: "PUT", path: url.pathname, queryParams: url.searchParams, + responseType: "xml", headers: { "content-type": "application/zip", ...extraHeaders, diff --git a/src/responseToError.js b/src/responseToError.js index 7ed45086e54..8587a3ce9e4 100644 --- a/src/responseToError.js +++ b/src/responseToError.js @@ -4,18 +4,26 @@ const _ = require("lodash"); const { FirebaseError } = require("./error"); module.exports = function (response, body) { - if (typeof body === "string" && response.statusCode === 404) { - body = { - error: { - message: "Not Found", - }, - }; - } - if (response.statusCode < 400) { return null; } + if (typeof body === "string") { + if (response.statusCode === 404) { + body = { + error: { + message: "Not Found", + }, + }; + } else { + body = { + error: { + message: body, + }, + }; + } + } + if (typeof body !== "object") { try { body = JSON.parse(body); diff --git a/src/test/apiv2.spec.ts b/src/test/apiv2.spec.ts index 6a67f3c84c7..68ef5a05895 100644 --- a/src/test/apiv2.spec.ts +++ b/src/test/apiv2.spec.ts @@ -394,6 +394,36 @@ describe("apiv2", () => { expect(nock.isDone()).to.be.true; }); + it("should preserve XML messages", async () => { + const xml = "Hello!"; + nock("https://example.com").get("/path/to/foo").reply(200, xml); + + const c = new Client({ urlPrefix: "https://example.com" }); + const r = await c.request({ + method: "GET", + path: "/path/to/foo", + responseType: "xml", + }); + expect(r.body).to.deep.equal(xml); + expect(nock.isDone()).to.be.true; + }); + + it("should preserve XML messages on error", async () => { + const xml = + "EntityTooLarge"; + nock("https://example.com").get("/path/to/foo").reply(400, xml); + + const c = new Client({ urlPrefix: "https://example.com" }); + await expect( + c.request({ + method: "GET", + path: "/path/to/foo", + responseType: "xml", + }) + ).to.eventually.be.rejectedWith(FirebaseError, /EntityTooLarge/); + expect(nock.isDone()).to.be.true; + }); + describe("with a proxy", () => { let proxyServer: Server; let targetServer: Server; From fe4cdd481def079eaeb0ea2021d5c977bcfbae33 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 17 Feb 2022 10:53:28 -0800 Subject: [PATCH 0088/1699] Use trigger.entryPoint instead of trigger.name (#4182) * Use trigger.entryPoint instead of trigger.name * pr fix --- src/emulator/functionsEmulator.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 4c58bfe26cf..9eb7c8433d2 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -911,11 +911,10 @@ export class FunctionsEmulator implements EmulatorInstance { envs.PORT = "80"; if (trigger) { - const service = trigger.name; - const target = service.replace(/-/g, "."); + const target = trigger.entryPoint; envs.FUNCTION_TARGET = target; envs.FUNCTION_SIGNATURE_TYPE = getSignatureType(trigger); - envs.K_SERVICE = service; + envs.K_SERVICE = trigger.name; } return envs; } From 3949a72003ec3161f958d6c7276a449a77e266dc Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 17 Feb 2022 19:20:35 +0000 Subject: [PATCH 0089/1699] 10.2.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a4b299ec0c2..4dfe12e2549 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.2.0", + "version": "10.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.2.0", + "version": "10.2.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index f1dc8c52e03..03b0b6acbc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.2.0", + "version": "10.2.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From a799118e82f0be27ffef0c5f31e1a52f482a90c9 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 17 Feb 2022 19:20:55 +0000 Subject: [PATCH 0090/1699] [firebase-release] Removed change log and reset repo after 10.2.1 release --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9699a6edfee..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +0,0 @@ -- Fixes an issue where ext:list was not printing out information about installed Extension instances (#4156) -- Fixes issue where Functions Emulator crashed when parsing triggers if accessing functions config values (#4162). -- `firebase emulators:start --export-on-exit
` now rejects overwriting the current directory or parents (#4127). -- Fixes broken functions:config:clone command (#4173). -- Fixes issue where `auth:import` would fail when reading a JSON file. (#4157) -- Fixes issue where custom claims added in Auth Emulator UI was not properly shown. -- Improves handling of API requests with XML responses (#4180). -- Updates the underlying request library in Hosting deploys and uses project-scoped URLs. (#2558) From aa178b29f8d06278294d88c8767b33d6f59ecd74 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:36:48 -0500 Subject: [PATCH 0091/1699] Fix iOS auth for resumable uploads in Storage Emulator (#4184) * Fix iOS auth for resumable uploads in Storage Emulator * Store auth header in ResumableUpload instead of app.locals * Rename upload resumable integration test * Add additional expect for upload request 200 --- scripts/storage-emulator-integration/tests.ts | 32 +++++++++++++++++++ src/emulator/storage/apis/firebase.ts | 6 ++-- src/emulator/storage/files.ts | 20 ++++++++++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index 2ed262755eb..54d7d8a229b 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -1309,6 +1309,38 @@ describe("Storage emulator", () => { expect(md.downloadTokens.split(",")).to.not.deep.equal([token]); }); }); + + it("#uploadResumableDoesNotRequireMultipleAuthHeaders", async () => { + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg` + ) + .set({ + Authorization: "Bearer owner", + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + // No Authorization required in upload + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload", + }) + .expect(200); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + // No Authorization required in finalize + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload, finalize", + }) + .expect(200); + }); }); after(async function (this) { diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index f76e4318ca1..6ff39b825f3 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -452,7 +452,9 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { req.params.bucketId, name, objectContentType, - req.body + req.body, + // Store auth header for use in the finalize request + req.header("authorization") ); storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0)); @@ -544,7 +546,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { // TODO This will be either create or update method: RulesetOperationMethod.CREATE, path: operationPath, - authorization: req.header("authorization"), + authorization: upload.authorization, file: { after: storageLayer.getMetadata(req.params.bucketId, name)?.asRulesResource(), }, diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index fbf089485f1..eefd8141b16 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -53,6 +53,7 @@ export class ResumableUpload { private _bucketId: string; private _objectId: string; private _contentType: string; + private _authorization: string | undefined; private _currentBytesUploaded = 0; private _status: UploadStatus = UploadStatus.ACTIVE; private _fileLocation: string; @@ -62,13 +63,15 @@ export class ResumableUpload { objectId: string, uploadId: string, contentType: string, - metadata: IncomingMetadata + metadata: IncomingMetadata, + authorization?: string ) { this._bucketId = bucketId; this._objectId = objectId; this._uploadId = uploadId; this._contentType = contentType; this._metadata = metadata; + this._authorization = authorization; this._fileLocation = encodeURIComponent(`${uploadId}_b_${bucketId}_o_${objectId}`); this._currentBytesUploaded = 0; } @@ -91,6 +94,9 @@ export class ResumableUpload { public set contentType(contentType: string) { this._contentType = contentType; } + public get authorization(): string | undefined { + return this._authorization; + } public get currentBytesUploaded(): number { return this._currentBytesUploaded; } @@ -190,10 +196,18 @@ export class StorageLayer { bucket: string, object: string, contentType: string, - metadata: IncomingMetadata + metadata: IncomingMetadata, + authorization?: string ): ResumableUpload { const uploadId = v4(); - const upload = new ResumableUpload(bucket, object, uploadId, contentType, metadata); + const upload = new ResumableUpload( + bucket, + object, + uploadId, + contentType, + metadata, + authorization + ); this._uploads.set(uploadId, upload); return upload; } From 700a96ff2bb63055d946eb6b7fe25dfcab39c1c5 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 18 Feb 2022 11:15:33 -0800 Subject: [PATCH 0092/1699] Moving to development behind a feature flag (#4188) * Emulate extensions from firebase.json (#4096) * Adds ability to download and emulate extensions during emulators:start and emulators:exec * format and self review * Successfully emulating extensions!" * formats * self review * handle case when extensions are not defined in firebase.json * Don't error out on fake project ids * adding unit test for toEmulatableBackend * pr fixes * adding todo * Adding listBackends endpoint to support Emulator UI (#4122) * Adds ability to download and emulate extensions during emulators:start and emulators:exec * format and self review * Successfully emulating extensions!" * formats * self review * handle case when extensions are not defined in firebase.json * Don't error out on fake project ids * adding unit test for toEmulatableBackend * pr fixes * starting on listBackends API * Adding listBackends endpoint to support Emulator UI * fix tests * clarifying todo * Switching extensions emulator to use the same function IDs as prod (#4143) * Adding support for --only emulators (#4141) * Adding support for --only emulators * Regenerating schema * pr fixes * Renaming some variables to make it clear which parts are only relevant for ext:dev:emulators:* commands (#4150) * Revert "Merging master" This reverts commit b71529aabd00227f1c1d12c375a754b23f1038e6, reversing changes made to 132684a1054a1a75fa42c1361cf4d5308c3a5295. * Revert "Revert "Merging master"" This reverts commit ca6d63d97c7febe570a0668ac2d0830fdfd01506. * Fixing small merge conmflict * adding preview flag for emulator (#4187) * Adding cors & START_LOGGING_EMULATOR to support emulator UI (#4191) * Validate extension source code before running in emulator (#4186) * Validate extension source code before running in emulator * Improve logging * Fix tests * Fix format * Address comments * Adding extension metadata to structured logs (#4190) * Adding extension metadata to structured logs * formats * fixing other logs Co-authored-by: Elvis Sun --- schema/firebase-config.json | 5 + .../emulator-tests/functionsEmulator.spec.ts | 21 ++ src/commands/emulators-start.ts | 6 +- src/deploy/extensions/params.ts | 4 + src/deploy/extensions/planner.ts | 2 + src/emulator/constants.ts | 3 + src/emulator/controller.ts | 91 +++++-- src/emulator/download.ts | 31 +++ src/emulator/emulatorLogger.ts | 18 +- src/emulator/extensionsEmulator.ts | 169 +++++++++++++ src/emulator/functionsEmulator.ts | 43 +++- src/emulator/functionsEmulatorUtils.ts | 2 +- src/emulator/functionsRuntimeWorker.ts | 10 +- src/emulator/loggingEmulator.ts | 4 + src/emulator/registry.ts | 6 +- src/emulator/types.ts | 2 + src/extensions/emulator/optionsHelper.ts | 36 ++- src/extensions/emulator/specHelper.ts | 54 +---- src/firebaseConfig.ts | 1 + src/previews.ts | 2 + .../extension.yaml | 226 ++++++++++++++++++ .../functions/.gitignore | 2 + .../functions/node_modules/test_data.text | 1 + .../functions/package.json | 3 + src/test/emulators/extensionsEmulator.spec.ts | 98 ++++++++ 25 files changed, 755 insertions(+), 85 deletions(-) create mode 100644 src/emulator/extensionsEmulator.ts create mode 100644 src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/extension.yaml create mode 100644 src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/.gitignore create mode 100644 src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/test_data.text create mode 100644 src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json create mode 100644 src/test/emulators/extensionsEmulator.spec.ts diff --git a/schema/firebase-config.json b/schema/firebase-config.json index 0727f46fb9e..ddf7f59bb18 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -174,6 +174,11 @@ }, "type": "object" }, + "extensions": { + "properties": { + }, + "type": "object" + }, "firestore": { "additionalProperties": false, "properties": { diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index b1495e6a74c..8e8a101ad04 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -627,6 +627,27 @@ describe("FunctionsEmulator-Hub", () => { }); }).timeout(TIMEOUT_LONG); + it("should respond to requests to /backends to with info about the running backends", async () => { + useFunctions(() => { + require("firebase-admin").initializeApp(); + return { + function_id: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + } + ), + }; + }); + + await supertest(functionsEmulator.createHubServer()) + .get("/backends") + .expect(200) + .then((res) => { + // TODO(b/216642962): Add tests for this endpoint that validate behavior when there are Extensions running + expect(res.body.backends).to.deep.equal([{ env: {}, functionTriggers: [] }]); + }); + }).timeout(TIMEOUT_LONG); + describe("environment variables", () => { let emulatorRegistryStub: sinon.SinonStub; diff --git a/src/commands/emulators-start.ts b/src/commands/emulators-start.ts index 1b7709db53b..615324cd3b3 100644 --- a/src/commands/emulators-start.ts +++ b/src/commands/emulators-start.ts @@ -71,10 +71,12 @@ module.exports = new Command("emulators:start") ...controller .filterEmulatorTargets(options) .map((emulator) => { - const info = EmulatorRegistry.getInfo(emulator); const emulatorName = Constants.description(emulator).replace(/ emulator/i, ""); const isSupportedByUi = EMULATORS_SUPPORTED_BY_UI.includes(emulator); - + // The Extensions emulator runs as part of the Functions emulator, so display the Functions emulators info instead. + const info = EmulatorRegistry.getInfo( + emulator === Emulators.EXTENSIONS ? Emulators.FUNCTIONS : emulator + ); if (!info) { return [emulatorName, "Failed to initialize (see above)", "", ""]; } diff --git a/src/deploy/extensions/params.ts b/src/deploy/extensions/params.ts index b32beb00920..152303cc8f4 100644 --- a/src/deploy/extensions/params.ts +++ b/src/deploy/extensions/params.ts @@ -22,6 +22,7 @@ export function readParams(args: { projectNumber: string; aliases: string[]; instanceId: string; + checkLocal?: boolean; }): Record { const filesToCheck = [ `${args.instanceId}.env`, @@ -29,6 +30,9 @@ export function readParams(args: { `${args.instanceId}.env.${args.projectNumber}`, `${args.instanceId}.env.${args.projectId}`, ]; + if (args.checkLocal) { + filesToCheck.push(`${args.instanceId}.env.local`); + } let noFilesFound = true; const combinedParams = {}; for (const fileToCheck of filesToCheck) { diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 701b789f73f..717debcadbd 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -80,6 +80,7 @@ export async function want(args: { aliases: string[]; projectDir: string; extensions: Record; + checkLocal?: boolean; }): Promise { const instanceSpecs: InstanceSpec[] = []; const errors: FirebaseError[] = []; @@ -95,6 +96,7 @@ export async function want(args: { projectId: args.projectId, projectNumber: args.projectNumber, aliases: args.aliases, + checkLocal: args.checkLocal, }); const autoPopulatedParams = await getFirebaseProjectParams(args.projectId); const subbedParams = substituteParams(params, autoPopulatedParams); diff --git a/src/emulator/constants.ts b/src/emulator/constants.ts index 8813aeacd6f..57215705aed 100644 --- a/src/emulator/constants.ts +++ b/src/emulator/constants.ts @@ -8,6 +8,7 @@ const DEFAULT_PORTS: { [s in Emulators]: number } = { logging: 4500, hosting: 5000, functions: 5001, + extensions: 5001, // The Extensions Emulator runs on the same port as the Functions Emulator firestore: 8080, pubsub: 8085, database: 9000, @@ -26,6 +27,7 @@ export const FIND_AVAILBLE_PORT_BY_DEFAULT: Record = { pubsub: false, auth: false, storage: false, + extensions: false, }; export const EMULATOR_DESCRIPTION: Record = { @@ -39,6 +41,7 @@ export const EMULATOR_DESCRIPTION: Record = { pubsub: "Pub/Sub Emulator", auth: "Authentication Emulator", storage: "Storage Emulator", + extensions: "Extensions Emulator", }; const DEFAULT_HOST = "localhost"; diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 6ad48266099..14a1e5d1268 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -24,7 +24,7 @@ import { DatabaseEmulator, DatabaseEmulatorArgs } from "./databaseEmulator"; import { FirestoreEmulator, FirestoreEmulatorArgs } from "./firestoreEmulator"; import { HostingEmulator } from "./hostingEmulator"; import { FirebaseError } from "../error"; -import { getProjectId, needProjectId } from "../projectUtils"; +import { getProjectId, needProjectId, getAliases, needProjectNumber } from "../projectUtils"; import { PubsubEmulator } from "./pubsubEmulator"; import * as commandUtils from "./commandUtils"; import { EmulatorHub } from "./hub"; @@ -43,8 +43,20 @@ import { getDefaultDatabaseInstance } from "../getDefaultDatabaseInstance"; import { getProjectDefaultAccount } from "../auth"; import { Options } from "../options"; import { ParsedTriggerDefinition } from "./functionsEmulatorShared"; +import { ExtensionsEmulator } from "./extensionsEmulator"; +import { previews } from "../previews"; + +const START_LOGGING_EMULATOR = utils.envOverride( + "START_LOGGING_EMULATOR", + "false", + (val) => val == "true" +); async function getAndCheckAddress(emulator: Emulators, options: Options): Promise
{ + if (emulator === Emulators.EXTENSIONS) { + // The Extensions emulator always runs on the same port as the Functions emulator. + emulator = Emulators.FUNCTIONS; + } let host = options.config.src.emulators?.[emulator]?.host || Constants.getDefaultHost(emulator); if (host === "localhost" && utils.isRunningInWSL()) { // HACK(https://github.com/firebase/firebase-tools-ui/issues/332): Use IPv4 @@ -178,7 +190,12 @@ export async function cleanShutdown(): Promise { * @param options */ export function filterEmulatorTargets(options: any): Emulators[] { - let targets = ALL_SERVICE_EMULATORS.filter((e) => { + let targets = [...ALL_SERVICE_EMULATORS]; + if (previews.extensionsemulator) { + targets.push(Emulators.EXTENSIONS); + } + + targets = targets.filter((e) => { return options.config.has(e) || options.config.has(`emulators.${e}`); }); @@ -306,7 +323,7 @@ function findExportMetadata(importPath: string): ExportMetadata | undefined { } interface EmulatorOptions extends Options { - extensionEnv?: Record; + extDevEnv?: Record; } export async function startAll(options: EmulatorOptions, showUI: boolean = true): Promise { @@ -402,21 +419,54 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) } } + const emulatableBackends: EmulatableBackend[] = []; + const projectDir = (options.extDevDir || options.config.projectDir) as string; if (shouldStart(options, Emulators.FUNCTIONS)) { - const functionsLogger = EmulatorLogger.forEmulator(Emulators.FUNCTIONS); - const functionsAddr = await getAndCheckAddress(Emulators.FUNCTIONS, options); - const projectId = needProjectId(options); - + // Note: ext:dev:emulators:* commands hit this path, not the Emulators.EXTENSIONS path utils.assertDefined(options.config.src.functions); utils.assertDefined( options.config.src.functions.source, "Error: 'functions.source' is not defined" ); - utils.assertIsStringOrUndefined(options.extensionDir); - const projectDir = options.extensionDir || options.config.projectDir; + utils.assertIsStringOrUndefined(options.extDevDir); const functionsDir = path.join(projectDir, options.config.src.functions.source); + emulatableBackends.push({ + functionsDir, + env: { + ...options.extDevEnv, + }, + // TODO(b/213335255): predefinedTriggers and nodeMajorVersion are here to support ext:dev:emulators:* commands. + // Ideally, we should handle that case via ExtensionEmulator. + predefinedTriggers: options.extDevTriggers as ParsedTriggerDefinition[] | undefined, + nodeMajorVersion: parseRuntimeVersion( + options.extDevNodeVersion || options.config.get("functions.runtime") + ), + }); + } + + if (shouldStart(options, Emulators.EXTENSIONS) && previews.extensionsemulator) { + // TODO: This should not error out when called with a fake project. + const projectNumber = await needProjectNumber(options); + const aliases = getAliases(options, projectId); + + const extensionEmulator = new ExtensionsEmulator({ + projectId, + projectDir: options.config.projectDir, + projectNumber, + aliases, + extensions: options.config.get("extensions"), + }); + const extensionsBackends = await extensionEmulator.getExtensionBackends(); + emulatableBackends.push(...extensionsBackends); + } + + if (emulatableBackends.length) { + const functionsLogger = EmulatorLogger.forEmulator(Emulators.FUNCTIONS); + const functionsAddr = await getAndCheckAddress(Emulators.FUNCTIONS, options); + const projectId = needProjectId(options); + let inspectFunctions: number | undefined; if (options.inspectFunctions) { inspectFunctions = commandUtils.parseInspectionPort(options); @@ -425,11 +475,11 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) functionsLogger.logLabeled( "WARN", "functions", - `You are running the functions emulator in debug mode (port=${inspectFunctions}). This means that functions will execute in sequence rather than in parallel.` + `You are running the Functions emulator in debug mode (port=${inspectFunctions}). This means that functions will execute in sequence rather than in parallel.` ); } - // Warn the developer that the Functions emulator can call out to production. + // Warn the developer that the Functions/Extensions emulator can call out to production. const emulatorsNotRunning = ALL_SERVICE_EMULATORS.filter((e) => { return e !== Emulators.FUNCTIONS && !shouldStart(options, e); }); @@ -444,19 +494,8 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) } const account = getProjectDefaultAccount(options.projectRoot); - // TODO: Go read firebase.json for extensions and add them to emualtableBackends. - const emulatableBackends: EmulatableBackend[] = [ - { - functionsDir, - env: { - ...options.extensionEnv, - }, - predefinedTriggers: options.extensionTriggers as ParsedTriggerDefinition[] | undefined, - nodeMajorVersion: parseRuntimeVersion( - options.extensionNodeVersion || options.config.get("functions.runtime") - ), - }, - ]; + + // TODO(b/213241033): Figure out how to watch for changes to extensions .env files & reload triggers when they change. const functionsEmulator = new FunctionsEmulator({ projectId, projectDir, @@ -691,7 +730,7 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) ); } - if (showUI && shouldStart(options, Emulators.UI)) { + if (showUI && (shouldStart(options, Emulators.UI) || START_LOGGING_EMULATOR)) { const loggingAddr = await getAndCheckAddress(Emulators.LOGGING, options); const loggingEmulator = new LoggingEmulator({ host: loggingAddr.host, @@ -699,7 +738,9 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) }); await startEmulator(loggingEmulator); + } + if (showUI && shouldStart(options, Emulators.UI)) { const uiAddr = await getAndCheckAddress(Emulators.UI, options); const ui = new EmulatorUI({ projectId: projectId, diff --git a/src/emulator/download.ts b/src/emulator/download.ts index 0bc3e0a1db6..65c0a5cd960 100644 --- a/src/emulator/download.ts +++ b/src/emulator/download.ts @@ -43,6 +43,37 @@ export async function downloadEmulator(name: DownloadableEmulators): Promise { + const emulatorLogger = EmulatorLogger.forExtension({ ref: extensionVersionRef }); + emulatorLogger.logLabeled( + "BULLET", + "extensions", + `Starting download for ${extensionVersionRef} source code...` + ); + try { + fs.mkdirSync(targetDir); + } catch (err) { + emulatorLogger.logLabeled( + "BULLET", + "extensions", + `cache directory for ${extensionVersionRef} already exists...` + ); + } + emulatorLogger.logLabeled("BULLET", "extensions", `downloading ${sourceDownloadUri}...`); + const sourceCodeZip = await downloadUtils.downloadToTmp(sourceDownloadUri); + await unzip(sourceCodeZip, targetDir); + fs.chmodSync(targetDir, 0o755); + + emulatorLogger.logLabeled("BULLET", "extensions", `Downloaded to ${targetDir}...`); + // TODO: We should not need to do this wait + // However, when I remove this, unzipDir doesn't contain everything yet. + await new Promise((resolve) => setTimeout(resolve, 1000)); +} + function unzip(zipPath: string, unzipDir: string): Promise { return new Promise((resolve, reject) => { fs.createReadStream(zipPath) diff --git a/src/emulator/emulatorLogger.ts b/src/emulator/emulatorLogger.ts index fdb62805dd9..fce993d7aa6 100644 --- a/src/emulator/emulatorLogger.ts +++ b/src/emulator/emulatorLogger.ts @@ -33,6 +33,10 @@ export enum Verbosity { INFO = 1, QUIET = 2, } +export type ExtensionLogInfo = { + ref?: string; + instanceId?: string; +}; export class EmulatorLogger { static verbosity: Verbosity = Verbosity.DEBUG; @@ -50,7 +54,7 @@ export class EmulatorLogger { }); } - static forFunction(functionName: string) { + static forFunction(functionName: string, extensionLogInfo?: ExtensionLogInfo): EmulatorLogger { return new EmulatorLogger({ metadata: { emulator: { @@ -59,6 +63,18 @@ export class EmulatorLogger { function: { name: functionName, }, + extension: extensionLogInfo, + }, + }); + } + + static forExtension(extensionLogInfo: ExtensionLogInfo): EmulatorLogger { + return new EmulatorLogger({ + metadata: { + emulator: { + name: "extensions", + }, + extension: extensionLogInfo, }, }); } diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts new file mode 100644 index 00000000000..121e842de06 --- /dev/null +++ b/src/emulator/extensionsEmulator.ts @@ -0,0 +1,169 @@ +import * as fs from "fs-extra"; +import * as os from "os"; +import * as path from "path"; +import { spawnSync } from "child_process"; + +import * as planner from "../deploy/extensions/planner"; +import { FirebaseError } from "../error"; +import { toExtensionVersionRef } from "../extensions/refs"; +import { downloadExtensionVersion } from "./download"; +import { EmulatableBackend } from "./functionsEmulator"; +import { getExtensionFunctionInfo } from "../extensions/emulator/optionsHelper"; +import { EmulatorLogger } from "./emulatorLogger"; + +export interface ExtensionEmulatorArgs { + projectId: string; + projectNumber: string; + aliases?: string[]; + extensions: Record; + projectDir: string; +} +// TODO: Consider a different name, since this does not implement the EmulatorInstance interface +// Note: At the moment, this doesn't really seem like it needs to be a class. However, I think the +// statefulness that enables will be useful once we want to watch .env files for config changes. +export class ExtensionsEmulator { + private want: planner.InstanceSpec[] = []; + private args: ExtensionEmulatorArgs; + + constructor(args: ExtensionEmulatorArgs) { + this.args = args; + } + + // readManifest checks the `extensions` section of `firebase.json` for the extension instances to emulate, + // and the `{projectRoot}/extensions` directory for param values. + private async readManifest(): Promise { + this.want = await planner.want({ + projectId: this.args.projectId, + projectNumber: this.args.projectNumber, + aliases: this.args.aliases ?? [], + projectDir: this.args.projectDir, + extensions: this.args.extensions, + checkLocal: true, + }); + } + + // ensureSourceCode checks the cache for the source code for a given extension version, + // downloads and builds it if it is not found, then returns the path to that source code. + private async ensureSourceCode(instance: planner.InstanceSpec): Promise { + // TODO(b/213335255): Handle local extensions. + if (!instance.ref) { + throw new FirebaseError( + `No ref found for ${instance.instanceId}. Emulating local extensions is not yet supported.` + ); + } + // TODO: If ref contains 'latest', we need to resolve that to a real version. + + const ref = toExtensionVersionRef(instance.ref); + const cacheDir = + process.env.FIREBASE_EXTENSIONS_CACHE_PATH || + path.join(os.homedir(), ".cache", "firebase", "extensions"); + const sourceCodePath = path.join(cacheDir, ref); + + if (!this.hasValidSource({ path: sourceCodePath, extRef: ref })) { + const extensionVersion = await planner.getExtensionVersion(instance); + await downloadExtensionVersion(ref, extensionVersion.sourceDownloadUri, sourceCodePath); + this.installAndBuildSourceCode(sourceCodePath); + } + return sourceCodePath; + } + + /** + * Returns if the source code at given path is valid. + * + * Checks against a list of required files or directories that need to be present. + */ + private hasValidSource(args: { path: string; extRef: string }): boolean { + // TODO(lihes): Source code can technically exist in other than "functions" dir. + // https://source.corp.google.com/piper///depot/google3/firebase/mods/go/worker/fetch_mod_source.go;l=451 + const requiredFiles = [ + "./extension.yaml", + "./functions/package.json", + "./functions/node_modules", + ]; + + for (const requiredFile of requiredFiles) { + const f = path.join(args.path, requiredFile); + if (!fs.existsSync(f)) { + EmulatorLogger.forExtension({ ref: args.extRef }).logLabeled( + "BULLET", + "extensions", + `Detected invalid source code for ${args.extRef}, expected to find ${f}` + ); + return false; + } + } + + return true; + } + + private installAndBuildSourceCode(sourceCodePath: string): void { + // TODO: Add logging during this so it is clear what is happening. + const npmInstall = spawnSync("npm", ["--prefix", `/${sourceCodePath}/functions/`, "install"], { + encoding: "utf8", + }); + if (npmInstall.error) { + throw npmInstall.error; + } + + const npmRunGCPBuild = spawnSync( + "npm", + ["--prefix", `/${sourceCodePath}/functions/`, "run", "gcp-build"], + { encoding: "utf8" } + ); + if (npmRunGCPBuild.error) { + // TODO: Make sure this does not error out if "gcp-build" is not defined, but does error if it fails otherwise. + throw npmRunGCPBuild.error; + } + } + + /** + * getEmulatableBackends reads firebase.json & .env files for a list of extension instances to emulate, + * downloads & builds the necessary source code (if it hasn't previously been cached), + * then builds returns a list of emulatableBackends + * @returns A list of emulatableBackends, one for each extension instance to be emulated + */ + public async getExtensionBackends(): Promise { + await this.readManifest(); + return Promise.all( + this.want.map((i: planner.InstanceSpec) => { + return this.toEmulatableBackend(i); + }) + ); + } + + /** + * toEmulatableBackend turns a InstanceSpec into an EmulatableBackend which can be run by the Functions emulator. + * It is exported for testing. + */ + public async toEmulatableBackend(instance: planner.InstanceSpec): Promise { + const extensionDir = await this.ensureSourceCode(instance); + // TODO: This should find package.json, then use that as functionsDir. + const functionsDir = path.join(extensionDir, "functions"); + const env = Object.assign(this.autoPopulatedParams(instance), instance.params); + const { extensionTriggers, nodeMajorVersion } = await getExtensionFunctionInfo( + extensionDir, + instance.instanceId, + env + ); + const extensionVersion = await planner.getExtensionVersion(instance); + return { + functionsDir, + env, + predefinedTriggers: extensionTriggers, + nodeMajorVersion: nodeMajorVersion, + extensionInstanceId: instance.instanceId, + extensionVersion, + }; + } + + private autoPopulatedParams(instance: planner.InstanceSpec): Record { + const projectId = this.args.projectId; + return { + PROJECT_ID: projectId ?? "", // TODO: Should this fallback to a default? + EXT_INSTANCE_ID: instance.instanceId, + DATABASE_INSTANCE: projectId ?? "", + DATABASE_URL: `https://${projectId}.firebaseio.com`, + STORAGE_BUCKET: `${projectId}.appspot.com`, + }; + } +} diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 9eb7c8433d2..0e06f55ecfb 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -5,6 +5,7 @@ import * as express from "express"; import * as clc from "cli-color"; import * as http from "http"; import * as jwt from "jsonwebtoken"; +import * as cors from "cors"; import { URL } from "url"; import { Account } from "../auth"; @@ -56,6 +57,7 @@ import { } from "./adminSdkConfig"; import { EventUtils } from "./events/types"; import { functionIdsAreValid } from "../deploy/functions/validate"; +import { ExtensionVersion } from "../extensions/extensionsApi"; import { getRuntimeDelegate } from "../deploy/functions/runtimes"; import * as backend from "../deploy/functions/backend"; import * as functionsEnv from "../functions/env"; @@ -86,6 +88,18 @@ export interface EmulatableBackend { predefinedTriggers?: ParsedTriggerDefinition[]; nodeMajorVersion?: number; nodeBinary?: string; + extensionInstanceId?: string; + extensionVersion?: ExtensionVersion; +} + +/** + * BackendInfo is an API type used by the Emulator UI containing info about an Extension or CF3 module. + */ +export interface BackendInfo { + env: Record; + functionTriggers: ParsedTriggerDefinition[]; + extensionInstanceId?: string; + extensionVersion?: ExtensionVersion; } export interface FunctionsEmulatorArgs { @@ -218,6 +232,7 @@ export class FunctionsEmulator implements EmulatorInstance { this.workQueue.start(); const hub = express(); + hub.use(cors({ origin: true })); // Enable cors so the Emulator UI can call out to the Functions Emulator. const dataMiddleware: express.RequestHandler = (req, res, next) => { const chunks: Buffer[] = []; @@ -243,6 +258,9 @@ export class FunctionsEmulator implements EmulatorInstance { // A trigger named "foo" needs to respond at "foo" as well as "foo/*" but not "fooBar". const httpsFunctionRoutes = [httpsFunctionRoute, `${httpsFunctionRoute}/*`]; + // The URL for the listBackends endpoint, which is used by the Emulator UI. + const listBackendsRoute = `/backends`; + const backgroundHandler: express.RequestHandler = (req, res) => { const region = req.params.region; const triggerId = req.params.trigger_name; @@ -318,9 +336,14 @@ export class FunctionsEmulator implements EmulatorInstance { res.json({ status: "multicast_acknowledged" }); }; + const listBackendsHandler: express.RequestHandler = (req, res) => { + res.json({ backends: this.getBackendInfo() }); + }; + // The ordering here is important. The longer routes (background) // need to be registered first otherwise the HTTP functions consume // all events. + hub.get(listBackendsRoute, dataMiddleware, listBackendsHandler); hub.post(backgroundFunctionRoute, dataMiddleware, backgroundHandler); hub.post(multicastFunctionRoute, dataMiddleware, multicastHandler); hub.all(httpsFunctionRoutes, dataMiddleware, httpsHandler); @@ -784,8 +807,17 @@ export class FunctionsEmulator implements EmulatorInstance { return def.eventTrigger ? `${def.id}-${this.triggerGeneration}` : def.id; } - getBackends(): EmulatableBackend[] { - return this.args.emulatableBackends; + getBackendInfo(): BackendInfo[] { + return this.args.emulatableBackends.map((e: EmulatableBackend) => { + return { + env: e.env, + extensionInstanceId: e.extensionInstanceId, + extensionVersion: e.extensionVersion, + functionTriggers: e.predefinedTriggers ?? [], + // TODO: Right now, functionTriggers will be an empty list for CF3 backends. + // We need to figure out how to expose the loaded triggers here here. + }; + }); } addTriggerRecord( @@ -1155,8 +1187,11 @@ export class FunctionsEmulator implements EmulatorInstance { return childProcess.send(JSON.stringify(args)); }, }; - - this.workerPool.addWorker(trigger.id, runtime); + const extensionLogInfo = { + instanceId: backend.extensionInstanceId, + ref: backend.extensionVersion?.ref, + }; + this.workerPool.addWorker(trigger.id, runtime, extensionLogInfo); return this.workerPool.submitWork(trigger.id, frb, opts); } diff --git a/src/emulator/functionsEmulatorUtils.ts b/src/emulator/functionsEmulatorUtils.ts index fcf7768488d..abd7b2c60c0 100644 --- a/src/emulator/functionsEmulatorUtils.ts +++ b/src/emulator/functionsEmulatorUtils.ts @@ -80,7 +80,7 @@ export function parseRuntimeVersion(runtime?: string): number | undefined { } const runtimeRe = /(nodejs)?([0-9]+)/; - const match = runtime.match(runtimeRe); + const match = runtimeRe.exec(runtime); if (match) { return Number.parseInt(match[2]); } diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index 9b18ac16802..371e32107c4 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -7,7 +7,7 @@ import { getTemporarySocketPath, } from "./functionsEmulatorShared"; import { EventEmitter } from "events"; -import { EmulatorLogger } from "./emulatorLogger"; +import { EmulatorLogger, ExtensionLogInfo } from "./emulatorLogger"; import { FirebaseError } from "../error"; type LogListener = (el: EmulatorLog) => any; @@ -256,7 +256,11 @@ export class RuntimeWorkerPool { return; } - addWorker(triggerId: string | undefined, runtime: FunctionsRuntimeInstance): RuntimeWorker { + addWorker( + triggerId: string | undefined, + runtime: FunctionsRuntimeInstance, + extensionLogInfo?: ExtensionLogInfo + ): RuntimeWorker { const worker = new RuntimeWorker(this.getKey(triggerId), runtime); this.log(`addWorker(${worker.key})`); @@ -265,7 +269,7 @@ export class RuntimeWorkerPool { this.setTriggerWorkers(triggerId, keyWorkers); const logger = triggerId - ? EmulatorLogger.forFunction(triggerId) + ? EmulatorLogger.forFunction(triggerId, extensionLogInfo) : EmulatorLogger.forEmulator(Emulators.FUNCTIONS); worker.onLogs((log: EmulatorLog) => { logger.handleRuntimeLog(log); diff --git a/src/emulator/loggingEmulator.ts b/src/emulator/loggingEmulator.ts index e2b69110dd0..973a8c63652 100644 --- a/src/emulator/loggingEmulator.ts +++ b/src/emulator/loggingEmulator.ts @@ -24,6 +24,10 @@ export interface LogData { function?: { name: string; }; + extension?: { + ref?: string; + instanceId?: string; + }; }; } diff --git a/src/emulator/registry.ts b/src/emulator/registry.ts index 016899e9b2b..f07d8a74c6d 100644 --- a/src/emulator/registry.ts +++ b/src/emulator/registry.ts @@ -48,9 +48,13 @@ export class EmulatorRegistry { // once shutdown starts ui: 0, + // The Extensions emulator runs on the same process as the Functions emulator + // so this is a no-op. We put this before functions for future proofing, since + // the Extensions emulator depends on the Functions emulator. + extensions: 1, // Functions is next since it has side effects and // dependencies across all the others - functions: 1, + functions: 1.1, // Hosting is next because it can trigger functions. hosting: 2, diff --git a/src/emulator/types.ts b/src/emulator/types.ts index 35d72b4e50e..5571515028b 100644 --- a/src/emulator/types.ts +++ b/src/emulator/types.ts @@ -13,6 +13,7 @@ export enum Emulators { UI = "ui", LOGGING = "logging", STORAGE = "storage", + EXTENSIONS = "extensions", } export type DownloadableEmulators = @@ -60,6 +61,7 @@ export const EMULATORS_SUPPORTED_BY_UI = [ Emulators.FIRESTORE, Emulators.FUNCTIONS, Emulators.STORAGE, + Emulators.EXTENSIONS, ]; export const EMULATORS_SUPPORTED_BY_USE_EMULATOR = [ diff --git a/src/extensions/emulator/optionsHelper.ts b/src/extensions/emulator/optionsHelper.ts index f330d4676d7..27ffe24a67a 100644 --- a/src/extensions/emulator/optionsHelper.ts +++ b/src/extensions/emulator/optionsHelper.ts @@ -15,9 +15,9 @@ import { needProjectId } from "../../projectUtils"; import { Emulators } from "../../emulator/types"; export async function buildOptions(options: any): Promise { - const extensionDir = localHelper.findExtensionYaml(process.cwd()); - options.extensionDir = extensionDir; - const spec = await specHelper.readExtensionYaml(extensionDir); + const extDevDir = localHelper.findExtensionYaml(process.cwd()); + options.extDevDir = extDevDir; + const spec = await specHelper.readExtensionYaml(extDevDir); extensionsHelper.validateSpec(spec); const params = getParams(options, spec); @@ -34,15 +34,39 @@ export async function buildOptions(options: any): Promise { checkTestConfig(testConfig, functionResources); } options.config = buildConfig(functionResources, testConfig); - options.extensionEnv = params; + options.extDevEnv = params; const functionEmuTriggerDefs: ParsedTriggerDefinition[] = functionResources.map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r) ); - options.extensionTriggers = functionEmuTriggerDefs; - options.extensionNodeVersion = specHelper.getNodeVersion(functionResources); + options.extDevTriggers = functionEmuTriggerDefs; + options.extDevNodeVersion = specHelper.getNodeVersion(functionResources); return options; } +// TODO: Better name? Also, should this be in extensionsEmulator instead? +export async function getExtensionFunctionInfo( + extensionDir: string, + instanceId: string, + params: Record +): Promise<{ + nodeMajorVersion: number; + extensionTriggers: ParsedTriggerDefinition[]; +}> { + const spec = await specHelper.readExtensionYaml(extensionDir); + const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, params); + const extensionTriggers: ParsedTriggerDefinition[] = functionResources + .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r)) + .map((trigger) => { + trigger.name = `ext-${instanceId}-${trigger.name}`; + return trigger; + }); + const nodeMajorVersion = specHelper.getNodeVersion(functionResources); + return { + extensionTriggers, + nodeMajorVersion, + }; +} + // Exported for testing export function getParams(options: any, extensionSpec: ExtensionSpec) { const projectId = needProjectId(options); diff --git a/src/extensions/emulator/specHelper.ts b/src/extensions/emulator/specHelper.ts index 2c7a218b32b..5120b4a3322 100644 --- a/src/extensions/emulator/specHelper.ts +++ b/src/extensions/emulator/specHelper.ts @@ -6,8 +6,7 @@ import * as fs from "fs-extra"; import { ExtensionSpec, Resource } from "../extensionsApi"; import { FirebaseError } from "../../error"; import { substituteParams } from "../extensionsHelper"; -import { EmulatorLogger } from "../../emulator/emulatorLogger"; -import { Emulators } from "../../emulator/types"; +import { parseRuntimeVersion } from "../../emulator/functionsEmulatorUtils"; const SPEC_FILE = "extension.yaml"; const validFunctionTypes = [ @@ -73,7 +72,7 @@ export function readFileFromDirectory( export function getFunctionResourcesWithParamSubstitution( extensionSpec: ExtensionSpec, params: { [key: string]: string } -): object[] { +): Resource[] { const rawResources = extensionSpec.resources.filter((resource) => validFunctionTypes.includes(resource.type) ); @@ -84,37 +83,19 @@ export function getFunctionProperties(resources: Resource[]) { return resources.map((r) => r.properties); } -/** - * Choses a node version to use based on the 'nodeVersion' field in resources. - * Currently, the emulator will use 1 node version for all functions, even though - * an extension can specify different node versions for each function when deployed. - * For now, we choose the newest version that a user lists in their function resources, - * and fall back to node 8 if none is listed. - */ -export function getNodeVersion(resources: Resource[]): string { - const functionNamesWithoutRuntime: string[] = []; +export function getNodeVersion(resources: Resource[]): number { + const invalidRuntimes: string[] = []; const versions = resources.map((r: Resource) => { - if (_.includes(r.type, "function")) { - if (r.properties?.runtime) { - return r.properties?.runtime; + if (r.properties?.runtime) { + const runtimeName = r.properties?.runtime as string; + const runtime = parseRuntimeVersion(runtimeName); + if (!runtime) { + invalidRuntimes.push(runtimeName); } else { - functionNamesWithoutRuntime.push(r.name); + return runtime; } } - return "nodejs8"; - }); - - if (functionNamesWithoutRuntime.length) { - EmulatorLogger.forEmulator(Emulators.FUNCTIONS).logLabeled( - "WARN", - "extensions", - `No 'runtime' property found for the following functions, defaulting to nodejs8: ${functionNamesWithoutRuntime.join( - ", " - )}` - ); - } - const invalidRuntimes = _.filter(versions, (v) => { - return !_.includes(v, "nodejs"); + return 14; }); if (invalidRuntimes.length) { @@ -124,16 +105,5 @@ export function getNodeVersion(resources: Resource[]): string { )}. \n Only Node runtimes are supported.` ); } - if (_.includes(versions, "nodejs10")) { - return "10"; - } - if (_.includes(versions, "nodejs6")) { - EmulatorLogger.forEmulator(Emulators.FUNCTIONS).logLabeled( - "WARN", - "extensions", - "Node 6 is deprecated. We recommend upgrading to a newer version." - ); - return "6"; - } - return "8"; + return Math.max(...versions); } diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index 92d6418eb62..b31a6eff622 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -159,6 +159,7 @@ export type EmulatorsConfig = { host?: string; port?: number | string; }; + extensions?: {}; }; export type ExtensionsConfig = Record; diff --git a/src/previews.ts b/src/previews.ts index a63e44e3170..81589dda6dc 100644 --- a/src/previews.ts +++ b/src/previews.ts @@ -5,6 +5,7 @@ interface PreviewFlags { rtdbrules: boolean; ext: boolean; extdev: boolean; + extensionsemulator: boolean; rtdbmanagement: boolean; functionsv2: boolean; golang: boolean; @@ -18,6 +19,7 @@ export const previews: PreviewFlags = { rtdbrules: false, ext: false, extdev: false, + extensionsemulator: false, rtdbmanagement: false, functionsv2: false, golang: false, diff --git a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/extension.yaml b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/extension.yaml new file mode 100644 index 00000000000..dbda3da8e4a --- /dev/null +++ b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/extension.yaml @@ -0,0 +1,226 @@ +# Copyright 2019 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. + +name: storage-resize-images +version: 0.1.18 +specVersion: v1beta + +displayName: Resize Images +description: Resizes images uploaded to Cloud Storage to a specified size, and optionally keeps or deletes the original image. + +license: Apache-2.0 + +sourceUrl: https://github.com/firebase/extensions/tree/master/storage-resize-images +releaseNotesUrl: https://github.com/firebase/extensions/blob/master/storage-resize-images/CHANGELOG.md + +author: + authorName: Firebase + url: https://firebase.google.com + +contributors: + - authorName: Tina Liang + url: https://github.com/tinaliang + - authorName: Chris Bianca + email: chris@csfrequency.com + url: https://github.com/chrisbianca + - authorName: Invertase + email: oss@invertase.io + url: https://github.com/invertase + +billingRequired: true + +apis: + - apiName: storage-component.googleapis.com + reason: Needed to use Cloud Storage + +roles: + - role: storage.admin + reason: Allows the extension to store resized images in Cloud Storage + +resources: + - name: generateResizedImage + type: firebaseextensions.v1beta.function + description: >- + Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, + then stores the resized images in the same bucket. Optionally keeps or deletes the original images. + properties: + location: ${param:LOCATION} + runtime: nodejs10 + eventTrigger: + eventType: google.storage.object.finalize + resource: projects/_/buckets/${param:IMG_BUCKET} + +params: + - param: LOCATION + label: Cloud Functions location + description: >- + Where do you want to deploy the functions created for this extension? + You usually want a location close to your Storage bucket. For help selecting a + location, refer to the [location selection + guide](https://firebase.google.com/docs/functions/locations). + type: select + options: + - label: Iowa (us-central1) + value: us-central1 + - label: South Carolina (us-east1) + value: us-east1 + - label: Northern Virginia (us-east4) + value: us-east4 + - label: Los Angeles (us-west2) + value: us-west2 + - label: Salt Lake City (us-west3) + value: us-west3 + - label: Las Vegas (us-west4) + value: us-west4 + - label: Belgium (europe-west1) + value: europe-west1 + - label: London (europe-west2) + value: europe-west2 + - label: Frankfurt (europe-west3) + value: europe-west3 + - label: Zurich (europe-west6) + value: europe-west6 + - label: Hong Kong (asia-east2) + value: asia-east2 + - label: Tokyo (asia-northeast1) + value: asia-northeast1 + - label: Osaka (asia-northeast2) + value: asia-northeast2 + - label: Seoul (asia-northeast3) + value: asia-northeast3 + - label: Mumbai (asia-south1) + value: asia-south1 + - label: Jakarta (asia-southeast2) + value: asia-southeast2 + - label: Montreal (northamerica-northeast1) + value: northamerica-northeast1 + - label: Sao Paulo (southamerica-east1) + value: southamerica-east1 + - label: Sydney (australia-southeast1) + value: australia-southeast1 + default: us-central1 + required: true + immutable: true + + - param: IMG_BUCKET + label: Cloud Storage bucket for images + description: > + To which Cloud Storage bucket will you upload images that you want to resize? + Resized images will be stored in this bucket. Depending on your extension configuration, + original images are either kept or deleted. + type: string + example: my-project-12345.appspot.com + validationRegex: ^([0-9a-z_.-]*)$ + validationErrorMessage: Invalid storage bucket + default: ${STORAGE_BUCKET} + required: true + + - param: IMG_SIZES + label: Sizes of resized images + description: > + What sizes of images would you like (in pixels)? Enter the sizes as a + comma-separated list of WIDTHxHEIGHT values. Learn more about + [how this parameter works](https://firebase.google.com/products/extensions/storage-resize-images). + type: string + example: "200x200" + validationRegex: ^\d+x(\d+,\d+x)*\d+$ + validationErrorMessage: Invalid sizes, must be a comma-separated list of WIDTHxHEIGHT values. + default: "200x200" + required: true + + - param: DELETE_ORIGINAL_FILE + label: Deletion of original file + description: >- + Do you want to automatically delete the original file from the Cloud Storage + bucket? Note that these deletions cannot be undone. + type: select + options: + - label: Yes + value: true + - label: No + value: false + - label: Delete on successful resize + value: on_success + default: false + required: true + + - param: RESIZED_IMAGES_PATH + label: Cloud Storage path for resized images + description: > + A relative path in which to store resized images. For example, + if you specify a path here of `thumbs` and you upload an image to + `/images/original.jpg`, then the resized image is stored at + `/images/thumbs/original_200x200.jpg`. If you prefer to store resized + images at the root of your bucket, leave this field empty. + example: thumbnails + required: false + + - param: INCLUDE_PATH_LIST + label: Paths that contain images you want to resize + description: > + Restrict storage-resize-images to only resize images in specific locations in your Storage bucket by + supplying a comma-separated list of absolute paths. For example, to only resize the images + stored in `/users/pictures` directory, specify the path `/users/pictures`. + If you prefer to resize every image uploaded to the storage bucket, + leave this field empty. + type: string + example: "/users/avatars,/design/pictures" + validationRegex: ^(\/[^\s\/\,]+)+(\,(\/[^\s\/\,]+)+)*$ + validationErrorMessage: Invalid paths, must be a comma-separated list of absolute path values. + required: false + + - param: EXCLUDE_PATH_LIST + label: List of absolute paths not included for resized images + description: > + A comma-separated list of absolute paths to not take into account for + images to be resized. For example, to not resize the images + stored in `/users/pictures/avatars` directory, specify the path + `/users/pictures/avatars`. If you prefer to resize every image uploaded + to the storage bucket, leave this field empty. + type: string + example: "/users/avatars/thumbs,/design/pictures/thumbs" + validationRegex: ^(\/[^\s\/\,]+)+(\,(\/[^\s\/\,]+)+)*$ + validationErrorMessage: Invalid paths, must be a comma-separated list of absolute path values. + required: false + + - param: CACHE_CONTROL_HEADER + label: Cache-Control header for resized images + description: > + This extension automatically copies any `Cache-Control` metadata from the original image + to the resized images. For the resized images, do you want to overwrite this copied + `Cache-Control` metadata or add `Cache-Control` metadata? Learn more about + [`Cache-Control` headers](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). + If you prefer not to overwrite or add `Cache-Control` metadata, leave this field empty. + example: max-age=86400 + required: false + + - param: IMAGE_TYPE + label: Convert image to preferred type + description: > + The image type you'd like your source image to convert to. The default for this option will + be to keep the original file type. + type: select + options: + - label: jpg + value: jpg + - label: png + value: png + - label: webp + value: webp + - label: tiff + value: tiff + - label: Do not convert + value: false + default: false + required: false diff --git a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/.gitignore b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/.gitignore new file mode 100644 index 00000000000..9dc671d9430 --- /dev/null +++ b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/.gitignore @@ -0,0 +1,2 @@ +#include node_modules here for testing. +!/node_modules diff --git a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/test_data.text b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/test_data.text new file mode 100644 index 00000000000..739ae61c52a --- /dev/null +++ b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/test_data.text @@ -0,0 +1 @@ +Empty file to hold the directory. diff --git a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json new file mode 100644 index 00000000000..d17601ddffd --- /dev/null +++ b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json @@ -0,0 +1,3 @@ +{ + "what's this": "Empty file for testing" +} diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts new file mode 100644 index 00000000000..298f02f56eb --- /dev/null +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -0,0 +1,98 @@ +import { expect } from "chai"; + +import { ExtensionsEmulator } from "../../emulator/extensionsEmulator"; +import { EmulatableBackend } from "../../emulator/functionsEmulator"; +import { ExtensionVersion } from "../../extensions/extensionsApi"; +import * as planner from "../../deploy/extensions/planner"; + +const TEST_EXTENSION_VERSION: ExtensionVersion = { + name: "publishers/firebase/extensions/storage-resize-images/versions/0.1.18", + ref: "firebase/storage-resize-images@0.1.18", + state: "PUBLISHED", + sourceDownloadUri: "https://fake.test", + hash: "abc123", + spec: { + name: "publishers/firebase/extensions/storage-resize-images/versions/0.1.18", + resources: [], + params: [], + version: "0.1.18", + sourceUrl: "https://fake.test", + }, +}; + +describe("Extensions Emulator", () => { + describe("toEmulatableBackends", () => { + let previousCachePath: string | undefined; + beforeEach(() => { + previousCachePath = process.env.FIREBASE_EXTENSIONS_CACHE_PATH; + process.env.FIREBASE_EXTENSIONS_CACHE_PATH = "./src/test/emulators/extensions"; + }); + afterEach(() => { + process.env.FIREBASE_EXTENSIONS_CACHE_PATH = previousCachePath; + }); + const testCases: { + desc: string; + input: planner.InstanceSpec; + expected: EmulatableBackend; + }[] = [ + { + desc: "should transform a instance spec to a backend", + input: { + instanceId: "ext-test", + ref: { + publisherId: "firebase", + extensionId: "storage-resize-images", + version: "0.1.18", + }, + params: { + LOCATION: "us-west1", + }, + extensionVersion: TEST_EXTENSION_VERSION, + }, + expected: { + env: { + LOCATION: "us-west1", + DATABASE_INSTANCE: "test-project", + DATABASE_URL: "https://test-project.firebaseio.com", + EXT_INSTANCE_ID: "ext-test", + PROJECT_ID: "test-project", + STORAGE_BUCKET: "test-project.appspot.com", + }, + extensionInstanceId: "ext-test", + functionsDir: + "src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions", + nodeMajorVersion: 10, + predefinedTriggers: [ + { + entryPoint: "generateResizedImage", + eventTrigger: { + eventType: "google.storage.object.finalize", + resource: "projects/_/buckets/${param:IMG_BUCKET}", + service: "storage.googleapis.com", + }, + name: "ext-ext-test-generateResizedImage", + platform: "gcfv1", + regions: ["us-west1"], + }, + ], + extensionVersion: TEST_EXTENSION_VERSION, + }, + }, + ]; + for (const testCase of testCases) { + it(testCase.desc, async () => { + const e = new ExtensionsEmulator({ + projectId: "test-project", + projectNumber: "1234567", + projectDir: ".", + extensions: {}, + aliases: [], + }); + + const result = await e.toEmulatableBackend(testCase.input); + + expect(result).to.deep.equal(testCase.expected); + }); + } + }); +}); From 5341609bc5d04d8942bdd25760ef6bdc6de43a9e Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Fri, 18 Feb 2022 15:58:52 -0500 Subject: [PATCH 0093/1699] Refactor ext manifest building logic to be more general so it can be reused by --local (#4194) * Emulate extensions from firebase.json (#4096) * Adds ability to download and emulate extensions during emulators:start and emulators:exec * format and self review * Successfully emulating extensions!" * formats * self review * handle case when extensions are not defined in firebase.json * Don't error out on fake project ids * adding unit test for toEmulatableBackend * pr fixes * adding todo * Adding listBackends endpoint to support Emulator UI (#4122) * Adds ability to download and emulate extensions during emulators:start and emulators:exec * format and self review * Successfully emulating extensions!" * formats * self review * handle case when extensions are not defined in firebase.json * Don't error out on fake project ids * adding unit test for toEmulatableBackend * pr fixes * starting on listBackends API * Adding listBackends endpoint to support Emulator UI * fix tests * clarifying todo * Switching extensions emulator to use the same function IDs as prod (#4143) * Adding support for --only emulators (#4141) * Adding support for --only emulators * Regenerating schema * pr fixes * Renaming some variables to make it clear which parts are only relevant for ext:dev:emulators:* commands (#4150) * Revert "Merging master" This reverts commit b71529aabd00227f1c1d12c375a754b23f1038e6, reversing changes made to 132684a1054a1a75fa42c1361cf4d5308c3a5295. * Revert "Revert "Merging master"" This reverts commit ca6d63d97c7febe570a0668ac2d0830fdfd01506. * Fixing small merge conmflict * adding preview flag for emulator (#4187) * Adding cors & START_LOGGING_EMULATOR to support emulator UI (#4191) * Validate extension source code before running in emulator (#4186) * Validate extension source code before running in emulator * Improve logging * Fix tests * Fix format * Address comments * refactor * Adding extension metadata to structured logs (#4190) * Adding extension metadata to structured logs * formats * fixing other logs * format * address comments * add todos Co-authored-by: joehan --- src/commands/ext-export.ts | 18 ++++++++-- src/extensions/export.ts | 60 ------------------------------- src/extensions/manifest.ts | 74 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 63 deletions(-) create mode 100644 src/extensions/manifest.ts diff --git a/src/commands/ext-export.ts b/src/commands/ext-export.ts index 34b23a10bb5..17704f55db2 100644 --- a/src/commands/ext-export.ts +++ b/src/commands/ext-export.ts @@ -1,16 +1,19 @@ import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; +import { Config } from "../config"; import * as planner from "../deploy/extensions/planner"; +import { FirebaseError } from "../error"; import { displayExportInfo, parameterizeProject, setSecretParamsToLatest, - writeFiles, } from "../extensions/export"; import { ensureExtensionsApiEnabled } from "../extensions/extensionsHelper"; +import { writeToManifest } from "../extensions/manifest"; import { partition } from "../functional"; import { getProjectNumber } from "../getProjectNumber"; import { logger } from "../logger"; +import { Options } from "../options"; import { needProjectId } from "../projectUtils"; import { promptOnce } from "../prompt"; import { requirePermissions } from "../requirePermissions"; @@ -23,7 +26,7 @@ module.exports = new Command("ext:export") .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") .withForce() - .action(async (options: any) => { + .action(async (options: Options) => { const projectId = needProjectId(options); const projectNumber = await getProjectNumber(options); // Look up the instances that already exist, @@ -63,5 +66,14 @@ module.exports = new Command("ext:export") return; } - await writeFiles(withRef, options); + const existingConfig = Config.load(options, true); + if (!existingConfig) { + throw new FirebaseError( + "Not currently in a Firebase directory. Please run `firebase init` to create a Firebase directory." + ); + } + await writeToManifest(withRef, existingConfig, { + nonInteractive: options.nonInteractive, + force: options.force, + }); }); diff --git a/src/extensions/export.ts b/src/extensions/export.ts index 6da6b63851e..94525f573d7 100644 --- a/src/extensions/export.ts +++ b/src/extensions/export.ts @@ -1,17 +1,8 @@ -import * as clc from "cli-color"; - -import * as refs from "./refs"; -import { getProjectNumber } from "../getProjectNumber"; -import { Options } from "../options"; -import { Config } from "../config"; import { getExtensionVersion, InstanceSpec } from "../deploy/extensions/planner"; import { humanReadable } from "../deploy/extensions/deploymentSummary"; import { logger } from "../logger"; -import { FirebaseError } from "../error"; -import { promptOnce } from "../prompt"; import { parseSecretVersionResourceName, toSecretVersionResourceName } from "../gcp/secretManager"; import { getActiveSecrets } from "./secretsUtils"; - /** * parameterizeProject searchs spec.params for any param that include projectId or projectNumber, * and replaces it with a parameterized version that can be used on other projects. @@ -82,54 +73,3 @@ function displaySpecs(specs: InstanceSpec[]): void { logger.info(""); } } - -function writeExtensionsToFirebaseJson(have: InstanceSpec[], existingConfig: Config): void { - const extensions = existingConfig.get("extensions", {}); - for (const s of have) { - extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref!); - } - existingConfig.set("extensions", extensions); - logger.info("Adding Extensions to " + clc.bold("firebase.json") + "..."); - existingConfig.writeProjectFile("firebase.json", existingConfig.src); -} - -async function writeEnvFile(spec: InstanceSpec, existingConfig: Config, force?: boolean) { - const content = Object.entries(spec.params) - .map((r) => `${r[0]}=${r[1]}`) - .join("\n"); - await existingConfig.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force); -} - -export async function writeFiles(have: InstanceSpec[], options: Options) { - const existingConfig = Config.load(options, true); - if (!existingConfig) { - throw new FirebaseError( - "Not currently in a Firebase directory. Please run `firebase init` to create a Firebase directory." - ); - } - if ( - existingConfig.has("extensions") && - Object.keys(existingConfig.get("extensions")).length && - !options.nonInteractive && - !options.force - ) { - const currentExtensions = Object.entries(existingConfig.get("extensions")) - .map((i) => `${i[0]}: ${i[1]}`) - .join("\n\t"); - const overwrite = await promptOnce({ - type: "list", - message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`, - choices: [ - { name: "Overwrite", value: true }, - { name: "Merge", value: false }, - ], - }); - if (overwrite) { - existingConfig.set("extensions", {}); - } - } - writeExtensionsToFirebaseJson(have, existingConfig); - for (const spec of have) { - await writeEnvFile(spec, existingConfig, options.force); - } -} diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts new file mode 100644 index 00000000000..6b5f070eb56 --- /dev/null +++ b/src/extensions/manifest.ts @@ -0,0 +1,74 @@ +import * as clc from "cli-color"; + +import * as refs from "./refs"; +import { Config } from "../config"; +import { InstanceSpec } from "../deploy/extensions/planner"; +import { logger } from "../logger"; +import { promptOnce } from "../prompt"; + +/** + * Write a list of instanceSpecs to extensions manifest. + * + * The manifest is composed of both the extension instance list in firebase.json, and + * env-var for each extension instance under ./extensions/*.env + * + * @param specs a list of InstanceSpec to write to the manifest + * @param config existing config in firebase.json + * @param options.nonInteractive will try to do the job without asking for user input. + * @param options.force only when this flag is true this will overwrite existing .env files + */ +export async function writeToManifest( + specs: InstanceSpec[], + config: Config, + options: { nonInteractive: boolean; force: boolean } +): Promise { + if ( + config.has("extensions") && + Object.keys(config.get("extensions")).length && + !options.nonInteractive && + !options.force + ) { + const currentExtensions = Object.entries(config.get("extensions")) + .map((i) => `${i[0]}: ${i[1]}`) + .join("\n\t"); + const overwrite = await promptOnce({ + type: "list", + message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`, + choices: [ + { name: "Overwrite", value: true }, + { name: "Merge", value: false }, + ], + }); + if (overwrite) { + config.set("extensions", {}); + } + } + + writeExtensionsToFirebaseJson(specs, config); + await writeEnvFiles(specs, config, options.force); +} + +// TODO(lihes): Add some tests. +function writeExtensionsToFirebaseJson(specs: InstanceSpec[], config: Config): void { + const extensions = config.get("extensions", {}); + for (const s of specs) { + extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref!); + } + config.set("extensions", extensions); + logger.info("Adding Extensions to " + clc.bold("firebase.json") + "..."); + config.writeProjectFile("firebase.json", config.src); +} + +// TODO(lihes): Add some tests. +async function writeEnvFiles( + specs: InstanceSpec[], + config: Config, + force?: boolean +): Promise { + for (const spec of specs) { + const content = Object.entries(spec.params) + .map((r) => `${r[0]}=${r[1]}`) + .join("\n"); + await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force); + } +} From 37134a22aae32510240487f46fd66c0272d55846 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 18 Feb 2022 13:49:18 -0800 Subject: [PATCH 0094/1699] Enable parsing function triggers via functions.yaml (#4124) With this change, CF3 users who've opted in for functionsv2 preview will now have their function triggers be parsed via the new container contract based mechanism. This relies on having the latest version of the Firebase Functions SDK, so we will fall back to the good ol' parseTrigger script when we detect an incompatible version. The PR includes various other changes we've made to the Manifest - e.g. explicit callableTrigger type, minor tweaks to the vpc trigger setting, supplying correct `requiredAPIs` in parseTrigger script, etc.) --- src/deploy/functions/backend.ts | 39 +++++++++-- src/deploy/functions/prepare.ts | 2 +- src/deploy/functions/release/planner.ts | 5 +- src/deploy/functions/release/reporter.ts | 5 ++ .../functions/runtimes/discovery/index.ts | 12 ++-- .../functions/runtimes/discovery/parsing.ts | 2 +- .../functions/runtimes/discovery/v1alpha1.ts | 33 +++++---- src/deploy/functions/runtimes/golang/index.ts | 2 +- src/deploy/functions/runtimes/node/index.ts | 69 +++++++++++++++++++ .../functions/runtimes/node/parseTriggers.ts | 40 +++++++++-- .../functions/runtimes/node/versioning.ts | 2 +- src/gcp/cloudfunctions.ts | 27 ++++++-- src/gcp/cloudfunctionsv2.ts | 26 +++++-- src/test/deploy/functions/backend.spec.ts | 2 +- .../runtimes/discovery/index.spec.ts | 4 +- .../runtimes/discovery/v1alpha1.spec.ts | 13 ++-- .../runtimes/node/parseTriggers.spec.ts | 29 +++++--- src/test/gcp/cloudfunctions.spec.ts | 17 +++-- src/test/gcp/cloudfunctionsv2.spec.ts | 15 ++-- 19 files changed, 271 insertions(+), 73 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 3fe7cf5d6a1..30f9d2f0858 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -40,6 +40,14 @@ export interface HttpsTriggered { httpsTrigger: HttpsTrigger; } +/** API agnostic version of a Firebase callable function. */ +export type CallableTrigger = Record; + +/** Something that has a callable trigger */ +export interface CallableTriggered { + callableTrigger: CallableTrigger; +} + /** Well known keys in the eventFilter attribute of an event trigger */ export type EventFilterKey = "resource"; @@ -119,6 +127,8 @@ export function endpointTriggerType(endpoint: Endpoint): string { return "scheduled"; } else if (isHttpsTriggered(endpoint)) { return "https"; + } else if (isCallableTriggered(endpoint)) { + return "callable"; } else if (isEventTriggered(endpoint)) { return endpoint.eventTrigger.eventType; } else if (isTaskQueueTriggered(endpoint)) { @@ -184,21 +194,33 @@ export interface ServiceConfiguration { timeout?: proto.Duration; maxInstances?: number; minInstances?: number; - vpcConnector?: string; - vpcConnectorEgressSettings?: VpcEgressSettings; + vpc?: { + connector: string; + egressSettings?: VpcEgressSettings; + }; ingressSettings?: IngressSettings; serviceAccountEmail?: "default" | string; } export type FunctionsPlatform = "gcfv1" | "gcfv2"; -export type Triggered = HttpsTriggered | EventTriggered | ScheduleTriggered | TaskQueueTriggered; +export type Triggered = + | HttpsTriggered + | CallableTriggered + | EventTriggered + | ScheduleTriggered + | TaskQueueTriggered; /** Whether something has an HttpsTrigger */ export function isHttpsTriggered(triggered: Triggered): triggered is HttpsTriggered { return {}.hasOwnProperty.call(triggered, "httpsTrigger"); } +/** Whether something has a CallableTrigger */ +export function isCallableTriggered(triggered: Triggered): triggered is CallableTriggered { + return {}.hasOwnProperty.call(triggered, "callableTrigger"); +} + /** Whether something has an EventTrigger */ export function isEventTriggered(triggered: Triggered): triggered is EventTriggered { return {}.hasOwnProperty.call(triggered, "eventTrigger"); @@ -235,14 +257,17 @@ export type Endpoint = TargetIds & sourceUploadUrl?: string; }; +export interface RequiredAPI { + reason: string; + api: string; +} + /** An API agnostic definition of an entire deployment a customer has or wants. */ export interface Backend { /** * requiredAPIs will be enabled when a Backend is deployed. - * Their format is friendly name -> API name. - * E.g. "scheduler" => "cloudscheduler.googleapis.com" */ - requiredAPIs: Record; + requiredAPIs: RequiredAPI[]; environmentVariables: EnvironmentVariables; // region -> id -> Endpoint endpoints: Record>; @@ -255,7 +280,7 @@ export interface Backend { */ export function empty(): Backend { return { - requiredAPIs: {}, + requiredAPIs: [], endpoints: {}, environmentVariables: {}, }; diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 8c970a7d79e..34f0b29bd21 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -144,7 +144,7 @@ export async function prepare( // require cloudscheudler and, in v1, require pub/sub), or can eventually come from // explicit dependencies. await Promise.all( - Object.values(wantBackend.requiredAPIs).map((api) => { + Object.values(wantBackend.requiredAPIs).map(({ api }) => { return ensureApiEnabled.ensure(projectId, api, "functions", /* silent=*/ false); }) ); diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index 3b844e58e81..dcb6e1d33d9 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -131,7 +131,8 @@ export function upgradedToGCFv2WithoutSettingConcurrency( }); } -/** Whether a trigger chagned regions. This can happen if, for example, +/** + * Whether a trigger chagned regions. This can happen if, for example, * a user listens to a different bucket, which happens to have a different region. */ export function changedTriggerRegion(want: backend.Endpoint, have: backend.Endpoint): boolean { @@ -200,6 +201,8 @@ export function checkForIllegalUpdate(want: backend.Endpoint, have: backend.Endp const triggerType = (e: backend.Endpoint): string => { if (backend.isHttpsTriggered(e)) { return "an HTTPS"; + } else if (backend.isCallableTriggered(e)) { + return "a callable"; } else if (backend.isEventTriggered(e)) { return "a background triggered"; } else if (backend.isScheduleTriggered(e)) { diff --git a/src/deploy/functions/release/reporter.ts b/src/deploy/functions/release/reporter.ts index 87eeebe9b7a..35c219ada8c 100644 --- a/src/deploy/functions/release/reporter.ts +++ b/src/deploy/functions/release/reporter.ts @@ -233,7 +233,12 @@ export function triggerTag(endpoint: backend.Endpoint): string { return `${prefix}.taskQueue`; } + if (backend.isCallableTriggered(endpoint)) { + return `${prefix}.callable`; + } + if (backend.isHttpsTriggered(endpoint)) { + // NOTE: Legacy trigger annotation relies on a special label to differentiate http vs callable triggers. if (endpoint.labels?.["deployment-callable"]) { return `${prefix}.callable`; } diff --git a/src/deploy/functions/runtimes/discovery/index.ts b/src/deploy/functions/runtimes/discovery/index.ts index dc0ec78a443..b9e83292e84 100644 --- a/src/deploy/functions/runtimes/discovery/index.ts +++ b/src/deploy/functions/runtimes/discovery/index.ts @@ -41,17 +41,17 @@ export async function detectFromYaml( ): Promise { let text: string; try { - text = await exports.readFileAsync(path.join(directory, "backend.yaml"), "utf8"); + text = await exports.readFileAsync(path.join(directory, "functions.yaml"), "utf8"); } catch (err: any) { if (err.code === "ENOENT") { - logger.debug("Could not find backend.yaml. Must use http discovery"); + logger.debug("Could not find functions.yaml. Must use http discovery"); } else { - logger.debug("Unexpected error looking for backend.yaml file:", err); + logger.debug("Unexpected error looking for functions.yaml file:", err); } return; } - logger.debug("Found backend.yaml. Got spec:", text); + logger.debug("Found functions.yaml. Got spec:", text); const parsed = yaml.load(text); return yamlToBackend(parsed, project, api.functionsDefaultRegion, runtime); } @@ -72,7 +72,7 @@ export async function detectFromPort( while (true) { try { - res = await Promise.race([fetch(`http://localhost:${port}/backend.yaml`), timedOut]); + res = await Promise.race([fetch(`http://localhost:${port}/__/functions.yaml`), timedOut]); break; } catch (err: any) { // Allow us to wait until the server is listening. @@ -84,7 +84,7 @@ export async function detectFromPort( } const text = await res.text(); - logger.debug("Got response from /backend.yaml", text); + logger.debug("Got response from /__/functions.yaml", text); let parsed: any; try { diff --git a/src/deploy/functions/runtimes/discovery/parsing.ts b/src/deploy/functions/runtimes/discovery/parsing.ts index 662bdac87cf..702bfd41282 100644 --- a/src/deploy/functions/runtimes/discovery/parsing.ts +++ b/src/deploy/functions/runtimes/discovery/parsing.ts @@ -28,7 +28,7 @@ export function assertKeyTypes( const fullKey = prefix ? prefix + "." + key : key; if (!schema[key] || schema[key] === "omit") { throw new FirebaseError( - `Unexpected key ${fullKey}. You may need to install a newer version of the Firebase CLI` + `Unexpected key ${fullKey}. You may need to install a newer version of the Firebase CLI.` ); } if (schema[key] === "string") { diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 668e6f2ccc3..503423dc49c 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -7,6 +7,7 @@ import { FirebaseError } from "../../../../error"; export type ManifestEndpoint = backend.ServiceConfiguration & backend.Triggered & Partial & + Partial & Partial & Partial & Partial & { @@ -17,7 +18,7 @@ export type ManifestEndpoint = backend.ServiceConfiguration & export interface Manifest { specVersion: string; - requiredAPIs?: Record; + requiredAPIs?: backend.RequiredAPI[]; endpoints: Record; } @@ -34,7 +35,7 @@ export function backendFromV1Alpha1( requireKeys("", manifest, "endpoints"); assertKeyTypes("", manifest, { specVersion: "string", - requiredAPIs: "object", + requiredAPIs: "array", endpoints: "object", }); for (const id of Object.keys(manifest.endpoints)) { @@ -46,19 +47,17 @@ export function backendFromV1Alpha1( return bkend; } -function parseRequiredAPIs(manifest: Manifest): Record { - const requiredAPIs: Record = {}; - // Note: this intentionally allows undefined to slip through as {} - if (typeof manifest !== "object" || Array.isArray(manifest)) { - throw new FirebaseError("Expected requiredApis to be a map of string to string"); - } - for (const [api, reason] of Object.entries(manifest.requiredAPIs || {})) { +function parseRequiredAPIs(manifest: Manifest): backend.RequiredAPI[] { + const requiredAPIs: backend.RequiredAPI[] = manifest.requiredAPIs || []; + for (const { api, reason } of requiredAPIs) { + if (typeof api !== "string") { + throw new FirebaseError(`Invalid api "${JSON.stringify(api)}. Expected string`); + } if (typeof reason !== "string") { throw new FirebaseError( `Invalid reason "${JSON.stringify(reason)} for API ${api}. Expected string` ); } - requiredAPIs[api] = reason; } return requiredAPIs; } @@ -84,13 +83,13 @@ function parseEndpoints( concurrency: "number", serviceAccountEmail: "string", timeout: "string", - vpcConnector: "string", - vpcConnectorEgressSettings: "string", + vpc: "object", labels: "object", ingressSettings: "string", environmentVariables: "object", secretEnvironmentVariables: "array", httpsTrigger: "object", + callableTrigger: "object", eventTrigger: "object", scheduleTrigger: "object", taskQueueTrigger: "object", @@ -99,6 +98,9 @@ function parseEndpoints( if (ep.httpsTrigger) { triggerCount++; } + if (ep.callableTrigger) { + triggerCount++; + } if (ep.eventTrigger) { triggerCount++; } @@ -109,7 +111,7 @@ function parseEndpoints( triggerCount++; } if (!triggerCount) { - throw new FirebaseError("Expected trigger in endpoint" + id); + throw new FirebaseError("Expected trigger in endpoint " + id); } if (triggerCount > 1) { throw new FirebaseError("Multiple triggers defined for endpoint" + id); @@ -132,6 +134,8 @@ function parseEndpoints( }); triggered = { httpsTrigger: {} }; copyIfPresent(triggered.httpsTrigger, ep.httpsTrigger, "invoker"); + } else if (backend.isCallableTriggered(ep)) { + triggered = { callableTrigger: {} }; } else if (backend.isScheduleTriggered(ep)) { assertKeyTypes(prefix + ".scheduleTrigger", ep.scheduleTrigger, { schedule: "string", @@ -195,8 +199,7 @@ function parseEndpoints( "concurrency", "serviceAccountEmail", "timeout", - "vpcConnector", - "vpcConnectorEgressSettings", + "vpc", "labels", "ingressSettings", "environmentVariables" diff --git a/src/deploy/functions/runtimes/golang/index.ts b/src/deploy/functions/runtimes/golang/index.ts index 77ef1f2864f..50edabe7c0b 100644 --- a/src/deploy/functions/runtimes/golang/index.ts +++ b/src/deploy/functions/runtimes/golang/index.ts @@ -131,7 +131,7 @@ export class Delegate { // If we SIGKILL the child process we're actually going to kill the go // runner and the webserver it launched will keep running. - await fetch(`http://localhost:${adminPort}/quitquitquit`); + await fetch(`http://localhost:${adminPort}/__/quitquitquit`); setTimeout(() => { if (!childProcess.killed) { childProcess.kill("SIGKILL"); diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 3d27bb266d0..0e5084e5dc4 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -1,15 +1,24 @@ import { promisify } from "util"; import * as fs from "fs"; import * as path from "path"; +import * as portfinder from "portfinder"; +import * as spawn from "cross-spawn"; +import * as semver from "semver"; +import fetch from "node-fetch"; import { FirebaseError } from "../../../../error"; import { getRuntimeChoice } from "./parseRuntimeAndValidateSDK"; import { logger } from "../../../../logger"; +import { previews } from "../../../../previews"; +import { logLabeledWarning } from "../../../../utils"; import * as backend from "../../backend"; import * as runtimes from ".."; import * as validate from "./validate"; import * as versioning from "./versioning"; import * as parseTriggers from "./parseTriggers"; +import * as discovery from "../discovery"; + +const MIN_FUNCTIONS_SDK_VERSION = "3.18.1"; export async function tryCreateDelegate( context: runtimes.DelegateContext @@ -80,10 +89,70 @@ export class Delegate { return Promise.resolve(() => Promise.resolve()); } + serve(port: number, envs: backend.EnvironmentVariables): Promise<() => Promise> { + const childProcess = spawn("./node_modules/.bin/firebase-functions", [this.sourceDir], { + env: { + ...envs, + PORT: port.toString(), + FUNCTIONS_CONTROL_API: "true", + HOME: process.env.HOME, + PATH: process.env.PATH, + }, + cwd: this.sourceDir, + stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "inherit"], + }); + childProcess.stdout?.on("data", (chunk) => { + logger.debug(chunk.toString()); + }); + return Promise.resolve(async () => { + const p = new Promise((resolve, reject) => { + childProcess.once("exit", resolve); + childProcess.once("error", reject); + }); + + await fetch(`http://localhost:${port}/__/quitquitquit`); + setTimeout(() => { + if (!childProcess.killed) { + childProcess.kill("SIGKILL"); + } + }, 10_000); + return p; + }); + } + async discoverSpec( config: backend.RuntimeConfigValues, env: backend.EnvironmentVariables ): Promise { + if (previews.functionsv2) { + if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) { + logLabeledWarning( + "functions", + `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` + + `Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}` + ); + return parseTriggers.discoverBackend( + this.projectId, + this.sourceDir, + this.runtime, + config, + env + ); + } + let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime); + if (!discovered) { + const getPort = promisify(portfinder.getPort) as () => Promise; + const port = await getPort(); + const kill = await this.serve(port, env); + try { + discovered = await discovery.detectFromPort(port, this.projectId, this.runtime); + } finally { + await kill(); + } + } + discovered.environmentVariables = env; + return discovered; + } return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env); } } diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index d916127813d..e7293883589 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -151,6 +151,23 @@ export async function discoverBackend( return want; } +/* @internal */ +export function mergeRequiredAPIs(backend: backend.Backend) { + const apiToReasons: Record> = {}; + for (const { api, reason } of backend.requiredAPIs) { + const reasons = apiToReasons[api] || new Set(); + reasons.add(reason); + apiToReasons[api] = reasons; + } + + const merged: backend.RequiredAPI[] = []; + for (const [api, reasons] of Object.entries(apiToReasons)) { + merged.push({ api, reason: Array.from(reasons).join(" ") }); + } + + backend.requiredAPIs = merged; +} + export function addResourcesToBackend( projectId: string, runtime: runtimes.Runtime, @@ -173,7 +190,10 @@ export function addResourcesToBackend( if (annotation.taskQueueTrigger) { triggered = { taskQueueTrigger: annotation.taskQueueTrigger }; - want.requiredAPIs["cloudtasks"] = "cloudtasks.googleapis.com"; + want.requiredAPIs.push({ + api: "cloudtasks.googleapis.com", + reason: "Needed for task queue functions.", + }); } else if (annotation.httpsTrigger) { const trigger: backend.HttpsTrigger = {}; if (annotation.failurePolicy) { @@ -182,8 +202,10 @@ export function addResourcesToBackend( proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker"); triggered = { httpsTrigger: trigger }; } else if (annotation.schedule) { - want.requiredAPIs["pubsub"] = "pubsub.googleapis.com"; - want.requiredAPIs["scheduler"] = "cloudscheduler.googleapis.com"; + want.requiredAPIs.push({ + api: "cloudscheduler.googleapis.com", + reason: "Needed for scheduled functions.", + }); triggered = { scheduleTrigger: annotation.schedule }; } else { triggered = { @@ -204,6 +226,7 @@ export function addResourcesToBackend( }; } } + const endpoint: backend.Endpoint = { platform: annotation.platform || "gcfv1", id: annotation.name, @@ -218,7 +241,13 @@ export function addResourcesToBackend( if (maybeId && !maybeId.includes("/")) { maybeId = `projects/${projectId}/locations/${region}/connectors/${maybeId}`; } - endpoint.vpcConnector = maybeId; + endpoint.vpc = { connector: maybeId }; + proto.renameIfPresent( + endpoint.vpc, + annotation, + "egressSettings", + "vpcConnectorEgressSettings" + ); } if (annotation.secrets) { @@ -240,7 +269,6 @@ export function addResourcesToBackend( "concurrency", "serviceAccountEmail", "labels", - "vpcConnectorEgressSettings", "ingressSettings", "timeout", "maxInstances", @@ -249,5 +277,7 @@ export function addResourcesToBackend( ); want.endpoints[region] = want.endpoints[region] || {}; want.endpoints[region][endpoint.id] = endpoint; + + mergeRequiredAPIs(want); } } diff --git a/src/deploy/functions/runtimes/node/versioning.ts b/src/deploy/functions/runtimes/node/versioning.ts index 234122798a9..ac3ea940254 100644 --- a/src/deploy/functions/runtimes/node/versioning.ts +++ b/src/deploy/functions/runtimes/node/versioning.ts @@ -100,7 +100,7 @@ export function checkFunctionsSDKVersion(currentVersion: string): void { } utils.logWarning( clc.bold.yellow("functions: ") + - "package.json indicates an outdated version of firebase-functions.\nPlease upgrade using " + + "package.json indicates an outdated version of firebase-functions. Please upgrade using " + clc.bold("npm install --save firebase-functions@latest") + " in your functions directory." ); diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 6ae493c1b42..86da8e115a1 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -512,15 +512,21 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi "timeout", "minInstances", "maxInstances", - "vpcConnector", - "vpcConnectorEgressSettings", "ingressSettings", "labels", "environmentVariables", "secretEnvironmentVariables", "sourceUploadUrl" ); - + if (gcfFunction.vpcConnector) { + endpoint.vpc = { connector: gcfFunction.vpcConnector }; + proto.renameIfPresent( + endpoint.vpc, + gcfFunction, + "egressSettings", + "vpcConnectorEgressSettings" + ); + } return endpoint; } @@ -575,6 +581,9 @@ export function functionFromEndpoint( gcfFunction.labels = { ...gcfFunction.labels, "deployment-taskqueue": "true" }; } else { gcfFunction.httpsTrigger = {}; + if (backend.isCallableTriggered(endpoint)) { + gcfFunction.labels = { ...gcfFunction.labels, "deployment-callabled": "true" }; + } } proto.copyIfPresent( @@ -585,12 +594,18 @@ export function functionFromEndpoint( "availableMemoryMb", "minInstances", "maxInstances", - "vpcConnector", - "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables", "secretEnvironmentVariables" ); - + if (endpoint.vpc) { + proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector"); + proto.renameIfPresent( + gcfFunction, + endpoint.vpc, + "vpcConnectorEgressSettings", + "egressSettings" + ); + } return gcfFunction; } diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 886020a5da8..6b7311288af 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -394,8 +394,6 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage gcfFunction.serviceConfig, endpoint, "environmentVariables", - "vpcConnector", - "vpcConnectorEgressSettings", "serviceAccountEmail", "ingressSettings" ); @@ -416,6 +414,16 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances"); proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances"); + if (endpoint.vpc) { + proto.renameIfPresent(gcfFunction.serviceConfig, endpoint.vpc, "vpcConnector", "connector"); + proto.renameIfPresent( + gcfFunction.serviceConfig, + endpoint.vpc, + "vpcConnectorEgressSettings", + "egressSettings" + ); + } + if (backend.isEventTriggered(endpoint)) { gcfFunction.eventTrigger = { eventType: endpoint.eventTrigger.eventType, @@ -443,6 +451,8 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage gcfFunction.labels = { ...gcfFunction.labels, "deployment-scheduled": "true" }; } else if (backend.isTaskQueueTriggered(endpoint)) { gcfFunction.labels = { ...gcfFunction.labels, "deployment-taskqueue": "true" }; + } else if (backend.isCallableTriggered(endpoint)) { + gcfFunction.labels = { ...gcfFunction.labels, "deployment-callable": "true" }; } return gcfFunction; @@ -502,8 +512,6 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", - "vpcConnector", - "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables" ); @@ -525,5 +533,15 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount"); proto.copyIfPresent(endpoint, gcfFunction, "labels"); + if (gcfFunction.serviceConfig.vpcConnector) { + endpoint.vpc = { connector: gcfFunction.serviceConfig.vpcConnector }; + proto.renameIfPresent( + endpoint.vpc, + gcfFunction.serviceConfig, + "egressSettings", + "vpcConnectorEgressSettings" + ); + } + return endpoint; } diff --git a/src/test/deploy/functions/backend.spec.ts b/src/test/deploy/functions/backend.spec.ts index db4229f9530..616c995f5e3 100644 --- a/src/test/deploy/functions/backend.spec.ts +++ b/src/test/deploy/functions/backend.spec.ts @@ -72,7 +72,7 @@ describe("Backend", () => { expect( backend.isEmptyBackend({ ...backend.empty(), - requiredAPIs: { foo: "foo.googleapis.com" }, + requiredAPIs: [{ api: "foo.googleapis.com", reason: "foo" }], }) ).to.be.false; expect(backend.isEmptyBackend(backend.of({ ...ENDPOINT, httpsTrigger: {} }))); diff --git a/src/test/deploy/functions/runtimes/discovery/index.spec.ts b/src/test/deploy/functions/runtimes/discovery/index.spec.ts index 9279f990a88..528e0acef34 100644 --- a/src/test/deploy/functions/runtimes/discovery/index.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/index.spec.ts @@ -98,12 +98,12 @@ describe("detectFromPort", () => { // 600ms, which is dangerously close to the default limit of 1s. Increase limits so // that this doesn't flake even when running on slower machines experiencing hiccup it("passes as smoke test", async () => { - nock("http://localhost:8080").get("/backend.yaml").times(20).replyWithError({ + nock("http://localhost:8080").get("/__/functions.yaml").times(20).replyWithError({ message: "Still booting", code: "ECONNREFUSED", }); - nock("http://localhost:8080").get("/backend.yaml").reply(200, YAML_TEXT); + nock("http://localhost:8080").get("/__/functions.yaml").reply(200, YAML_TEXT); const parsed = await discovery.detectFromPort(8080, "project", "nodejs16"); expect(parsed).to.deep.equal(BACKEND); diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 58e37020f89..c759d49e554 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -32,7 +32,7 @@ describe("backendFromV1Alpha1", () => { for (const [key, value] of Object.entries(invalidBackendTypes)) { it(`throws on invalid value for top-level key ${key}`, () => { const obj = { - requiredAPIs: {}, + requiredAPIs: [], endpoints: {}, [key]: value, }; @@ -311,7 +311,10 @@ describe("backendFromV1Alpha1", () => { }, }, }; - const expected = backend.of({ ...DEFAULTED_ENDPOINT, scheduleTrigger }); + const expected = backend.of({ + ...DEFAULTED_ENDPOINT, + scheduleTrigger, + }); const parsed = v1alpha1.backendFromV1Alpha1(yaml, PROJECT, REGION, RUNTIME); expect(parsed).to.deep.equal(expected); }); @@ -349,8 +352,10 @@ describe("backendFromV1Alpha1", () => { timeout: "60s", maxInstances: 20, minInstances: 1, - vpcConnector: "hello", - vpcConnectorEgressSettings: "ALL_TRAFFIC", + vpc: { + connector: "hello", + egressSettings: "ALL_TRAFFIC", + }, ingressSettings: "ALLOW_INTERNAL_ONLY", serviceAccountEmail: "sa@", }; diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index 1cca607f427..a1c504a1c3f 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -76,9 +76,12 @@ describe("addResourcesToBackend", () => { const expected: backend.Backend = { ...backend.of({ ...BASIC_ENDPOINT, taskQueueTrigger: {} }), - requiredAPIs: { - cloudtasks: "cloudtasks.googleapis.com", - }, + requiredAPIs: [ + { + api: "cloudtasks.googleapis.com", + reason: "Needed for task queue functions.", + }, + ], }; expect(result).to.deep.equal(expected); }); @@ -141,8 +144,10 @@ describe("addResourcesToBackend", () => { maxInstances: 42, minInstances: 1, serviceAccountEmail: "inlined@google.com", - vpcConnectorEgressSettings: "PRIVATE_RANGES_ONLY", - vpcConnector: "projects/project/locations/region/connectors/connector", + vpc: { + connector: "projects/project/locations/region/connectors/connector", + egressSettings: "PRIVATE_RANGES_ONLY", + }, ingressSettings: "ALLOW_ALL", timeout: "60s", labels: { @@ -281,10 +286,12 @@ describe("addResourcesToBackend", () => { scheduleTrigger: schedule, } ), - requiredAPIs: { - pubsub: "pubsub.googleapis.com", - scheduler: "cloudscheduler.googleapis.com", - }, + requiredAPIs: [ + { + api: "cloudscheduler.googleapis.com", + reason: "Needed for scheduled functions.", + }, + ], }; expect(result).to.deep.equal(expected); @@ -303,7 +310,9 @@ describe("addResourcesToBackend", () => { const expected: backend.Backend = backend.of({ ...BASIC_ENDPOINT, httpsTrigger: {}, - vpcConnector: "", + vpc: { + connector: "", + }, }); expect(result).to.deep.equal(expected); diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index 8e7182e8830..34285fccce5 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -95,8 +95,10 @@ describe("cloudfunctions", () => { availableMemoryMb: 128, minInstances: 1, maxInstances: 42, - vpcConnector: "connector", - vpcConnectorEgressSettings: "ALL_TRAFFIC", + vpc: { + connector: "connector", + egressSettings: "ALL_TRAFFIC", + }, ingressSettings: "ALLOW_ALL", timeout: "15s", serviceAccountEmail: "inlined@google.com", @@ -281,8 +283,6 @@ describe("cloudfunctions", () => { availableMemoryMb: 128, minInstances: 1, maxInstances: 42, - vpcConnector: "connector", - vpcConnectorEgressSettings: "ALL_TRAFFIC", ingressSettings: "ALLOW_ALL", serviceAccountEmail: "inlined@google.com", timeout: "15s", @@ -293,15 +293,24 @@ describe("cloudfunctions", () => { FOO: "bar", }, }; + const vpcConnector = "connector"; + const vpcConnectorEgressSettings = "ALL_TRAFFIC"; + expect( cloudfunctions.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION, ...extraFields, + vpcConnector, + vpcConnectorEgressSettings, httpsTrigger: {}, } as cloudfunctions.CloudFunction) ).to.deep.equal({ ...ENDPOINT, ...extraFields, + vpc: { + connector: vpcConnector, + egressSettings: vpcConnectorEgressSettings, + }, httpsTrigger: {}, }); }); diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 83160ea87d8..a059227ba1d 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -144,8 +144,10 @@ describe("cloudfunctionsv2", () => { ...ENDPOINT, httpsTrigger: {}, platform: "gcfv2", - vpcConnector: "connector", - vpcConnectorEgressSettings: "ALL_TRAFFIC", + vpc: { + connector: "connector", + egressSettings: "ALL_TRAFFIC", + }, ingressSettings: "ALLOW_ALL", serviceAccountEmail: "inlined@google.com", labels: { @@ -304,20 +306,24 @@ describe("cloudfunctionsv2", () => { it("should copy optional fields", () => { const extraFields: backend.ServiceConfiguration = { - vpcConnector: "connector", - vpcConnectorEgressSettings: "ALL_TRAFFIC", ingressSettings: "ALLOW_ALL", serviceAccountEmail: "inlined@google.com", environmentVariables: { FOO: "bar", }, }; + const vpc = { + connector: "connector", + egressSettings: "ALL_TRAFFIC" as const, + }; expect( cloudfunctionsv2.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION_V2, serviceConfig: { ...HAVE_CLOUD_FUNCTION_V2.serviceConfig, ...extraFields, + vpcConnector: vpc.connector, + vpcConnectorEgressSettings: vpc.egressSettings, availableMemory: "128M", }, labels: { @@ -330,6 +336,7 @@ describe("cloudfunctionsv2", () => { httpsTrigger: {}, uri: RUN_URI, ...extraFields, + vpc, availableMemoryMb: 128, labels: { foo: "bar", From ba3c15aabd93058a984707933983eaff923a94f5 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 18 Feb 2022 14:08:21 -0800 Subject: [PATCH 0095/1699] Add EVENTARC_CLOUD_EVENT_SOURCE as reserved environment variable for Firebase Extensions use. (#4196) Add EVENTARC_CLOUD_EVENT_SOURCE as reserved environment variable for Firebase Extensions use. --- CHANGELOG.md | 1 + src/functions/env.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..ae23704df05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Updates reserved environment variables for CF3 to include 'EVENTARC_CLOUD_EVENT_SOURCE' (#4196). diff --git a/src/functions/env.ts b/src/functions/env.ts index 5e403aff1d2..b92625dece8 100644 --- a/src/functions/env.ts +++ b/src/functions/env.ts @@ -13,6 +13,7 @@ const RESERVED_KEYS = [ // Cloud Functions for Firebase "FIREBASE_CONFIG", "CLOUD_RUNTIME_CONFIG", + "EVENTARC_CLOUD_EVENT_SOURCE", // Cloud Functions - old runtimes: // https://cloud.google.com/functions/docs/env-var#nodejs_8_python_37_and_go_111 "ENTRY_POINT", From 0355adccd65ac3034f1aac377dc255025ef336ec Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 18 Feb 2022 14:23:26 -0800 Subject: [PATCH 0096/1699] Set env var FUNCTION_SIGNATURE_TYPE=cloudevent when deploying gen2 functions (#4197) By setting FUNCTION_SIGNATURE_TYPE=cloudevent, we prevent deployed gen2 functions from downcasting cloudevent messages. --- src/deploy/functions/prepare.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 34f0b29bd21..32f2b2ac0da 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -138,6 +138,15 @@ export async function prepare( // Setup environment variables on each function. for (const endpoint of backend.allEndpoints(wantBackend)) { endpoint.environmentVariables = wantBackend.environmentVariables; + if (endpoint.platform === "gcfv2") { + if (backend.isEventTriggered(endpoint)) { + // By default, Functions Framework in GCFv2 opts to downcast incoming cloudevent messages to legacy formats. + // Since Firebase Functions SDK expects messages in cloudevent format, we set FUNCTION_SIGNATURE_TYPE to tell + // Functions Framework to disable downcast before passing the cloudevent message to function handler. + // See https://github.com/GoogleCloudPlatform/functions-framework-nodejs/blob/master/README.md#configure-the-functions-framework + endpoint.environmentVariables["FUNCTION_SIGNATURE_TYPE"] = "cloudevent"; + } + } } // Enable required APIs. This may come implicitly from triggers (e.g. scheduled triggers From feaee21fafb10f72cd1e67b3940f1938e6698c8c Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Fri, 18 Feb 2022 18:52:40 -0500 Subject: [PATCH 0097/1699] Fix arg order for Storage Emulator start command (#4195) * Fix arg order for Storage Emulator command * Add CHANGELOG entry --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 2 +- src/emulator/storage/rules/runtime.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae23704df05..41b13b48802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Updates reserved environment variables for CF3 to include 'EVENTARC_CLOUD_EVENT_SOURCE' (#4196). +- Fixes arg order for `firebase emulators:start --only storage` (#4195). diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 72d96b76ffa..eda2fd7c294 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -170,10 +170,10 @@ const Commands: { [s in DownloadableEmulators]: DownloadableEmulatorCommand } = // separately in ./storage/runtime.ts (not via the start function below). binary: "java", args: [ - "-jar", // Required for rules error/warning messages, which are in English only. // Attempts to fetch the messages in another language leads to crashes. "-Duser.language=en", + "-jar", getExecPath(Emulators.STORAGE), "serve", ], diff --git a/src/emulator/storage/rules/runtime.ts b/src/emulator/storage/rules/runtime.ts index 37016d065d4..9934f20a285 100644 --- a/src/emulator/storage/rules/runtime.ts +++ b/src/emulator/storage/rules/runtime.ts @@ -155,6 +155,7 @@ export class StorageRulesRuntime { this._childprocess.stderr?.on("data", (buf: Buffer) => { const error = buf.toString(); if (error.includes("jarfile")) { + EmulatorLogger.forEmulator(Emulators.STORAGE).log("ERROR", error); throw new FirebaseError( "There was an issue starting the rules emulator, please run 'firebase setup:emulators:storage` again" ); From d46f2b737a78df9bc89f75e98029084c38a0f1ac Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Fri, 18 Feb 2022 18:42:22 -0600 Subject: [PATCH 0098/1699] rewrite hashcashe to typescript (#4095) * rewrite hashcashe to typescript * swap out foreach for for-of Co-authored-by: joehan --- src/deploy/hosting/hashcache.js | 45 ------------------- src/deploy/hosting/hashcache.ts | 55 +++++++++++++++++++++++ src/deploy/hosting/uploader.ts | 14 +++--- src/test/deploy/hosting/hashcache.spec.ts | 38 ++++++++++++++++ 4 files changed, 100 insertions(+), 52 deletions(-) delete mode 100644 src/deploy/hosting/hashcache.js create mode 100644 src/deploy/hosting/hashcache.ts create mode 100644 src/test/deploy/hosting/hashcache.spec.ts diff --git a/src/deploy/hosting/hashcache.js b/src/deploy/hosting/hashcache.js deleted file mode 100644 index 34d50ee607d..00000000000 --- a/src/deploy/hosting/hashcache.js +++ /dev/null @@ -1,45 +0,0 @@ -const fs = require("fs-extra"); -const path = require("path"); -const { logger } = require("../../logger"); - -function cachePath(cwd, name) { - return path.resolve(cwd, ".firebase/hosting." + name + ".cache"); -} - -exports.load = function (cwd, name) { - try { - const out = {}; - const lines = fs.readFileSync(cachePath(cwd, name), { - encoding: "utf8", - }); - lines.split("\n").forEach(function (line) { - const d = line.split(","); - if (d.length === 3) { - out[d[0]] = { mtime: parseInt(d[1]), hash: d[2] }; - } - }); - return out; - } catch (e) { - if (e.code === "ENOENT") { - logger.debug("[hosting] hash cache [" + name + "] not populated"); - } else { - logger.debug("[hosting] hash cache [" + name + "] load error:", e.message); - } - return {}; - } -}; - -exports.dump = function (cwd, name, data) { - let st = ""; - let count = 0; - for (const [path, d] of data) { - count++; - st += path + "," + d.mtime + "," + d.hash + "\n"; - } - try { - fs.outputFileSync(cachePath(cwd, name), st, { encoding: "utf8" }); - logger.debug("[hosting] hash cache [" + name + "] stored for", count, "files"); - } catch (e) { - logger.debug("[hosting] unable to store hash cache [" + name + "]", e.stack); - } -}; diff --git a/src/deploy/hosting/hashcache.ts b/src/deploy/hosting/hashcache.ts new file mode 100644 index 00000000000..38a4b87ac7d --- /dev/null +++ b/src/deploy/hosting/hashcache.ts @@ -0,0 +1,55 @@ +import * as fs from "fs-extra"; +import * as path from "path"; + +import { logger } from "../../logger"; + +function cachePath(cwd: string, name: string): string { + return path.resolve(cwd, `.firebase/hosting.${name}.cache`); +} + +export interface HashRecord { + mtime: number; + hash: string; +} + +/** + * Load brings in the data from the cache named by `name`. + */ +export function load(cwd: string, name: string): Map { + try { + const out = new Map(); + const lines = fs.readFileSync(cachePath(cwd, name), "utf8"); + for (const line of lines.split("\n")) { + const d = line.split(","); + if (d.length === 3) { + out.set(d[0], { mtime: parseInt(d[1]), hash: d[2] }); + } + } + return out; + } catch (e: any) { + if (e.code === "ENOENT") { + logger.debug(`[hosting] hash cache [${name}] not populated`); + } else { + logger.debug(`[hosting] hash cache [${name}] load error: ${e.message}`); + } + return new Map(); + } +} + +/** + * Dump puts the data specified into the cache named by `name`. + */ +export function dump(cwd: string, name: string, data: Map): void { + let st = ""; + let count = 0; + for (const [path, d] of data) { + count++; + st += `${path},${d.mtime},${d.hash}\n`; + } + try { + fs.outputFileSync(cachePath(cwd, name), st, { encoding: "utf8" }); + logger.debug(`[hosting] hash cache [${name}] stored for ${count} files`); + } catch (e: any) { + logger.debug(`[hosting] unable to store hash cache [${name}]: ${e.stack}`); + } +} diff --git a/src/deploy/hosting/uploader.ts b/src/deploy/hosting/uploader.ts index 0cda801fc7b..825cbc21c2c 100644 --- a/src/deploy/hosting/uploader.ts +++ b/src/deploy/hosting/uploader.ts @@ -9,7 +9,7 @@ import * as zlib from "zlib"; import { Client } from "../../apiv2"; import { Queue } from "../../throttler/queue"; import { hostingApiOrigin } from "../../api"; -import * as hashcache from "./hashcache"; +import { load, dump, HashRecord } from "./hashcache"; import { logger } from "../../logger"; import { FirebaseError } from "../../error"; @@ -40,8 +40,8 @@ export class Uploader { private files: string[]; private fileCount: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any - private cache: { [key: string]: any }; - private cacheNew: Map; + private cache: Map; + private cacheNew: Map; private sizeMap: { [key: string]: number }; private hashMap: { [key: string]: string }; private pathMap: { [key: string]: string }; @@ -83,7 +83,7 @@ export class Uploader { this.files = options.files; this.fileCount = this.files.length; - this.cache = hashcache.load(this.projectRoot, this.hashcacheName()); + this.cache = load(this.projectRoot, this.hashcacheName()); this.cacheNew = new Map(); this.sizeMap = {}; @@ -112,7 +112,7 @@ export class Uploader { .wait() .then(this.queuePopulate.bind(this)) .then(() => { - hashcache.dump(this.projectRoot, this.hashcacheName(), this.cacheNew); + dump(this.projectRoot, this.hashcacheName(), this.cacheNew); logger.debug("[hosting][hash queue][FINAL]", this.hashQueue.stats()); this.populateQueue.close(); return this.populateQueue.wait(); @@ -128,7 +128,7 @@ export class Uploader { logger.debug( "[hosting][upload queue] upload failed with content hash error. Deleting hash cache" ); - hashcache.dump(this.projectRoot, this.hashcacheName(), new Map()); + dump(this.projectRoot, this.hashcacheName(), new Map()); } }); @@ -170,7 +170,7 @@ export class Uploader { const stats = fs.statSync(path.resolve(this.public, filePath)); const mtime = stats.mtime.getTime(); this.sizeMap[filePath] = stats.size; - const cached = this.cache[filePath]; + const cached = this.cache.get(filePath); if (cached && cached.mtime === mtime) { this.cacheNew.set(filePath, cached); this.addHash(filePath, cached.hash); diff --git a/src/test/deploy/hosting/hashcache.spec.ts b/src/test/deploy/hosting/hashcache.spec.ts new file mode 100644 index 00000000000..84e6cd92427 --- /dev/null +++ b/src/test/deploy/hosting/hashcache.spec.ts @@ -0,0 +1,38 @@ +import { expect } from "chai"; +import { existsSync, mkdirpSync, readFileSync, writeFileSync } from "fs-extra"; +import { join } from "path"; +import * as tmp from "tmp"; + +import { load, dump, HashRecord } from "../../../deploy/hosting/hashcache"; + +tmp.setGracefulCleanup(); + +describe("hashcache", () => { + it("should return an empty object if a file doesn't exist", () => { + expect(load("cwd-doesnt-exist", "somename")).to.deep.equal(new Map()); + }); + + it("should be able to dump configuration to a file", () => { + const dir = tmp.dirSync(); + const name = "testcache"; + const data = new Map([["foo", { mtime: 0, hash: "deadbeef" }]]); + + expect(dump(dir.name, name, data)).to.not.throw; + + expect(existsSync(join(dir.name, ".firebase", `hosting.${name}.cache`))).to.be.true; + expect(readFileSync(join(dir.name, ".firebase", `hosting.${name}.cache`), "utf8")).to.equal( + "foo,0,deadbeef\n" + ); + }); + + it("should be able to load configuration from a file", () => { + const dir = tmp.dirSync(); + const name = "testcache"; + mkdirpSync(join(dir.name, ".firebase")); + writeFileSync(join(dir.name, ".firebase", `hosting.${name}.cache`), "bar,4,alivebeef\n"); + + expect(load(dir.name, name)).to.deep.equal( + new Map([["bar", { mtime: 4, hash: "alivebeef" }]]) + ); + }); +}); From 084284e96adb53399429fbffb487fd7fd6333877 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 18 Feb 2022 17:32:00 -0800 Subject: [PATCH 0099/1699] Don't intentionally remove pubsub topic when updating gen 2 functions. (#4198) As of GCF Gen2 public preview, removing pubsub topic on update deletes(!?) the deployed function. --- CHANGELOG.md | 1 + src/deploy/functions/release/fabricator.ts | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b13b48802..5dff350d288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ +- Fixes bug where updating gen 2 pubsub functions always failed (#4198). - Updates reserved environment variables for CF3 to include 'EVENTARC_CLOUD_EVENT_SOURCE' (#4196). - Fixes arg order for `firebase emulators:start --only storage` (#4195). diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index c3798ce6b51..954e69ccd0e 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -362,14 +362,6 @@ export class Fabricator { } const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage[endpoint.region]); - // N.B. As of GCFv2 private preview the API chokes on any update call that - // includes the pub/sub topic even if that topic is unchanged. - // We know that the user hasn't changed the topic between deploys because - // of checkForInvalidChangeOfTrigger(). - if (apiFunction.eventTrigger?.pubsubTopic) { - delete apiFunction.eventTrigger.pubsubTopic; - } - const resultFunction = await this.functionExecutor .run(async () => { const op: { name: string } = await gcfV2.updateFunction(apiFunction); From 1dc9dd07a726822fcad1c6ac6ed08429c834eb54 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Tue, 22 Feb 2022 14:34:24 -0500 Subject: [PATCH 0100/1699] Reorganize ext command code (#4206) * move around * Update ext-install.ts * Address comments --- src/commands/ext-dev-init.ts | 116 +++++++------- src/commands/ext-info.ts | 1 + src/commands/ext-install.ts | 280 +++++++++++++++++----------------- src/commands/ext-uninstall.ts | 34 ++--- src/commands/ext-update.ts | 22 +-- 5 files changed, 227 insertions(+), 226 deletions(-) diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index 4d68da54af9..7f7c73ffed1 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -27,6 +27,64 @@ function readCommonTemplates() { }; } +/** + * Command for setting up boilerplate code for a new extension. + */ +export default new Command("ext:dev:init") + .description("initialize files for writing an extension in the current directory") + .before(checkMinRequiredVersion, "extDevMinVersion") + .action(async (options: any) => { + const cwd = options.cwd || process.cwd(); + const config = new Config({}, { projectDir: cwd, cwd: cwd }); + + try { + const lang = await promptOnce({ + type: "list", + name: "language", + message: "In which language do you want to write the Cloud Functions for your extension?", + default: "javascript", + choices: [ + { + name: "JavaScript", + value: "javascript", + }, + { + name: "TypeScript", + value: "typescript", + }, + ], + }); + switch (lang) { + case "javascript": { + await javascriptSelected(config); + break; + } + case "typescript": { + await typescriptSelected(config); + break; + } + default: { + throw new FirebaseError(`${lang} is not supported.`); + } + } + + await npmDependencies.askInstallDependencies({}, config); + + const welcome = fs.readFileSync(path.join(TEMPLATE_ROOT, lang, "WELCOME.md"), "utf8"); + return logger.info("\n" + marked(welcome)); + } catch (err: any) { + if (!(err instanceof FirebaseError)) { + throw new FirebaseError( + `Error occurred when initializing files for new extension: ${err.message}`, + { + original: err, + } + ); + } + throw err; + } + }); + /** * Sets up Typescript boilerplate code for new extension * @param {Config} config configuration options @@ -127,61 +185,3 @@ async function javascriptSelected(config: Config): Promise { } await config.askWriteProjectFile("functions/.gitignore", gitignoreTemplate); } - -/** - * Command for setting up boilerplate code for a new extension. - */ -export default new Command("ext:dev:init") - .description("initialize files for writing an extension in the current directory") - .before(checkMinRequiredVersion, "extDevMinVersion") - .action(async (options: any) => { - const cwd = options.cwd || process.cwd(); - const config = new Config({}, { projectDir: cwd, cwd: cwd }); - - try { - const lang = await promptOnce({ - type: "list", - name: "language", - message: "In which language do you want to write the Cloud Functions for your extension?", - default: "javascript", - choices: [ - { - name: "JavaScript", - value: "javascript", - }, - { - name: "TypeScript", - value: "typescript", - }, - ], - }); - switch (lang) { - case "javascript": { - await javascriptSelected(config); - break; - } - case "typescript": { - await typescriptSelected(config); - break; - } - default: { - throw new FirebaseError(`${lang} is not supported.`); - } - } - - await npmDependencies.askInstallDependencies({}, config); - - const welcome = fs.readFileSync(path.join(TEMPLATE_ROOT, lang, "WELCOME.md"), "utf8"); - return logger.info("\n" + marked(welcome)); - } catch (err: any) { - if (!(err instanceof FirebaseError)) { - throw new FirebaseError( - `Error occurred when initializing files for new extension: ${err.message}`, - { - original: err, - } - ); - } - throw err; - } - }); diff --git a/src/commands/ext-info.ts b/src/commands/ext-info.ts index e78fc40e725..8ce19e81ddb 100644 --- a/src/commands/ext-info.ts +++ b/src/commands/ext-info.ts @@ -15,6 +15,7 @@ const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); const FUNCTION_TYPE_REGEX = /\..+\.function/; + export default new Command("ext:info ") .description( "display information about an extension by name (extensionName@x.y.z for a specific version)" diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 6d4fa2d5d85..50f38b6a930 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -44,6 +44,146 @@ marked.setOptions({ renderer: new TerminalRenderer(), }); +/** + * Command for installing an extension + */ +export default new Command("ext:install [extensionName]") + .description( + "install an official extension if [extensionName] or [extensionName@version] is provided; " + + (previews.extdev + ? "install a local extension if [localPathOrUrl] or [url#root] is provided; install a published extension (not authored by Firebase) if [publisherId/extensionId] is provided " + : "") + + "or run with `-i` to see all available extensions." + ) + .withForce() + .option("--params ", "name of params variables file with .env format.") + .before(requirePermissions, ["firebaseextensions.instances.create"]) + .before(ensureExtensionsApiEnabled) + .before(checkMinRequiredVersion, "extMinVersion") + .before(diagnoseAndFixProject) + .action(async (extensionName: string, options: any) => { + const projectId = needProjectId(options); + const paramsEnvPath = options.params; + let learnMore = false; + if (!extensionName) { + if (options.interactive) { + learnMore = true; + extensionName = await promptForOfficialExtension( + "Which official extension do you wish to install?\n" + + " Select an extension, then press Enter to learn more." + ); + } else { + throw new FirebaseError( + `Unable to find published extension '${clc.bold(extensionName)}'. ` + + `Run ${clc.bold( + "firebase ext:install -i" + )} to select from the list of all available published extensions.` + ); + } + } + let source; + let extVersion; + // If the user types in URL, or a local path (prefixed with ~/, ../, or ./), install from local/URL source. + // Otherwise, treat the input as an extension reference and proceed with reference-based installation. + if (isLocalOrURLPath(extensionName)) { + track("Extension Install", "Install by Source", options.interactive ? 1 : 0); + source = await infoInstallBySource(projectId, extensionName); + } else { + track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); + extVersion = await infoInstallByReference(extensionName, options.interactive); + } + if ( + !(await confirm({ + nonInteractive: options.nonInteractive, + force: options.force, + default: true, + })) + ) { + return; + } + if (!source && !extVersion) { + throw new FirebaseError( + "Could not find a source. Please specify a valid source to continue." + ); + } + const spec = source?.spec || extVersion?.spec; + if (!spec) { + throw new FirebaseError( + `Could not find the extension.yaml for extension '${clc.bold( + extensionName + )}'. Please make sure this is a valid extension and try again.` + ); + } + if (learnMore) { + utils.logLabeledBullet( + logPrefix, + `You selected: ${clc.bold(spec.displayName)}.\n` + + `${spec.description}\n` + + `View details: https://firebase.google.com/products/extensions/${spec.name}\n` + ); + } + try { + return installExtension({ + paramsEnvPath, + projectId, + extensionName, + source, + extVersion, + nonInteractive: options.nonInteractive, + force: options.force, + }); + } catch (err: any) { + if (!(err instanceof FirebaseError)) { + throw new FirebaseError(`Error occurred installing the extension: ${err.message}`, { + original: err, + }); + } + throw err; + } + }); + +async function infoInstallBySource( + projectId: string, + extensionName: string +): Promise { + // Create a one off source to use for the install flow. + let source; + try { + source = await createSourceFromLocation(projectId, extensionName); + } catch (err: any) { + throw new FirebaseError( + `Unable to find published extension '${clc.bold(extensionName)}', ` + + `and encountered the following error when trying to create an instance of extension '${clc.bold( + extensionName + )}':\n ${err.message}` + ); + } + displayExtInfo(extensionName, "", source.spec); + return source; +} + +async function infoInstallByReference( + extensionName: string, + interactive: boolean +): Promise { + // Infer firebase if publisher ID not provided. + if (extensionName.split("/").length < 2) { + const [extensionID, version] = extensionName.split("@"); + extensionName = `firebase/${extensionID}@${version || "latest"}`; + } + // Get the correct version for a given extension reference from the Registry API. + const ref = refs.parse(extensionName); + const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref)); + if (!ref.version) { + track("Extension Install", "Install by Extension Version Ref", interactive ? 1 : 0); + extensionName = `${extensionName}@latest`; + } + const extVersion = await extensionsApi.getExtensionVersion(extensionName); + displayExtInfo(extensionName, ref.publisherId, extVersion.spec, true); + await displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion); + return extVersion; +} + interface InstallExtensionOptions { paramsEnvPath?: string; projectId: string; @@ -216,143 +356,3 @@ async function installExtension(options: InstallExtensionOptions): Promise }); } } - -async function infoInstallBySource( - projectId: string, - extensionName: string -): Promise { - // Create a one off source to use for the install flow. - let source; - try { - source = await createSourceFromLocation(projectId, extensionName); - } catch (err: any) { - throw new FirebaseError( - `Unable to find published extension '${clc.bold(extensionName)}', ` + - `and encountered the following error when trying to create an instance of extension '${clc.bold( - extensionName - )}':\n ${err.message}` - ); - } - displayExtInfo(extensionName, "", source.spec); - return source; -} - -async function infoInstallByReference( - extensionName: string, - interactive: boolean -): Promise { - // Infer firebase if publisher ID not provided. - if (extensionName.split("/").length < 2) { - const [extensionID, version] = extensionName.split("@"); - extensionName = `firebase/${extensionID}@${version || "latest"}`; - } - // Get the correct version for a given extension reference from the Registry API. - const ref = refs.parse(extensionName); - const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref)); - if (!ref.version) { - track("Extension Install", "Install by Extension Version Ref", interactive ? 1 : 0); - extensionName = `${extensionName}@latest`; - } - const extVersion = await extensionsApi.getExtensionVersion(extensionName); - displayExtInfo(extensionName, ref.publisherId, extVersion.spec, true); - await displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion); - return extVersion; -} - -/** - * Command for installing an extension - */ -export default new Command("ext:install [extensionName]") - .description( - "install an official extension if [extensionName] or [extensionName@version] is provided; " + - (previews.extdev - ? "install a local extension if [localPathOrUrl] or [url#root] is provided; install a published extension (not authored by Firebase) if [publisherId/extensionId] is provided " - : "") + - "or run with `-i` to see all available extensions." - ) - .withForce() - .option("--params ", "name of params variables file with .env format.") - .before(requirePermissions, ["firebaseextensions.instances.create"]) - .before(ensureExtensionsApiEnabled) - .before(checkMinRequiredVersion, "extMinVersion") - .before(diagnoseAndFixProject) - .action(async (extensionName: string, options: any) => { - const projectId = needProjectId(options); - const paramsEnvPath = options.params; - let learnMore = false; - if (!extensionName) { - if (options.interactive) { - learnMore = true; - extensionName = await promptForOfficialExtension( - "Which official extension do you wish to install?\n" + - " Select an extension, then press Enter to learn more." - ); - } else { - throw new FirebaseError( - `Unable to find published extension '${clc.bold(extensionName)}'. ` + - `Run ${clc.bold( - "firebase ext:install -i" - )} to select from the list of all available published extensions.` - ); - } - } - let source; - let extVersion; - // If the user types in URL, or a local path (prefixed with ~/, ../, or ./), install from local/URL source. - // Otherwise, treat the input as an extension reference and proceed with reference-based installation. - if (isLocalOrURLPath(extensionName)) { - track("Extension Install", "Install by Source", options.interactive ? 1 : 0); - source = await infoInstallBySource(projectId, extensionName); - } else { - track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); - extVersion = await infoInstallByReference(extensionName, options.interactive); - } - if ( - !(await confirm({ - nonInteractive: options.nonInteractive, - force: options.force, - default: true, - })) - ) { - return; - } - if (!source && !extVersion) { - throw new FirebaseError( - "Could not find a source. Please specify a valid source to continue." - ); - } - const spec = source?.spec || extVersion?.spec; - if (!spec) { - throw new FirebaseError( - `Could not find the extension.yaml for extension '${clc.bold( - extensionName - )}'. Please make sure this is a valid extension and try again.` - ); - } - if (learnMore) { - utils.logLabeledBullet( - logPrefix, - `You selected: ${clc.bold(spec.displayName)}.\n` + - `${spec.description}\n` + - `View details: https://firebase.google.com/products/extensions/${spec.name}\n` - ); - } - try { - return installExtension({ - paramsEnvPath, - projectId, - extensionName, - source, - extVersion, - nonInteractive: options.nonInteractive, - force: options.force, - }); - } catch (err: any) { - if (!(err instanceof FirebaseError)) { - throw new FirebaseError(`Error occurred installing the extension: ${err.message}`, { - original: err, - }); - } - throw err; - } - }); diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 3c3bc5621fe..ac656bff3d6 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -26,23 +26,6 @@ marked.setOptions({ renderer: new TerminalRenderer(), }); -/** - * We do not currently support uninstalling extensions that require additional uninstall steps to be taken in the CLI. Direct them to the Console to uninstall the extension. - * - * @param projectId ID of the user's project - * @param instanceId ID of the extension instance - * @return a void Promise - */ -function consoleUninstallOnly(projectId: string, instanceId: string): Promise { - const instanceURL = `https://console.firebase.google.com/project/${projectId}/extensions/instances/${instanceId}`; - const consoleUninstall = - "This extension has additional uninstall checks that are not currently supported by the CLI, and can only be uninstalled through the Firebase Console. " + - `Please visit **[${instanceURL}](${instanceURL})** to uninstall this extension.`; - logger.info("\n"); - utils.logLabeledWarning(logPrefix, marked(consoleUninstall)); - return Promise.resolve(); -} - export default new Command("ext:uninstall ") .description("uninstall an extension that is installed in your Firebase project by instance ID") .withForce() @@ -139,3 +122,20 @@ export default new Command("ext:uninstall ") } utils.logLabeledSuccess(logPrefix, `uninstalled ${clc.bold(instanceId)}`); }); + +/** + * We do not currently support uninstalling extensions that require additional uninstall steps to be taken in the CLI. Direct them to the Console to uninstall the extension. + * + * @param projectId ID of the user's project + * @param instanceId ID of the extension instance + * @return a void Promise + */ +function consoleUninstallOnly(projectId: string, instanceId: string): Promise { + const instanceURL = `https://console.firebase.google.com/project/${projectId}/extensions/instances/${instanceId}`; + const consoleUninstall = + "This extension has additional uninstall checks that are not currently supported by the CLI, and can only be uninstalled through the Firebase Console. " + + `Please visit **[${instanceURL}](${instanceURL})** to uninstall this extension.`; + logger.info("\n"); + utils.logLabeledWarning(logPrefix, marked(consoleUninstall)); + return Promise.resolve(); +} diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 8305ab2cc4c..fbfe6540a15 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -44,17 +44,6 @@ marked.setOptions({ renderer: new TerminalRenderer(), }); -function isValidUpdate(existingSourceOrigin: SourceOrigin, newSourceOrigin: SourceOrigin): boolean { - if (existingSourceOrigin === SourceOrigin.PUBLISHED_EXTENSION) { - return [SourceOrigin.PUBLISHED_EXTENSION, SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes( - newSourceOrigin - ); - } else if (existingSourceOrigin === SourceOrigin.LOCAL) { - return [SourceOrigin.LOCAL, SourceOrigin.URL].includes(newSourceOrigin); - } - return false; -} - /** * Command for updating an existing extension instance */ @@ -290,3 +279,14 @@ export default new Command("ext:update [updateSource]") throw err; } }); + +function isValidUpdate(existingSourceOrigin: SourceOrigin, newSourceOrigin: SourceOrigin): boolean { + if (existingSourceOrigin === SourceOrigin.PUBLISHED_EXTENSION) { + return [SourceOrigin.PUBLISHED_EXTENSION, SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes( + newSourceOrigin + ); + } else if (existingSourceOrigin === SourceOrigin.LOCAL) { + return [SourceOrigin.LOCAL, SourceOrigin.URL].includes(newSourceOrigin); + } + return false; +} From 0d943fb29b7e493dc0af958011120f4b16bf6543 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 22 Feb 2022 13:39:30 -0800 Subject: [PATCH 0101/1699] Update v2 function deploys (#4205) This PR is combination of the following: * Rollback of https://github.com/firebase/firebase-tools/pull/4198: This PR made wrong diagnosis and doesn't fix anything. * Fix foward of https://github.com/firebase/firebase-tools/pull/4197: Environment variables are set on more appropriate module. * Partial rollback of https://github.com/firebase/firebase-tools/pull/4124: We need to iron out some details with event trigger parsing before enabling for v2 preview. --- CHANGELOG.md | 1 - src/deploy/functions/prepare.ts | 9 ------ src/deploy/functions/release/fabricator.ts | 12 +++++-- src/deploy/functions/runtimes/node/index.ts | 36 --------------------- src/gcp/cloudfunctionsv2.ts | 8 +++++ src/test/gcp/cloudfunctionsv2.spec.ts | 5 +++ 6 files changed, 23 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dff350d288..41b13b48802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,2 @@ -- Fixes bug where updating gen 2 pubsub functions always failed (#4198). - Updates reserved environment variables for CF3 to include 'EVENTARC_CLOUD_EVENT_SOURCE' (#4196). - Fixes arg order for `firebase emulators:start --only storage` (#4195). diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 32f2b2ac0da..34f0b29bd21 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -138,15 +138,6 @@ export async function prepare( // Setup environment variables on each function. for (const endpoint of backend.allEndpoints(wantBackend)) { endpoint.environmentVariables = wantBackend.environmentVariables; - if (endpoint.platform === "gcfv2") { - if (backend.isEventTriggered(endpoint)) { - // By default, Functions Framework in GCFv2 opts to downcast incoming cloudevent messages to legacy formats. - // Since Firebase Functions SDK expects messages in cloudevent format, we set FUNCTION_SIGNATURE_TYPE to tell - // Functions Framework to disable downcast before passing the cloudevent message to function handler. - // See https://github.com/GoogleCloudPlatform/functions-framework-nodejs/blob/master/README.md#configure-the-functions-framework - endpoint.environmentVariables["FUNCTION_SIGNATURE_TYPE"] = "cloudevent"; - } - } } // Enable required APIs. This may come implicitly from triggers (e.g. scheduled triggers diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 954e69ccd0e..08320fd47a1 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -279,7 +279,7 @@ export class Fabricator { .catch(rethrowAs(endpoint, "create topic")); } - const resultFunction = (await this.functionExecutor + const resultFunction = await this.functionExecutor .run(async () => { const op: { name: string } = await gcfV2.createFunction(apiFunction); return await poller.pollOperation({ @@ -288,7 +288,7 @@ export class Fabricator { operationResourceName: op.name, }); }) - .catch(rethrowAs(endpoint, "create"))) as gcfV2.CloudFunction; + .catch(rethrowAs(endpoint, "create")); endpoint.uri = resultFunction.serviceConfig.uri; const serviceName = resultFunction.serviceConfig.service!; @@ -362,6 +362,14 @@ export class Fabricator { } const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage[endpoint.region]); + // N.B. As of GCFv2 private preview the API chokes on any update call that + // includes the pub/sub topic even if that topic is unchanged. + // We know that the user hasn't changed the topic between deploys because + // of checkForInvalidChangeOfTrigger(). + if (apiFunction.eventTrigger?.pubsubTopic) { + delete apiFunction.eventTrigger.pubsubTopic; + } + const resultFunction = await this.functionExecutor .run(async () => { const op: { name: string } = await gcfV2.updateFunction(apiFunction); diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 0e5084e5dc4..4bd1eeb125b 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -1,24 +1,17 @@ import { promisify } from "util"; import * as fs from "fs"; import * as path from "path"; -import * as portfinder from "portfinder"; import * as spawn from "cross-spawn"; -import * as semver from "semver"; import fetch from "node-fetch"; import { FirebaseError } from "../../../../error"; import { getRuntimeChoice } from "./parseRuntimeAndValidateSDK"; import { logger } from "../../../../logger"; -import { previews } from "../../../../previews"; -import { logLabeledWarning } from "../../../../utils"; import * as backend from "../../backend"; import * as runtimes from ".."; import * as validate from "./validate"; import * as versioning from "./versioning"; import * as parseTriggers from "./parseTriggers"; -import * as discovery from "../discovery"; - -const MIN_FUNCTIONS_SDK_VERSION = "3.18.1"; export async function tryCreateDelegate( context: runtimes.DelegateContext @@ -124,35 +117,6 @@ export class Delegate { config: backend.RuntimeConfigValues, env: backend.EnvironmentVariables ): Promise { - if (previews.functionsv2) { - if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) { - logLabeledWarning( - "functions", - `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` + - `Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}` - ); - return parseTriggers.discoverBackend( - this.projectId, - this.sourceDir, - this.runtime, - config, - env - ); - } - let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime); - if (!discovered) { - const getPort = promisify(portfinder.getPort) as () => Promise; - const port = await getPort(); - const kill = await this.serve(port, env); - try { - discovered = await discovery.detectFromPort(port, this.projectId, this.runtime); - } finally { - await kill(); - } - } - discovered.environmentVariables = env; - return discovered; - } return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env); } } diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 6b7311288af..83256cb79d2 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -446,6 +446,14 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage if (endpoint.eventTrigger.retry) { logger.warn("Cannot set a retry policy on Cloud Function", endpoint.id); } + // By default, Functions Framework in GCFv2 opts to downcast incoming cloudevent messages to legacy formats. + // Since Firebase Functions SDK expects messages in cloudevent format, we set FUNCTION_SIGNATURE_TYPE to tell + // Functions Framework to disable downcast before passing the cloudevent message to function handler. + // See https://github.com/GoogleCloudPlatform/functions-framework-nodejs/blob/master/README.md#configure-the-functions- + gcfFunction.serviceConfig.environmentVariables = { + ...gcfFunction.serviceConfig.environmentVariables, + FUNCTION_SIGNATURE_TYPE: "cloudevent", + }; } else if (backend.isScheduleTriggered(endpoint)) { // trigger type defaults to HTTPS. gcfFunction.labels = { ...gcfFunction.labels, "deployment-scheduled": "true" }; diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index a059227ba1d..1725b2238b8 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -117,6 +117,10 @@ describe("cloudfunctionsv2", () => { }, ], }, + serviceConfig: { + ...CLOUD_FUNCTION_V2.serviceConfig, + environmentVariables: { FUNCTION_SIGNATURE_TYPE: "cloudevent" }, + }, }; expect( cloudfunctionsv2.functionFromEndpoint(eventEndpoint, CLOUD_FUNCTION_V2_SOURCE) @@ -215,6 +219,7 @@ describe("cloudfunctionsv2", () => { minInstanceCount: 1, timeoutSeconds: 15, availableMemory: "128M", + environmentVariables: { FUNCTION_SIGNATURE_TYPE: "cloudevent" }, }, }; From 22c04d462bccc44ff62e6626015b3ec7276afb0c Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 23 Feb 2022 09:13:31 -0800 Subject: [PATCH 0102/1699] Adds CF3 triggers to listBackends endpoint (#4211) * Adds CF3 triggers to listBackends endpoint * Add CF3 backends to test * ?? --- .../emulator-tests/functionsEmulator.spec.ts | 72 ++++++++++++++++++- src/emulator/functionsEmulator.ts | 7 +- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index 8e8a101ad04..78bcfba6649 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -644,7 +644,77 @@ describe("FunctionsEmulator-Hub", () => { .expect(200) .then((res) => { // TODO(b/216642962): Add tests for this endpoint that validate behavior when there are Extensions running - expect(res.body.backends).to.deep.equal([{ env: {}, functionTriggers: [] }]); + expect(res.body.backends).to.deep.equal([ + { + env: {}, + functionTriggers: [ + { + entryPoint: "function_id", + httpsTrigger: {}, + id: "us-central1-function_id", + labels: {}, + name: "function_id", + platform: "gcfv1", + region: "us-central1", + }, + { + entryPoint: "function_id", + httpsTrigger: {}, + id: "europe-west2-function_id", + labels: {}, + name: "function_id", + platform: "gcfv1", + region: "europe-west2", + }, + { + entryPoint: "function_id", + httpsTrigger: {}, + id: "europe-west3-function_id", + labels: {}, + name: "function_id", + platform: "gcfv1", + region: "europe-west3", + }, + { + entryPoint: "callable_function_id", + httpsTrigger: {}, + id: "us-central1-callable_function_id", + labels: { + "deployment-callable": "true", + }, + name: "callable_function_id", + platform: "gcfv1", + region: "us-central1", + }, + { + entryPoint: "nested.function_id", + httpsTrigger: {}, + id: "us-central1-nested-function_id", + labels: {}, + name: "nested-function_id", + platform: "gcfv1", + region: "us-central1", + }, + { + entryPoint: "secrets_function_id", + httpsTrigger: {}, + id: "us-central1-secrets_function_id", + labels: {}, + name: "secrets_function_id", + platform: "gcfv1", + region: "us-central1", + secretEnvironmentVariables: [ + { + key: "MY_SECRET", + projectId: "fake-project-id", + secret: "MY_SECRET", + version: "1", + }, + ], + }, + ], + }, + ]); }); }).timeout(TIMEOUT_LONG); diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 0e06f55ecfb..90864386bf2 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -808,14 +808,15 @@ export class FunctionsEmulator implements EmulatorInstance { } getBackendInfo(): BackendInfo[] { + const cf3Triggers = Object.values(this.triggers) + .filter((t) => !t.backend.extensionInstanceId) + .map((t) => t.def); return this.args.emulatableBackends.map((e: EmulatableBackend) => { return { env: e.env, extensionInstanceId: e.extensionInstanceId, extensionVersion: e.extensionVersion, - functionTriggers: e.predefinedTriggers ?? [], - // TODO: Right now, functionTriggers will be an empty list for CF3 backends. - // We need to figure out how to expose the loaded triggers here here. + functionTriggers: e.predefinedTriggers ?? cf3Triggers, }; }); } From 73eec36614fdab4936cb3f7660105eb7154ce1ce Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 23 Feb 2022 11:52:01 -0800 Subject: [PATCH 0103/1699] Fix bug where environment variables for v2 functions weren't being updated (#4209) When calculating update mask for v2, we weren't properly accounting for the fact that the object holding environment variables should not be recursed. Fixes https://github.com/firebase/firebase-tools/issues/4192 --- CHANGELOG.md | 1 + src/gcp/cloudfunctionsv2.ts | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b13b48802..2e9330e28c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Updates reserved environment variables for CF3 to include 'EVENTARC_CLOUD_EVENT_SOURCE' (#4196). - Fixes arg order for `firebase emulators:start --only storage` (#4195). +- Fixes bug where environment variable for gen 2 functions weren't updated on deploy (#4209). diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 83256cb79d2..8f9097afd9a 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -285,7 +285,7 @@ export async function getFunction( */ export async function listFunctions(projectId: string, region: string): Promise { const res = await listFunctionsInternal(projectId, region); - if (res.unreachable!.includes(region)) { + if (res.unreachable.includes(region)) { throw new FirebaseError(`Cloud Functions region ${region} is unavailable`); } return res.functions; @@ -333,9 +333,16 @@ async function listFunctionsInternal( export async function updateFunction( cloudFunction: Omit ): Promise { + // Keys in labels and environmentVariables are user defined, so we don't recurse + // for field masks. + const fieldMasks = proto.fieldMasks( + cloudFunction, + /* doNotRecurseIn...=*/ "labels", + "serviceConfig.environmentVariables" + ); try { const queryParams = { - updateMask: proto.fieldMasks(cloudFunction).join(","), + updateMask: fieldMasks.join(","), }; const res = await client.patch( cloudFunction.name, @@ -480,7 +487,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi } else if (gcfFunction.eventTrigger) { trigger = { eventTrigger: { - eventType: gcfFunction.eventTrigger!.eventType, + eventType: gcfFunction.eventTrigger.eventType, eventFilters: {}, retry: false, }, From e47d5f351028d4bf0cee57a86ed93c62f1880ae7 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Wed, 23 Feb 2022 15:28:55 -0500 Subject: [PATCH 0104/1699] Add void in front of unawaited promises to remove lint errors (#4215) --- src/auth.ts | 6 +++--- src/command.ts | 2 +- src/deploy/functions/checkIam.ts | 2 +- src/deploy/functions/ensure.ts | 4 ++-- src/deploy/functions/prepare.ts | 2 +- .../functions/runtimes/node/parseRuntimeAndValidateSDK.ts | 4 ++-- src/deploy/functions/runtimes/node/versioning.ts | 2 +- src/deploy/hosting/deploy.ts | 4 ++-- src/emulator/controller.ts | 4 ++-- src/emulator/functionsEmulator.ts | 4 ++-- src/extensions/paramHelper.ts | 8 ++++---- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index b2fc80e19af..8a1e1accba3 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -469,7 +469,7 @@ async function loginRemotely(userHint?: string): Promise { codeVerifier ); - track("login", "google_remote"); + void track("login", "google_remote"); return { user: jwt.decode(tokens.id_token!) as User, @@ -493,7 +493,7 @@ async function loginWithLocalhostGoogle(port: number, userHint?: string): Promis getTokensFromAuthorizationCode ); - track("login", "google_localhost"); + void track("login", "google_localhost"); // getTokensFromAuthoirzationCode doesn't handle the --token case, so we know we'll // always have an id_token. return { @@ -514,7 +514,7 @@ async function loginWithLocalhostGitHub(port: number): Promise { successTemplate, getGithubTokensFromAuthorizationCode ); - track("login", "google_localhost"); + void track("login", "google_localhost"); return tokens; } diff --git a/src/command.ts b/src/command.ts index 0a566bd0680..2370a368d9b 100644 --- a/src/command.ts +++ b/src/command.ts @@ -195,7 +195,7 @@ export class Command { ); } const duration = new Date().getTime() - start; - track(this.name, "success", duration).then(() => process.exit()); + void track(this.name, "success", duration).then(() => process.exit()); }) .catch(async (err) => { if (getInheritedOption(options, "json")) { diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index 0a8362f6fa0..d4f2bcd892c 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -95,7 +95,7 @@ export async function checkHttpIam( } if (!passed) { - track("Error (User)", "deploy:functions:http_create_missing_iam"); + void track("Error (User)", "deploy:functions:http_create_missing_iam"); throw new FirebaseError( `Missing required permission on project ${bold( context.projectId diff --git a/src/deploy/functions/ensure.ts b/src/deploy/functions/ensure.ts index 49e86a3327b..a14884d1e9a 100644 --- a/src/deploy/functions/ensure.ts +++ b/src/deploy/functions/ensure.ts @@ -30,7 +30,7 @@ export async function defaultServiceAccount(e: backend.Endpoint): Promise { const name = instance.getName(); // Log the command for analytics - track("Emulator Run", name); + void track("Emulator Run", name); await EmulatorRegistry.start(instance); } @@ -396,7 +396,7 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) // since we originally mistakenly reported emulators:start events // for each emulator, by reporting the "hub" we ensure that our // historical data can still be viewed. - track("emulators:start", "hub"); + void track("emulators:start", "hub"); await startEmulator(hub); } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 90864386bf2..465cd5cdfb7 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -1263,7 +1263,7 @@ export class FunctionsEmulator implements EmulatorInstance { }); // For analytics, track the invoked service - track(EVENT_INVOKE, getFunctionService(trigger)); + void track(EVENT_INVOKE, getFunctionService(trigger)); worker.waitForDone().then(() => { resolve({ status: "acknowledged" }); @@ -1377,7 +1377,7 @@ export class FunctionsEmulator implements EmulatorInstance { // Wait for the worker to set up its internal HTTP server await worker.waitForSocketReady(); - track(EVENT_INVOKE, "https"); + void track(EVENT_INVOKE, "https"); this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`); diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index b84d4a9ff95..50717fab2a5 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -93,7 +93,7 @@ export async function getParams(args: { !!args.reconfiguring ); } - track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params)); + void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params)); return params; } @@ -134,7 +134,7 @@ export async function getParamsForUpdate(args: { instanceId: args.instanceId, }); } - track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params)); + void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params)); return params; } @@ -207,9 +207,9 @@ export function getParamsFromFile(args: { let envParams; try { envParams = readEnvFile(args.paramsEnvPath); - track("Extension Env File", "Present"); + void track("Extension Env File", "Present"); } catch (err: any) { - track("Extension Env File", "Invalid"); + void track("Extension Env File", "Invalid"); throw new FirebaseError(`Error reading env file: ${err.message}\n`, { original: err }); } const params = populateDefaultParams(envParams, args.paramSpecs); From 6c9e4a81da368c8c1d8cddcdd30eec440d1e2056 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Wed, 23 Feb 2022 16:44:55 -0500 Subject: [PATCH 0105/1699] Better error handling on silently failing source uploads (#4216) --- src/extensions/extensionsHelper.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index b68e4119fe0..cb6af813f88 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -490,7 +490,9 @@ export async function publishExtensionVersionFromLocalSource(args: { packageUri = storageOrigin + objectPath + "?alt=media"; } catch (err: any) { uploadSpinner.fail(); - throw err; + throw new FirebaseError(`Failed to archive and upload extension source, ${err}`, { + original: err, + }); } const publishSpinner = ora(`Publishing ${clc.bold(ref)}`); let res; @@ -538,7 +540,9 @@ export async function createSourceFromLocation( extensionRoot = "/"; } catch (err: any) { uploadSpinner.fail(); - throw err; + throw new FirebaseError(`Failed to archive and upload extension source, ${err}`, { + original: err, + }); } } else { [packageUri, extensionRoot] = sourceUri.split("#"); From 5564ad1836ca91010e4b99ff78ef70f8f44bdc89 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Wed, 23 Feb 2022 19:38:38 -0500 Subject: [PATCH 0106/1699] Emulator fix - stop triggering metadata update on a file upload (#4213) * stop triggering metadata update on a file upload * changelog entry * adding trigger tests * formatter --- CHANGELOG.md | 1 + scripts/integration-helpers/framework.ts | 8 ++ .../functions/index.js | 24 +++++ scripts/triggers-end-to-end-tests/tests.ts | 88 +++++++++++++++---- src/emulator/storage/files.ts | 6 +- 5 files changed, 107 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e9330e28c1..8ec73b46584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Updates reserved environment variables for CF3 to include 'EVENTARC_CLOUD_EVENT_SOURCE' (#4196). - Fixes arg order for `firebase emulators:start --only storage` (#4195). - Fixes bug where environment variable for gen 2 functions weren't updated on deploy (#4209). +- Fixes an issue in the storage emulator where a file upload would trigger functions with a metadata update handler (#4213). diff --git a/scripts/integration-helpers/framework.ts b/scripts/integration-helpers/framework.ts index 474d0a3af2c..5b65ff68e0e 100644 --- a/scripts/integration-helpers/framework.ts +++ b/scripts/integration-helpers/framework.ts @@ -291,6 +291,14 @@ export class TriggerEndToEndTest { return this.invokeHttpFunction("writeToSpecificStorageBucket"); } + updateMetadataDefaultStorage(): Promise { + return this.invokeHttpFunction("updateMetadataFromDefaultStorage"); + } + + updateMetadataSpecificStorageBucket(): Promise { + return this.invokeHttpFunction("updateMetadataFromSpecificStorageBucket"); + } + updateDeleteFromDefaultStorage(): Promise { return this.invokeHttpFunction("updateDeleteFromDefaultStorage"); } diff --git a/scripts/triggers-end-to-end-tests/functions/index.js b/scripts/triggers-end-to-end-tests/functions/index.js index 9778e546674..4338bfd184a 100644 --- a/scripts/triggers-end-to-end-tests/functions/index.js +++ b/scripts/triggers-end-to-end-tests/functions/index.js @@ -126,6 +126,30 @@ exports.writeToSpecificStorageBucket = functions.https.onRequest(async (req, res res.json({ created: "ok" }); }); +exports.updateMetadataFromDefaultStorage = functions.https.onRequest(async (req, res) => { + await admin.storage().bucket().file(STORAGE_FILE_NAME).save("hello metadata update!"); + console.log("Wrote to Storage bucket"); + await admin.storage().bucket().file(STORAGE_FILE_NAME).setMetadata({ somekey: "someval" }); + console.log("Updated metadata of default Storage bucket"); + res.json({ done: "ok" }); +}); + +exports.updateMetadataFromSpecificStorageBucket = functions.https.onRequest(async (req, res) => { + await admin + .storage() + .bucket("test-bucket") + .file(STORAGE_FILE_NAME) + .save("hello metadata update!"); + console.log("Wrote to a specific Storage bucket"); + await admin + .storage() + .bucket("test-bucket") + .file(STORAGE_FILE_NAME) + .setMetadata({ somenewkey: "somenewval" }); + console.log("Updated metadata of a specific Storage bucket"); + res.json({ done: "ok" }); +}); + exports.updateDeleteFromDefaultStorage = functions.https.onRequest(async (req, res) => { await admin.storage().bucket().file(STORAGE_FILE_NAME).save("something new!"); console.log("Wrote to Storage bucket"); diff --git a/scripts/triggers-end-to-end-tests/tests.ts b/scripts/triggers-end-to-end-tests/tests.ts index 8063f98f20f..d347b10c4c3 100755 --- a/scripts/triggers-end-to-end-tests/tests.ts +++ b/scripts/triggers-end-to-end-tests/tests.ts @@ -270,18 +270,18 @@ describe("storage emulator function triggers", () => { }); it("should have triggered cloud functions", () => { - /* on object create two events fire (finalize & metadata update) */ + /* on object create one event fires (finalize) */ // default bucket expect(test.storageFinalizedTriggerCount).to.equal(1); - expect(test.storageMetadataTriggerCount).to.equal(1); expect(test.storageV2FinalizedTriggerCount).to.equal(1); - expect(test.storageV2MetadataTriggerCount).to.equal(1); + expect(test.storageMetadataTriggerCount).to.equal(0); + expect(test.storageV2MetadataTriggerCount).to.equal(0); expect(test.storageDeletedTriggerCount).to.equal(0); expect(test.storageV2DeletedTriggerCount).to.equal(0); // specific bucket expect(test.storageBucketFinalizedTriggerCount).to.equal(0); - expect(test.storageBucketMetadataTriggerCount).to.equal(0); expect(test.storageBucketV2FinalizedTriggerCount).to.equal(0); + expect(test.storageBucketMetadataTriggerCount).to.equal(0); expect(test.storageBucketV2MetadataTriggerCount).to.equal(0); expect(test.storageBucketDeletedTriggerCount).to.equal(0); expect(test.storageBucketV2DeletedTriggerCount).to.equal(0); @@ -297,25 +297,81 @@ describe("storage emulator function triggers", () => { }); it("should have triggered cloud functions", () => { - /* on object create two events fire (finalize & metadata update) */ + /* on object create one event fires (finalize) */ // default bucket expect(test.storageFinalizedTriggerCount).to.equal(0); + expect(test.storageV2FinalizedTriggerCount).to.equal(0); expect(test.storageMetadataTriggerCount).to.equal(0); + expect(test.storageV2MetadataTriggerCount).to.equal(0); + expect(test.storageDeletedTriggerCount).to.equal(0); + expect(test.storageV2DeletedTriggerCount).to.equal(0); + // specific bucket + expect(test.storageBucketFinalizedTriggerCount).to.equal(1); + expect(test.storageBucketV2FinalizedTriggerCount).to.equal(1); + expect(test.storageBucketMetadataTriggerCount).to.equal(0); + expect(test.storageBucketV2MetadataTriggerCount).to.equal(0); + expect(test.storageBucketDeletedTriggerCount).to.equal(0); + expect(test.storageBucketV2DeletedTriggerCount).to.equal(0); + test.resetCounts(); + }); + + it("should write and update metadata from the default bucket of the storage emulator", async function (this) { + this.timeout(EMULATOR_TEST_TIMEOUT); + + const response = await test.updateMetadataDefaultStorage(); + expect(response.status).to.equal(200); + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); + }); + + it("should have triggered cloud functions", () => { + /* on object create one event fires (finalize) */ + /* on update one event fires (metadataUpdate) */ + // default bucket + expect(test.storageFinalizedTriggerCount).to.equal(1); + expect(test.storageV2FinalizedTriggerCount).to.equal(1); + expect(test.storageMetadataTriggerCount).to.equal(1); + expect(test.storageV2MetadataTriggerCount).to.equal(1); + expect(test.storageDeletedTriggerCount).to.equal(0); + expect(test.storageV2DeletedTriggerCount).to.equal(0); + // specific bucket + expect(test.storageBucketFinalizedTriggerCount).to.equal(0); + expect(test.storageBucketV2FinalizedTriggerCount).to.equal(0); + expect(test.storageBucketMetadataTriggerCount).to.equal(0); + expect(test.storageBucketV2MetadataTriggerCount).to.equal(0); + expect(test.storageBucketDeletedTriggerCount).to.equal(0); + expect(test.storageBucketV2DeletedTriggerCount).to.equal(0); + test.resetCounts(); + }); + + it("should write and update metadata from a specific bucket of the storage emulator", async function (this) { + this.timeout(EMULATOR_TEST_TIMEOUT); + + const response = await test.updateMetadataSpecificStorageBucket(); + expect(response.status).to.equal(200); + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); + }); + + it("should have triggered cloud functions", () => { + /* on object create one event fires (finalize) */ + /* on update one event fires (metadataUpdate) */ + // default bucket + expect(test.storageFinalizedTriggerCount).to.equal(0); expect(test.storageV2FinalizedTriggerCount).to.equal(0); + expect(test.storageMetadataTriggerCount).to.equal(0); expect(test.storageV2MetadataTriggerCount).to.equal(0); expect(test.storageDeletedTriggerCount).to.equal(0); expect(test.storageV2DeletedTriggerCount).to.equal(0); // specific bucket expect(test.storageBucketFinalizedTriggerCount).to.equal(1); - expect(test.storageBucketMetadataTriggerCount).to.equal(1); expect(test.storageBucketV2FinalizedTriggerCount).to.equal(1); + expect(test.storageBucketMetadataTriggerCount).to.equal(1); expect(test.storageBucketV2MetadataTriggerCount).to.equal(1); expect(test.storageBucketDeletedTriggerCount).to.equal(0); expect(test.storageBucketV2DeletedTriggerCount).to.equal(0); test.resetCounts(); }); - it("should write, update, and delete from the default bucket of the storage emulator", async function (this) { + it("should write and delete from the default bucket of the storage emulator", async function (this) { this.timeout(EMULATOR_TEST_TIMEOUT); const response = await test.updateDeleteFromDefaultStorage(); @@ -324,26 +380,26 @@ describe("storage emulator function triggers", () => { }); it("should have triggered cloud functions", () => { - /* on update two events fire (finalize & metadata update) */ + /* on create one event fires (finalize) */ /* on delete one event fires (delete) */ // default bucket expect(test.storageFinalizedTriggerCount).to.equal(1); - expect(test.storageMetadataTriggerCount).to.equal(1); expect(test.storageV2FinalizedTriggerCount).to.equal(1); - expect(test.storageV2MetadataTriggerCount).to.equal(1); + expect(test.storageMetadataTriggerCount).to.equal(0); + expect(test.storageV2MetadataTriggerCount).to.equal(0); expect(test.storageDeletedTriggerCount).to.equal(1); expect(test.storageV2DeletedTriggerCount).to.equal(1); // specific bucket expect(test.storageBucketFinalizedTriggerCount).to.equal(0); - expect(test.storageBucketMetadataTriggerCount).to.equal(0); expect(test.storageBucketV2FinalizedTriggerCount).to.equal(0); + expect(test.storageBucketMetadataTriggerCount).to.equal(0); expect(test.storageBucketV2MetadataTriggerCount).to.equal(0); expect(test.storageBucketDeletedTriggerCount).to.equal(0); expect(test.storageBucketV2DeletedTriggerCount).to.equal(0); test.resetCounts(); }); - it("should write, update, and delete from a specific bucket of the storage emulator", async function (this) { + it("should write and delete from a specific bucket of the storage emulator", async function (this) { this.timeout(EMULATOR_TEST_TIMEOUT); const response = await test.updateDeleteFromSpecificStorageBucket(); @@ -352,20 +408,20 @@ describe("storage emulator function triggers", () => { }); it("should have triggered cloud functions", () => { - /* on update two events fire (finalize & metadata update) */ + /* on create one event fires (finalize) */ /* on delete one event fires (delete) */ // default bucket expect(test.storageFinalizedTriggerCount).to.equal(0); - expect(test.storageMetadataTriggerCount).to.equal(0); expect(test.storageV2FinalizedTriggerCount).to.equal(0); + expect(test.storageMetadataTriggerCount).to.equal(0); expect(test.storageV2MetadataTriggerCount).to.equal(0); expect(test.storageDeletedTriggerCount).to.equal(0); expect(test.storageV2DeletedTriggerCount).to.equal(0); // specific bucket expect(test.storageBucketFinalizedTriggerCount).to.equal(1); - expect(test.storageBucketMetadataTriggerCount).to.equal(1); expect(test.storageBucketV2FinalizedTriggerCount).to.equal(1); - expect(test.storageBucketV2MetadataTriggerCount).to.equal(1); + expect(test.storageBucketMetadataTriggerCount).to.equal(0); + expect(test.storageBucketV2MetadataTriggerCount).to.equal(0); expect(test.storageBucketDeletedTriggerCount).to.equal(1); expect(test.storageBucketV2DeletedTriggerCount).to.equal(1); test.resetCounts(); diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index eefd8141b16..b824c9ea7a7 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -286,8 +286,7 @@ export class StorageLayer { customMetadata: upload.metadata.metadata, }, this._cloudFunctions, - bytes, - upload.metadata + bytes ); const file = new StoredFile(finalMetadata, filePath); this._files.set(filePath, file); @@ -320,8 +319,7 @@ export class StorageLayer { customMetadata: incomingMetadata.metadata, }, this._cloudFunctions, - bytes, - incomingMetadata + bytes ); const file = new StoredFile(md, this._persistence.getDiskPath(filePath)); this._files.set(filePath, file); From afd434290f90d7f8e6f0b526a094a45f756cfcb6 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 24 Feb 2022 11:22:03 -0800 Subject: [PATCH 0107/1699] =?UTF-8?q?Changing=20backendInfo=20to=20include?= =?UTF-8?q?=20extension=20&=20support=20local=20Extensions=20=E2=80=A6=20(?= =?UTF-8?q?#4218)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Changing backendInfo to include extension & support local Extensions in the future * fixing emulator test --- .../emulator-tests/functionsEmulator.spec.ts | 3 +++ src/emulator/extensionsEmulator.ts | 3 +++ src/emulator/functionsEmulator.ts | 20 +++++++++++++------ src/test/emulators/extensionsEmulator.spec.ts | 17 +++++++++++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index 78bcfba6649..90e125f368a 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -6,6 +6,7 @@ import * as sinon from "sinon"; import * as supertest from "supertest"; import * as winston from "winston"; import * as logform from "logform"; +import * as path from "path"; import { EmulatedTriggerDefinition } from "../../src/emulator/functionsEmulatorShared"; import { @@ -644,8 +645,10 @@ describe("FunctionsEmulator-Hub", () => { .expect(200) .then((res) => { // TODO(b/216642962): Add tests for this endpoint that validate behavior when there are Extensions running + const expectedDirectory = path.resolve(`${__dirname}/../..`); expect(res.body.backends).to.deep.equal([ { + directory: expectedDirectory, env: {}, functionTriggers: [ { diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 121e842de06..25f52235347 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -139,12 +139,14 @@ export class ExtensionsEmulator { const extensionDir = await this.ensureSourceCode(instance); // TODO: This should find package.json, then use that as functionsDir. const functionsDir = path.join(extensionDir, "functions"); + // TODO(b/213335255): For local extensions, this should include extensionSpec instead of extensionVersion const env = Object.assign(this.autoPopulatedParams(instance), instance.params); const { extensionTriggers, nodeMajorVersion } = await getExtensionFunctionInfo( extensionDir, instance.instanceId, env ); + const extension = await planner.getExtension(instance); const extensionVersion = await planner.getExtensionVersion(instance); return { functionsDir, @@ -152,6 +154,7 @@ export class ExtensionsEmulator { predefinedTriggers: extensionTriggers, nodeMajorVersion: nodeMajorVersion, extensionInstanceId: instance.instanceId, + extension, extensionVersion, }; } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 465cd5cdfb7..dde6e4827aa 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -57,7 +57,7 @@ import { } from "./adminSdkConfig"; import { EventUtils } from "./events/types"; import { functionIdsAreValid } from "../deploy/functions/validate"; -import { ExtensionVersion } from "../extensions/extensionsApi"; +import { Extension, ExtensionSpec, ExtensionVersion } from "../extensions/extensionsApi"; import { getRuntimeDelegate } from "../deploy/functions/runtimes"; import * as backend from "../deploy/functions/backend"; import * as functionsEnv from "../functions/env"; @@ -89,17 +89,22 @@ export interface EmulatableBackend { nodeMajorVersion?: number; nodeBinary?: string; extensionInstanceId?: string; - extensionVersion?: ExtensionVersion; + extension?: Extension; // Only present for published extensions + extensionVersion?: ExtensionVersion; // Only present for published extensions + extensionSpec?: ExtensionSpec; // Only present for local extensions } /** * BackendInfo is an API type used by the Emulator UI containing info about an Extension or CF3 module. */ export interface BackendInfo { + directory: string; env: Record; functionTriggers: ParsedTriggerDefinition[]; extensionInstanceId?: string; - extensionVersion?: ExtensionVersion; + extension?: Extension; // Only present for published extensions + extensionVersion?: ExtensionVersion; // Only present for published extensions + extensionSpec?: ExtensionSpec; // Only present for local extensions } export interface FunctionsEmulatorArgs { @@ -813,10 +818,13 @@ export class FunctionsEmulator implements EmulatorInstance { .map((t) => t.def); return this.args.emulatableBackends.map((e: EmulatableBackend) => { return { + directory: e.functionsDir, env: e.env, - extensionInstanceId: e.extensionInstanceId, - extensionVersion: e.extensionVersion, - functionTriggers: e.predefinedTriggers ?? cf3Triggers, + extensionInstanceId: e.extensionInstanceId, // Present on all extensions + extension: e.extension, // Only present on published extensions + extensionVersion: e.extensionVersion, // Only present on published extensions + extensionSpec: e.extensionSpec, // Only present on local extensions + functionTriggers: e.predefinedTriggers ?? cf3Triggers, // If we don't have predefinedTriggers, this is the CF3 backend. }; }); } diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts index 298f02f56eb..40e280a5732 100644 --- a/src/test/emulators/extensionsEmulator.spec.ts +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -2,9 +2,22 @@ import { expect } from "chai"; import { ExtensionsEmulator } from "../../emulator/extensionsEmulator"; import { EmulatableBackend } from "../../emulator/functionsEmulator"; -import { ExtensionVersion } from "../../extensions/extensionsApi"; +import { + Extension, + ExtensionVersion, + RegistryLaunchStage, + Visibility, +} from "../../extensions/extensionsApi"; import * as planner from "../../deploy/extensions/planner"; +const TEST_EXTENSION: Extension = { + name: "publishers/firebase/extensions/storage-resize-images", + ref: "firebase/storage-resize-images", + visibility: Visibility.PUBLIC, + registryLaunchStage: RegistryLaunchStage.BETA, + createTime: "0", +}; + const TEST_EXTENSION_VERSION: ExtensionVersion = { name: "publishers/firebase/extensions/storage-resize-images/versions/0.1.18", ref: "firebase/storage-resize-images@0.1.18", @@ -47,6 +60,7 @@ describe("Extensions Emulator", () => { params: { LOCATION: "us-west1", }, + extension: TEST_EXTENSION, extensionVersion: TEST_EXTENSION_VERSION, }, expected: { @@ -75,6 +89,7 @@ describe("Extensions Emulator", () => { regions: ["us-west1"], }, ], + extension: TEST_EXTENSION, extensionVersion: TEST_EXTENSION_VERSION, }, }, From e57f77b29c69bfac3d6f1b8c22ea863d95df03d6 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 25 Feb 2022 06:41:30 -0800 Subject: [PATCH 0108/1699] adding extensions to emulator registry (#4220) --- src/emulator/controller.ts | 5 ++++- src/emulator/hub.ts | 9 ++++++--- src/emulator/registry.ts | 16 +++++++++++++++- src/emulator/types.ts | 1 + 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index a933d82b7e4..71b4c1cf5b5 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -3,7 +3,6 @@ import * as clc from "cli-color"; import * as fs from "fs"; import * as path from "path"; -import { Config } from "../config"; import { logger } from "../logger"; import * as track from "../track"; import * as utils from "../utils"; @@ -460,6 +459,10 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) }); const extensionsBackends = await extensionEmulator.getExtensionBackends(); emulatableBackends.push(...extensionsBackends); + + // Log the command for analytics + void track("Emulator Run", Emulators.EXTENSIONS); + EmulatorRegistry.registerExtensionsEmulator(); } if (emulatableBackends.length) { diff --git a/src/emulator/hub.ts b/src/emulator/hub.ts index 446097d143d..bd0dbb6e5b5 100644 --- a/src/emulator/hub.ts +++ b/src/emulator/hub.ts @@ -81,9 +81,12 @@ export class EmulatorHub implements EmulatorInstance { this.hub.get(EmulatorHub.PATH_EMULATORS, (req, res) => { const body: GetEmulatorsResponse = {}; - EmulatorRegistry.listRunning().forEach((name) => { - body[name] = EmulatorRegistry.get(name)!.getInfo(); - }); + for (const emulator of EmulatorRegistry.listRunning()) { + const info = EmulatorRegistry.getInfo(emulator); + if (info) { + body[emulator] = info; + } + } res.json(body); }); diff --git a/src/emulator/registry.ts b/src/emulator/registry.ts index f07d8a74c6d..7ebc0b4d96f 100644 --- a/src/emulator/registry.ts +++ b/src/emulator/registry.ts @@ -11,6 +11,8 @@ import { EmulatorLogger } from "./emulatorLogger"; * through the start() and stop() methods which ensures correctness. */ export class EmulatorRegistry { + private static extensionsEmulatorRegistered: boolean = false; + static async start(instance: EmulatorInstance): Promise { const description = Constants.description(instance.getName()); if (this.isRunning(instance.getName())) { @@ -27,6 +29,10 @@ export class EmulatorRegistry { await portUtils.waitForPortClosed(info.port, info.host); } + static registerExtensionsEmulator(): void { + this.extensionsEmulatorRegistered = true; + } + static async stop(name: Emulators): Promise { EmulatorLogger.forEmulator(name).logLabeled( "BULLET", @@ -92,6 +98,11 @@ export class EmulatorRegistry { } static isRunning(emulator: Emulators): boolean { + if (emulator === Emulators.EXTENSIONS) { + return ( + this.extensionsEmulatorRegistered && this.INSTANCES.get(Emulators.FUNCTIONS) !== undefined + ); + } const instance = this.INSTANCES.get(emulator); return instance !== undefined; } @@ -111,7 +122,10 @@ export class EmulatorRegistry { } static getInfo(emulator: Emulators): EmulatorInfo | undefined { - const instance = this.INSTANCES.get(emulator); + // For Extensions, return the info for the Functions Emulator. + const instance = this.INSTANCES.get( + emulator === Emulators.EXTENSIONS ? Emulators.FUNCTIONS : emulator + ); if (!instance) { return undefined; } diff --git a/src/emulator/types.ts b/src/emulator/types.ts index 5571515028b..99d5ce8390a 100644 --- a/src/emulator/types.ts +++ b/src/emulator/types.ts @@ -76,6 +76,7 @@ export const ALL_EMULATORS = [ Emulators.HUB, Emulators.UI, Emulators.LOGGING, + Emulators.EXTENSIONS, ...ALL_SERVICE_EMULATORS, ]; From 0b516d58c7a50a9127234016e14940ef768a1db5 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 25 Feb 2022 10:21:32 -0800 Subject: [PATCH 0109/1699] PR fixes from 4220 (#4224) --- src/emulator/hub.ts | 4 +--- src/emulator/registry.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/emulator/hub.ts b/src/emulator/hub.ts index bd0dbb6e5b5..518b49d28fc 100644 --- a/src/emulator/hub.ts +++ b/src/emulator/hub.ts @@ -83,9 +83,7 @@ export class EmulatorHub implements EmulatorInstance { const body: GetEmulatorsResponse = {}; for (const emulator of EmulatorRegistry.listRunning()) { const info = EmulatorRegistry.getInfo(emulator); - if (info) { - body[emulator] = info; - } + body[emulator] = info!; } res.json(body); }); diff --git a/src/emulator/registry.ts b/src/emulator/registry.ts index 7ebc0b4d96f..992c6195ed9 100644 --- a/src/emulator/registry.ts +++ b/src/emulator/registry.ts @@ -99,9 +99,7 @@ export class EmulatorRegistry { static isRunning(emulator: Emulators): boolean { if (emulator === Emulators.EXTENSIONS) { - return ( - this.extensionsEmulatorRegistered && this.INSTANCES.get(Emulators.FUNCTIONS) !== undefined - ); + return this.extensionsEmulatorRegistered && this.isRunning(Emulators.FUNCTIONS); } const instance = this.INSTANCES.get(emulator); return instance !== undefined; From fa9093d999218c54048bc6a493d8a26bc4460e07 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Fri, 25 Feb 2022 13:43:59 -0500 Subject: [PATCH 0110/1699] Fix Storage Emulator rules resource evaluation (#4214) * Fix Storage Emulator rules null value error * Add CHANGELOG entry * Add integration test for resource evaluation --- CHANGELOG.md | 1 + .../storage.rules | 3 +- scripts/storage-emulator-integration/tests.ts | 56 ++++++++++++------- src/emulator/storage/apis/firebase.ts | 13 +++-- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ec73b46584..582e34863ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Fixes arg order for `firebase emulators:start --only storage` (#4195). - Fixes bug where environment variable for gen 2 functions weren't updated on deploy (#4209). - Fixes an issue in the storage emulator where a file upload would trigger functions with a metadata update handler (#4213). +- Fixes Storage Emulator rules resource evaluation (#4214). diff --git a/scripts/storage-emulator-integration/storage.rules b/scripts/storage-emulator-integration/storage.rules index 79833da03d6..9c494fbdc1a 100644 --- a/scripts/storage-emulator-integration/storage.rules +++ b/scripts/storage-emulator-integration/storage.rules @@ -2,7 +2,8 @@ rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { - allow read, write: if request.auth != null; + allow read, create, update: if request.auth != null; + allow delete: if request.auth != null && resource.contentType != 'text/plain'; } match /public/{allPaths=**} { diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index 54d7d8a229b..bc37735ba9b 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -1187,30 +1187,46 @@ describe("Storage emulator", () => { }); }); - it("#delete()", async () => { - const downloadUrl = await page.evaluate((filename) => { - return firebase.storage().ref(filename).getDownloadURL(); - }, filename); + describe("deleteFile", () => { + it("should delete file", async () => { + await page.evaluate((filename) => { + return firebase.storage().ref(filename).delete(); + }, filename); - expect(downloadUrl).to.be.not.null; + const error = await page.evaluate((filename) => { + return new Promise((resolve) => { + firebase + .storage() + .ref(filename) + .getDownloadURL() + .catch((err) => { + resolve(err.message); + }); + }); + }, filename); - await page.evaluate((filename) => { - return firebase.storage().ref(filename).delete(); - }, filename); + expect(error).to.contain("does not exist."); + }); - const error = await page.evaluate((filename) => { - return new Promise((resolve) => { - firebase - .storage() - .ref(filename) - .getDownloadURL() - .catch((err) => { - resolve(err.message); - }); - }); - }, filename); + it("should not delete file when security rule on resource object disallows it", async () => { + await page.evaluate((filename) => { + firebase.storage().ref(filename).updateMetadata({ contentType: "text/plain" }); + }, filename); + + const error = await page.evaluate((filename) => { + return new Promise((resolve) => { + firebase + .storage() + .ref(filename) + .delete() + .catch((err) => { + resolve(err.message); + }); + }); + }, filename); - expect(error).to.contain("does not exist."); + expect(error).to.contain("does not have permission to access"); + }); }); }); diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 6ff39b825f3..37831f2c909 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -591,6 +591,13 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { const decodedObjectId = decodeURIComponent(req.params.objectId); const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/"); + const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId); + + const rulesFiles: { before?: RulesResourceMetadata } = {}; + + if (md) { + rulesFiles.before = md.asRulesResource(); + } if ( !(await isPermitted({ @@ -598,9 +605,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { method: RulesetOperationMethod.DELETE, path: operationPath, authorization: req.header("authorization"), - file: { - // TODO load before metadata - }, + file: rulesFiles, })) ) { return res.status(403).json({ @@ -611,8 +616,6 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }); } - const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId); - if (!md) { res.sendStatus(404); return; From 4349494edcdba5fb6ae66df13591bf2e6ea2c360 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Fri, 25 Feb 2022 18:29:24 -0500 Subject: [PATCH 0111/1699] Remove httpsTrigger from field mask to stop overwrite of securityLevel (#4208) * remove httpsTrigger from field mask array to stop overwrite of securityLevel * calling update mask with the correct object & add changelog entry * adding securityLevel to backend & removing delete field update function * adding comment to explain temp fix and linking to issue * default to secure always * formatter * referencing SecurityLevel in gcf * formatter --- CHANGELOG.md | 1 + src/deploy/functions/backend.ts | 5 +++++ src/deploy/functions/prepare.ts | 2 ++ src/gcp/cloudfunctions.ts | 8 ++++++++ 4 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 582e34863ff..2ff315f05d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Fixes bug where environment variable for gen 2 functions weren't updated on deploy (#4209). - Fixes an issue in the storage emulator where a file upload would trigger functions with a metadata update handler (#4213). - Fixes Storage Emulator rules resource evaluation (#4214). +- Fixes bug where securityLevel is overwritten on https function re-deploys (#4208). diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 30f9d2f0858..f9955e516b9 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -255,6 +255,11 @@ export type Endpoint = TargetIds & // on GCFv2 always uri?: string; sourceUploadUrl?: string; + // TODO(colerogers): yank this field and set securityLevel to SECURE_ALWAYS + // in functionFromEndpoint during a breaking change release. + // This is a temporary fix to address https://github.com/firebase/firebase-tools/issues/4171 + // GCFv1 can be http or https and GCFv2 is always https + securityLevel?: gcf.SecurityLevel; }; export interface RequiredAPI { diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 4b59c6edcf9..4bcf36ef784 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -205,6 +205,8 @@ export function inferDetailsFromExisting( wantE.availableMemoryMb = haveE.availableMemoryMb; } + wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS"; + maybeCopyTriggerRegion(wantE, haveE); } } diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 86da8e115a1..137f8d5cf28 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -465,6 +465,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi const [, project, , region, , id] = gcfFunction.name.split("/"); let trigger: backend.Triggered; let uri: string | undefined; + let securityLevel: SecurityLevel | undefined; if (gcfFunction.labels?.["deployment-scheduled"]) { trigger = { scheduleTrigger: {}, @@ -476,6 +477,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi } else if (gcfFunction.httpsTrigger) { trigger = { httpsTrigger: {} }; uri = gcfFunction.httpsTrigger.url; + securityLevel = gcfFunction.httpsTrigger.securityLevel; } else { trigger = { eventTrigger: { @@ -504,6 +506,9 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi if (uri) { endpoint.uri = uri; } + if (securityLevel) { + endpoint.securityLevel = securityLevel; + } proto.copyIfPresent( endpoint, gcfFunction, @@ -584,6 +589,9 @@ export function functionFromEndpoint( if (backend.isCallableTriggered(endpoint)) { gcfFunction.labels = { ...gcfFunction.labels, "deployment-callabled": "true" }; } + if (endpoint.securityLevel) { + gcfFunction.httpsTrigger.securityLevel = endpoint.securityLevel; + } } proto.copyIfPresent( From 2948555525a0707a740c03357bca8c3ddccb9edb Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 28 Feb 2022 10:40:07 -0800 Subject: [PATCH 0112/1699] Fix bug where functions.runtime option in firebase.json is ignored in the Emulator (#4207) Fixes https://github.com/firebase/firebase-tools/issues/4200 --- CHANGELOG.md | 1 + src/emulator/functionsEmulator.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ff315f05d7..d613fd090f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - Fixes an issue in the storage emulator where a file upload would trigger functions with a metadata update handler (#4213). - Fixes Storage Emulator rules resource evaluation (#4214). - Fixes bug where securityLevel is overwritten on https function re-deploys (#4208). +- Fixes bug where functions emulator ignored functions.runtime option in firebase.json (#4207). diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index dde6e4827aa..378198dc9ea 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -6,7 +6,9 @@ import * as clc from "cli-color"; import * as http from "http"; import * as jwt from "jsonwebtoken"; import * as cors from "cors"; +import * as stream from "stream"; import { URL } from "url"; +import { EventEmitter } from "events"; import { Account } from "../auth"; import * as api from "../api"; @@ -41,8 +43,6 @@ import { emulatedFunctionsByRegion, } from "./functionsEmulatorShared"; import { EmulatorRegistry } from "./registry"; -import { EventEmitter } from "events"; -import * as stream from "stream"; import { EmulatorLogger, Verbosity } from "./emulatorLogger"; import { RuntimeWorker, RuntimeWorkerPool } from "./functionsRuntimeWorker"; import { PubsubEmulator } from "./pubsubEmulator"; @@ -58,10 +58,10 @@ import { import { EventUtils } from "./events/types"; import { functionIdsAreValid } from "../deploy/functions/validate"; import { Extension, ExtensionSpec, ExtensionVersion } from "../extensions/extensionsApi"; -import { getRuntimeDelegate } from "../deploy/functions/runtimes"; +import { accessSecretVersion } from "../gcp/secretManager"; +import * as runtimes from "../deploy/functions/runtimes"; import * as backend from "../deploy/functions/backend"; import * as functionsEnv from "../functions/env"; -import { accessSecretVersion } from "../gcp/secretManager"; const EVENT_INVOKE = "functions:invoke"; const LOCAL_SECRETS_FILE = ".secret.local"; @@ -480,11 +480,15 @@ export class FunctionsEmulator implements EmulatorInstance { triggerDefinitions = emulatedFunctionsByRegion(emulatableBackend.predefinedTriggers); } else { const runtimeConfig = this.getRuntimeConfig(emulatableBackend); - const runtimeDelegate = await getRuntimeDelegate({ + const runtimeDelegateContext: runtimes.DelegateContext = { projectId: this.args.projectId, projectDir: this.args.projectDir, sourceDir: emulatableBackend.functionsDir, - }); + }; + if (emulatableBackend.nodeMajorVersion) { + runtimeDelegateContext.runtime = `nodejs${emulatableBackend.nodeMajorVersion}`; + } + const runtimeDelegate = await runtimes.getRuntimeDelegate(runtimeDelegateContext); logger.debug(`Validating ${runtimeDelegate.name} source`); await runtimeDelegate.validate(); logger.debug(`Building ${runtimeDelegate.name} source`); From 9233e80392316a99aad3a2a2b10a98ff8e54263d Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Mon, 28 Feb 2022 17:50:10 -0500 Subject: [PATCH 0113/1699] Fix Storage Emulator resource evaluation integration test (#4230) --- scripts/storage-emulator-integration/tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index bc37735ba9b..2364199e169 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -1210,7 +1210,7 @@ describe("Storage emulator", () => { it("should not delete file when security rule on resource object disallows it", async () => { await page.evaluate((filename) => { - firebase.storage().ref(filename).updateMetadata({ contentType: "text/plain" }); + return firebase.storage().ref(filename).updateMetadata({ contentType: "text/plain" }); }, filename); const error = await page.evaluate((filename) => { From 2a56d9520241ab9897a59aac22c9fd016251a7cd Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 28 Feb 2022 15:38:20 -0800 Subject: [PATCH 0114/1699] npm audit fix 2022-02-28 (#4229) --- npm-shrinkwrap.json | 1175 +++++++++++++++++++++++++++++-------------- 1 file changed, 784 insertions(+), 391 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 4dfe12e2549..c99c88be21c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -658,16 +658,6 @@ "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==", "dev": true }, - "node_modules/@firebase/analytics/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/analytics/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -692,22 +682,77 @@ "xmlhttprequest": "1.8.0" } }, + "node_modules/@firebase/app-compat": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.18.tgz", + "integrity": "sha512-YXmMLQro2g2xlNnzB6zVxYoFx9sJS/JDEQy6vsj3FpMUuARaImipL6W8KuGfH+tJ3M+q38qRaFROk5gK6PoCrQ==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/app": "0.7.17", + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/app": { + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.17.tgz", + "integrity": "sha512-OnZab790eMwRxkUs7o/kgniAzSBxecDTGEk1PVhiG0HQhKrIf+R7lgqOZHDb/2GJsX12jby1p/Z5+WJCBxVbJQ==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/component": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", + "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/util": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", + "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true, + "peer": true + }, "node_modules/@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", "dev": true }, - "node_modules/@firebase/app/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/app/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -750,39 +795,160 @@ } }, "node_modules/@firebase/component": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", - "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/component/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", "dev": true, "dependencies": { - "@firebase/util": "0.3.4", "tslib": "^1.11.1" } }, "node_modules/@firebase/database": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.1.tgz", - "integrity": "sha512-/1HhR4ejpqUaM9Cn3KSeNdQvdlehWIhdfTVWFxS73ZlLYf7ayk9jITwH10H3ZOIm5yNzxF67p/U7Z/0IPhgWaQ==", + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", "dev": true, "dependencies": { "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.21", - "@firebase/database-types": "0.6.1", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.4", + "@firebase/util": "0.3.2", "faye-websocket": "0.11.3", "tslib": "^1.11.1" } }, + "node_modules/@firebase/database-compat": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.5.tgz", + "integrity": "sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==", + "dev": true, + "dependencies": { + "@firebase/component": "0.5.10", + "@firebase/database": "0.12.5", + "@firebase/database-types": "0.9.4", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==", + "dev": true + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/component": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", + "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "dev": true, + "dependencies": { + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/database": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.5.tgz", + "integrity": "sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==", + "dev": true, + "dependencies": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/database-types": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.4.tgz", + "integrity": "sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==", + "dev": true, + "dependencies": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.4.3" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/@firebase/util": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", + "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat/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==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/@firebase/database-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz", - "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", "dev": true, "dependencies": { "@firebase/app-types": "0.6.1" } }, + "node_modules/@firebase/database/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/firestore": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.18.0.tgz", @@ -816,16 +982,6 @@ "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/firestore/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/firestore/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -867,25 +1023,6 @@ "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==", "dev": true }, - "node_modules/@firebase/functions/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "node_modules/@firebase/functions/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/functions/node_modules/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -921,16 +1058,6 @@ "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/installations/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/installations/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -973,16 +1100,6 @@ "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/messaging/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/messaging/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1016,16 +1133,6 @@ "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==", "dev": true }, - "node_modules/@firebase/performance/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/performance/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1070,16 +1177,6 @@ "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==", "dev": true }, - "node_modules/@firebase/remote-config/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/remote-config/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1115,16 +1212,6 @@ "@firebase/util": "0.x" } }, - "node_modules/@firebase/storage/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/storage/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1139,6 +1226,7 @@ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", "dev": true, + "peer": true, "dependencies": { "tslib": "^1.11.1" } @@ -1722,6 +1810,15 @@ "node": ">=8.0.0" } }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1995,6 +2092,16 @@ "@types/serve-static": "*" } }, + "node_modules/@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "node_modules/@types/express-serve-static-core": { "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", @@ -2006,6 +2113,15 @@ "@types/range-parser": "*" } }, + "node_modules/@types/express-unless": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.3.tgz", + "integrity": "sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/fs-extra": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", @@ -2740,7 +2856,6 @@ "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -2761,7 +2876,6 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -3182,15 +3296,24 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/atlassian-openapi": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.13.tgz", - "integrity": "sha512-2/CRqPWZ15BBr9s6/c48QaBKvpxbonTeFGGXKFSmcSVFqH0KfFMWkgMSnCWKyAQ7gZ8Ch9BrqCDAG7ENzFWX2A==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.15.tgz", + "integrity": "sha512-HzgdBHJ/9jZWZfass5DRJNG4vLxoFl6Zcl3B+8Cp2VSpEH7t0laBGnGtcthvj2h73hq8dzjKtVlG30agBZ4OPw==", "dev": true, "dependencies": { - "jsonpointer": "^4.0.1", + "jsonpointer": "^5.0.0", "urijs": "^1.18.10" } }, + "node_modules/atlassian-openapi/node_modules/jsonpointer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", + "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3764,9 +3887,15 @@ } }, "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -5941,36 +6070,46 @@ } }, "node_modules/firebase-admin": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.4.2.tgz", - "integrity": "sha512-mRnBJbW6BAz6DJkZ0GOUTkmnmCrwVzMreMc6O+RXWukFydOzi5Xr6TKSiPKxoOQw41r9IluP2AZ3Qzvlx2SR+g==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", + "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", "dev": true, "dependencies": { - "@firebase/database": "^0.8.1", - "@firebase/database-types": "^0.6.1", - "@types/node": "^10.10.0", + "@firebase/database-compat": "^0.1.1", + "@firebase/database-types": "^0.7.2", + "@types/node": ">=12.12.47", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", "node-forge": "^0.10.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=10.13.0" }, "optionalDependencies": { "@google-cloud/firestore": "^4.5.0", "@google-cloud/storage": "^5.3.0" } }, - "node_modules/firebase-admin/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "node_modules/firebase-admin/node_modules/@firebase/app-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", + "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==", "dev": true }, + "node_modules/firebase-admin/node_modules/@firebase/database-types": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", + "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "dev": true, + "dependencies": { + "@firebase/app-types": "0.6.3" + } + }, "node_modules/firebase-functions": { - "version": "3.15.7", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.15.7.tgz", - "integrity": "sha512-ZD7r8eoWWebgs+mTqfH8NLUT2C0f7/cyAvIA1RSUdBVQZN7MBBt3oSlN/rL3e+m6tdlJz6YbQ3hrOKOGjOVYvQ==", + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.18.1.tgz", + "integrity": "sha512-sPYZc9U/o0MjrpL3yz0pmoviJ1SkDoMV54X1rT/O2g0JTbV9eoQZsZuRoIUeaY3gmWFcMnN5TbJsPQUVh+omtw==", "dev": true, "dependencies": { "@types/cors": "^2.8.5", @@ -5979,11 +6118,14 @@ "express": "^4.17.1", "lodash": "^4.17.14" }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, "engines": { "node": "^8.13.0 || >=10.10.0" }, "peerDependencies": { - "firebase-admin": "^8.0.0 || ^9.0.0" + "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, "node_modules/firebase-functions/node_modules/@types/express": { @@ -5997,40 +6139,6 @@ "@types/serve-static": "*" } }, - "node_modules/firebase/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "node_modules/firebase/node_modules/@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", - "dev": true, - "dependencies": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "faye-websocket": "0.11.3", - "tslib": "^1.11.1" - } - }, - "node_modules/firebase/node_modules/@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", - "dev": true, - "dependencies": { - "@firebase/app-types": "0.6.1" - } - }, "node_modules/firebase/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -6610,9 +6718,9 @@ } }, "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7766,6 +7874,21 @@ "valid-url": "^1" } }, + "node_modules/jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "dev": true, + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "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", @@ -8013,6 +8136,45 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwks-rsa": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", + "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", + "dev": true, + "dependencies": { + "@types/express-jwt": "0.0.42", + "debug": "^4.3.2", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=10 < 13 || >=14" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -8103,6 +8265,12 @@ "node": ">= 0.8.0" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -8157,6 +8325,12 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -8405,6 +8579,32 @@ "node": ">=10" } }, + "node_modules/lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dev": true, + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, "node_modules/lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -8855,32 +9055,32 @@ "dev": true }, "node_modules/mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", + "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.2.0", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -8904,9 +9104,9 @@ "dev": true }, "node_modules/mocha/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -9056,9 +9256,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -10467,6 +10667,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -13002,9 +13208,9 @@ } }, "node_modules/urijs": { - "version": "1.19.7", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", - "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==", + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.8.tgz", + "integrity": "sha512-iIXHrjomQ0ZCuDRy44wRbyTZVnfVNLVo3Ksz1yxNyE5wV1IDZW2S5Jszy45DTlw/UdsnRT7DyDhIz7Gy+vJumw==", "dev": true }, "node_modules/url-join": { @@ -13087,9 +13293,13 @@ } }, "node_modules/vm2": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz", - "integrity": "sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.9.tgz", + "integrity": "sha512-xwTm7NLh/uOjARRBs8/95H0e8fT3Ukw5D/JJWhxMbhKzNh1Nu981jQKvkep9iKYNxzlVrdzD0mlBGkDKZWprlw==", + "dependencies": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + }, "bin": { "vm2": "bin/vm2" }, @@ -13239,9 +13449,9 @@ } }, "node_modules/workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "node_modules/wrap-ansi": { @@ -13944,16 +14154,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -13986,24 +14186,81 @@ "xmlhttprequest": "1.8.0" }, "dependencies": { + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + } + } + } + }, + "@firebase/app-compat": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.18.tgz", + "integrity": "sha512-YXmMLQro2g2xlNnzB6zVxYoFx9sJS/JDEQy6vsj3FpMUuARaImipL6W8KuGfH+tJ3M+q38qRaFROk5gK6PoCrQ==", + "dev": true, + "peer": true, + "requires": { + "@firebase/app": "0.7.17", + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/app": { + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.17.tgz", + "integrity": "sha512-OnZab790eMwRxkUs7o/kgniAzSBxecDTGEk1PVhiG0HQhKrIf+R7lgqOZHDb/2GJsX12jby1p/Z5+WJCBxVbJQ==", + "dev": true, + "peer": true, + "requires": { + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", + "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "dev": true, + "peer": true, + "requires": { + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", "dev": true, + "peer": true, "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "tslib": "^2.1.0" } }, "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", + "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", "dev": true, + "peer": true, "requires": { - "tslib": "^1.11.1" + "tslib": "^2.1.0" } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true, + "peer": true } } }, @@ -14037,34 +14294,152 @@ "requires": {} }, "@firebase/component": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", - "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", "dev": true, "requires": { - "@firebase/util": "0.3.4", + "@firebase/util": "0.3.2", "tslib": "^1.11.1" + }, + "dependencies": { + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + } + } } }, "@firebase/database": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.1.tgz", - "integrity": "sha512-/1HhR4ejpqUaM9Cn3KSeNdQvdlehWIhdfTVWFxS73ZlLYf7ayk9jITwH10H3ZOIm5yNzxF67p/U7Z/0IPhgWaQ==", + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", "dev": true, "requires": { "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.21", - "@firebase/database-types": "0.6.1", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.4", + "@firebase/util": "0.3.2", "faye-websocket": "0.11.3", "tslib": "^1.11.1" + }, + "dependencies": { + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + } + } + } + }, + "@firebase/database-compat": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.5.tgz", + "integrity": "sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==", + "dev": true, + "requires": { + "@firebase/component": "0.5.10", + "@firebase/database": "0.12.5", + "@firebase/database-types": "0.9.4", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==", + "dev": true + }, + "@firebase/auth-interop-types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "dev": true, + "requires": {} + }, + "@firebase/component": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", + "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", + "dev": true, + "requires": { + "@firebase/util": "1.4.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.5.tgz", + "integrity": "sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==", + "dev": true, + "requires": { + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.10", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.4.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "@firebase/database-types": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.4.tgz", + "integrity": "sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==", + "dev": true, + "requires": { + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.4.3" + } + }, + "@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", + "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } } }, "@firebase/database-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz", - "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", "dev": true, "requires": { "@firebase/app-types": "0.6.1" @@ -14087,16 +14462,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14134,25 +14499,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -14180,16 +14526,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14228,16 +14564,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14270,16 +14596,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14322,16 +14638,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14361,16 +14667,6 @@ "tslib": "^1.11.1" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -14394,6 +14690,7 @@ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", "dev": true, + "peer": true, "requires": { "tslib": "^1.11.1" } @@ -14849,6 +15146,12 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.24.0.tgz", "integrity": "sha512-a/szuMQV0Quy0/M7kKdglcbRSoorleyyOwbTNNJ32O+RBN766wbQlMTvdimImTmwYWGr+NJOni1EcC242WlRcA==" }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "dev": true + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -15113,6 +15416,16 @@ "@types/serve-static": "*" } }, + "@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "@types/express-serve-static-core": { "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", @@ -15124,6 +15437,15 @@ "@types/range-parser": "*" } }, + "@types/express-unless": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.3.tgz", + "integrity": "sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/fs-extra": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", @@ -15726,8 +16048,7 @@ "acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" }, "acorn-jsx": { "version": "5.3.2", @@ -15739,8 +16060,7 @@ "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" }, "agent-base": { "version": "6.0.2", @@ -16080,13 +16400,21 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atlassian-openapi": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.13.tgz", - "integrity": "sha512-2/CRqPWZ15BBr9s6/c48QaBKvpxbonTeFGGXKFSmcSVFqH0KfFMWkgMSnCWKyAQ7gZ8Ch9BrqCDAG7ENzFWX2A==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/atlassian-openapi/-/atlassian-openapi-1.0.15.tgz", + "integrity": "sha512-HzgdBHJ/9jZWZfass5DRJNG4vLxoFl6Zcl3B+8Cp2VSpEH7t0laBGnGtcthvj2h73hq8dzjKtVlG30agBZ4OPw==", "dev": true, "requires": { - "jsonpointer": "^4.0.1", + "jsonpointer": "^5.0.0", "urijs": "^1.18.10" + }, + "dependencies": { + "jsonpointer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", + "integrity": "sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==", + "dev": true + } } }, "aws-sign2": { @@ -16522,9 +16850,9 @@ "dev": true }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -18213,40 +18541,6 @@ "@firebase/util": "0.3.2" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", - "dev": true, - "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "faye-websocket": "0.11.3", - "tslib": "^1.11.1" - } - }, - "@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", - "dev": true, - "requires": { - "@firebase/app-types": "0.6.1" - } - }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -18259,33 +18553,43 @@ } }, "firebase-admin": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.4.2.tgz", - "integrity": "sha512-mRnBJbW6BAz6DJkZ0GOUTkmnmCrwVzMreMc6O+RXWukFydOzi5Xr6TKSiPKxoOQw41r9IluP2AZ3Qzvlx2SR+g==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", + "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", "dev": true, "requires": { - "@firebase/database": "^0.8.1", - "@firebase/database-types": "^0.6.1", + "@firebase/database-compat": "^0.1.1", + "@firebase/database-types": "^0.7.2", "@google-cloud/firestore": "^4.5.0", "@google-cloud/storage": "^5.3.0", - "@types/node": "^10.10.0", + "@types/node": ">=12.12.47", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", "node-forge": "^0.10.0" }, "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "@firebase/app-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", + "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==", "dev": true + }, + "@firebase/database-types": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", + "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "dev": true, + "requires": { + "@firebase/app-types": "0.6.3" + } } } }, "firebase-functions": { - "version": "3.15.7", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.15.7.tgz", - "integrity": "sha512-ZD7r8eoWWebgs+mTqfH8NLUT2C0f7/cyAvIA1RSUdBVQZN7MBBt3oSlN/rL3e+m6tdlJz6YbQ3hrOKOGjOVYvQ==", + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.18.1.tgz", + "integrity": "sha512-sPYZc9U/o0MjrpL3yz0pmoviJ1SkDoMV54X1rT/O2g0JTbV9eoQZsZuRoIUeaY3gmWFcMnN5TbJsPQUVh+omtw==", "dev": true, "requires": { "@types/cors": "^2.8.5", @@ -18772,9 +19076,9 @@ } }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -19679,6 +19983,15 @@ "valid-url": "^1" } }, + "jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "dev": true, + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -19886,6 +20199,36 @@ "safe-buffer": "^5.0.1" } }, + "jwks-rsa": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", + "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", + "dev": true, + "requires": { + "@types/express-jwt": "0.0.42", + "debug": "^4.3.2", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -19963,6 +20306,12 @@ "type-check": "~0.3.2" } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -20011,6 +20360,12 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -20227,6 +20582,34 @@ "yallist": "^4.0.0" } }, + "lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dev": true, + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dev": true, + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -20567,32 +20950,32 @@ "dev": true }, "mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", + "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.2.0", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -20605,9 +20988,9 @@ "dev": true }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -20716,9 +21099,9 @@ "optional": true }, "nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, "nash": { @@ -21811,6 +22194,12 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -23786,9 +24175,9 @@ } }, "urijs": { - "version": "1.19.7", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", - "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==", + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.8.tgz", + "integrity": "sha512-iIXHrjomQ0ZCuDRy44wRbyTZVnfVNLVo3Ksz1yxNyE5wV1IDZW2S5Jszy45DTlw/UdsnRT7DyDhIz7Gy+vJumw==", "dev": true }, "url-join": { @@ -23856,9 +24245,13 @@ } }, "vm2": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.5.tgz", - "integrity": "sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng==" + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.9.tgz", + "integrity": "sha512-xwTm7NLh/uOjARRBs8/95H0e8fT3Ukw5D/JJWhxMbhKzNh1Nu981jQKvkep9iKYNxzlVrdzD0mlBGkDKZWprlw==", + "requires": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + } }, "wcwidth": { "version": "1.0.1", @@ -23983,9 +24376,9 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "wrap-ansi": { From 82c8da5f02af5ab5ddef10a66ca50dfdf3dddf7b Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Tue, 1 Mar 2022 14:36:33 -0500 Subject: [PATCH 0115/1699] Fix Storage Emulator crash on iOS auth error for resumable uploads (#4210) * Fix Storage Emulator crash on iOS 403 * Add CHANGELOG entry * Refactor finalizeUpload to call Cloud Functions after auth check * Add createMetadata method and refactor finalizeUpload to take upload, not id --- CHANGELOG.md | 2 + scripts/storage-emulator-integration/tests.ts | 138 ++++++++++++++++++ src/emulator/storage/apis/firebase.ts | 27 ++-- src/emulator/storage/apis/gcloud.ts | 12 +- src/emulator/storage/files.ts | 77 +++++----- src/test/emulators/storage/files.spec.ts | 27 ++++ 6 files changed, 228 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d613fd090f8..c990921ec54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ - Updates reserved environment variables for CF3 to include 'EVENTARC_CLOUD_EVENT_SOURCE' (#4196). - Fixes arg order for `firebase emulators:start --only storage` (#4195). +- Fixes iOS auth for resumable uploads in Storage Emulator (#4184). +- Fixes Storage Emulator crash on iOS auth error for resumable uploads (#4210). - Fixes bug where environment variable for gen 2 functions weren't updated on deploy (#4209). - Fixes an issue in the storage emulator where a file upload would trigger functions with a metadata update handler (#4213). - Fixes Storage Emulator rules resource evaluation (#4214). diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index 2364199e169..b367ae00a58 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -1356,6 +1356,144 @@ describe("Storage emulator", () => { "X-Goog-Upload-Command": "upload, finalize", }) .expect(200); + + await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${storageBucket}/o/test_upload.jpg`) + .set({ Authorization: "Bearer owner" }) + .expect(200); + }); + + it("should return 403 when resumable upload is unauthenticated", async () => { + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg` + ) + .set({ + // Authorization missing + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload, finalize", + }) + .expect(403); + }); + + describe("cancels upload", () => { + it("should cancel upload successfully", async () => { + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg` + ) + .set({ + Authorization: "Bearer owner", + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "cancel", + }) + .expect(200); + + await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${storageBucket}/o/test_upload.jpg`) + .set({ Authorization: "Bearer owner" }) + .expect(404); + }); + + it("should return 200 when cancelling already cancelled upload", async () => { + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg` + ) + .set({ + Authorization: "Bearer owner", + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "cancel", + }) + .expect(200); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "cancel", + }) + .expect(200); + }); + + it("should return 400 when cancelling finalized resumable upload", async () => { + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg` + ) + .set({ + Authorization: "Bearer owner", + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload, finalize", + }) + .expect(200); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "cancel", + }) + .expect(400); + }); + + it("should return 404 when cancelling non-existent upload", async () => { + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg` + ) + .set({ + Authorization: "Bearer owner", + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search.replace(/(upload_id=).*?(&)/, "$1foo$2")) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "cancel", + }) + .expect(404); + }); }); }); diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 37831f2c909..1c74e531f7d 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -493,12 +493,13 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } if (uploadCommand == "cancel") { - const upload = storageLayer.cancelUpload(uploadId); - if (!upload) { - res.sendStatus(400); - return; + const upload = storageLayer.queryUpload(uploadId); + if (upload) { + const cancelled = storageLayer.cancelUpload(upload); + res.sendStatus(cancelled ? 200 : 400); + } else { + res.sendStatus(404); } - res.sendStatus(200); return; } @@ -530,14 +531,11 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } if (uploadCommand.includes("finalize")) { - const finalizedUpload = storageLayer.finalizeUpload(uploadId); - if (!finalizedUpload) { + upload = storageLayer.queryUpload(uploadId); + if (!upload) { res.sendStatus(400); return; } - upload = finalizedUpload.upload; - - res.header("x-goog-upload-status", "final"); // For resumable uploads, we check auth on finalization in case of byte-dependant rules if ( @@ -548,7 +546,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { path: operationPath, authorization: upload.authorization, file: { - after: storageLayer.getMetadata(req.params.bucketId, name)?.asRulesResource(), + after: storageLayer.createMetadata(upload).asRulesResource(), }, })) ) { @@ -561,12 +559,15 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }); } - const md = finalizedUpload.file.metadata; + res.header("x-goog-upload-status", "final"); + const uploadedFile = storageLayer.finalizeUpload(upload); + + const md = uploadedFile.metadata; if (md.downloadTokens.length == 0) { md.addDownloadToken(); } - res.json(new OutgoingFirebaseMetadata(finalizedUpload.file.metadata)); + res.json(new OutgoingFirebaseMetadata(uploadedFile.metadata)); } else if (!upload) { res.sendStatus(400); return; diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index fe74e16645f..68883f35546 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -123,20 +123,14 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { }); }); - let upload = storageLayer.uploadBytes(uploadId, req.body); - + const upload = storageLayer.uploadBytes(uploadId, req.body); if (!upload) { res.sendStatus(400); return; } - const finalizedUpload = storageLayer.finalizeUpload(uploadId); - if (!finalizedUpload) { - res.sendStatus(400); - return; - } - upload = finalizedUpload.upload; - res.status(200).json(new CloudStorageObjectMetadata(finalizedUpload.file.metadata)).send(); + const uploadedFile = storageLayer.finalizeUpload(upload); + res.status(200).json(new CloudStorageObjectMetadata(uploadedFile.metadata)).send(); }); gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", (req, res) => { diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index b824c9ea7a7..ae7c7fa37de 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -120,11 +120,6 @@ export enum UploadStatus { FINISHED, } -export type FinalizedUpload = { - upload: ResumableUpload; - file: StoredFile; -}; - export class StorageLayer { private _files!: Map; private _uploads!: Map; @@ -173,6 +168,27 @@ export class StorageLayer { return; } + /** + * Generates metadata for an uploaded file. Generally, this should only be used for finalized + * uploads, unless needed for security rule checks. + * @param upload The upload corresponding to the file for which to generate metadata. + * @returns Metadata for uploaded file. + */ + public createMetadata(upload: ResumableUpload): StoredFileMetadata { + const bytes = this._persistence.readBytes(upload.fileLocation, upload.currentBytesUploaded); + return new StoredFileMetadata( + { + name: upload.objectId, + bucket: upload.bucketId, + contentType: "", + contentEncoding: upload.metadata.contentEncoding, + customMetadata: upload.metadata.metadata, + }, + this._cloudFunctions, + bytes + ); + } + public getBytes( bucket: string, object: string, @@ -216,13 +232,18 @@ export class StorageLayer { return this._uploads.get(uploadId); } - public cancelUpload(uploadId: string): ResumableUpload | undefined { - const upload = this._uploads.get(uploadId); - if (!upload) { - return undefined; + /** + * Deletes partially uploaded file from persistence layer and updates its status. Cancelling + * an upload is idempotent. + * @param upload The upload to be cancelled. + * @returns Whether the upload was cancelled (i.e. its initial status was ACTIVE or CANCELLED). + */ + public cancelUpload(upload: ResumableUpload): boolean { + if (upload.status === UploadStatus.ACTIVE) { + this._persistence.deleteFile(upload.fileLocation); + upload.status = UploadStatus.CANCELLED; } - upload.status = UploadStatus.CANCELLED; - this._persistence.deleteFile(upload.fileLocation); + return upload.status === UploadStatus.CANCELLED; } public uploadBytes(uploadId: string, bytes: Buffer): ResumableUpload | undefined { @@ -266,36 +287,26 @@ export class StorageLayer { return this._persistence.deleteAll(); } - public finalizeUpload(uploadId: string): FinalizedUpload | undefined { - const upload = this._uploads.get(uploadId); - - if (!upload) { - return undefined; - } - + /** + * Stores the uploaded file with generated metadata and triggers Object Finalize Cloud Functions. + * @param upload The upload to finalize. + * @returns The stored file. + */ + public finalizeUpload(upload: ResumableUpload): StoredFile { upload.status = UploadStatus.FINISHED; + + const metadata = this.createMetadata(upload); const filePath = this.path(upload.bucketId, upload.objectId); + const file = new StoredFile(metadata, filePath); - const bytes = this._persistence.readBytes(upload.fileLocation, upload.currentBytesUploaded); - const finalMetadata = new StoredFileMetadata( - { - name: upload.objectId, - bucket: upload.bucketId, - contentType: "", - contentEncoding: upload.metadata.contentEncoding, - customMetadata: upload.metadata.metadata, - }, - this._cloudFunctions, - bytes - ); - const file = new StoredFile(finalMetadata, filePath); this._files.set(filePath, file); - this._persistence.deleteFile(filePath, true); + this._persistence.deleteFile(filePath, /* failSilently = */ true); this._persistence.renameFile(upload.fileLocation, filePath); this._cloudFunctions.dispatch("finalize", new CloudStorageObjectMetadata(file.metadata)); - return { upload: upload, file: file }; + + return file; } public oneShotUpload( diff --git a/src/test/emulators/storage/files.spec.ts b/src/test/emulators/storage/files.spec.ts index 511fea8e5f0..48834ad5029 100644 --- a/src/test/emulators/storage/files.spec.ts +++ b/src/test/emulators/storage/files.spec.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { StoredFileMetadata } from "../../../emulator/storage/metadata"; import { StorageCloudFunctions } from "../../../emulator/storage/cloudFunctions"; +import { StorageLayer } from "../../../emulator/storage/files"; describe("files", () => { it("can serialize and deserialize metadata", () => { @@ -23,4 +24,30 @@ describe("files", () => { const deserialized = StoredFileMetadata.fromJSON(json, cf); expect(deserialized).to.deep.equal(metadata); }); + + it("should store file in memory when upload is finalized", () => { + const storageLayer = new StorageLayer("project"); + const bytesToWrite = "Hello, World!"; + + const upload = storageLayer.startUpload("bucket", "object", "mime/type", { + contentType: "mime/type", + }); + storageLayer.uploadBytes(upload.uploadId, Buffer.from(bytesToWrite)); + storageLayer.finalizeUpload(upload); + + expect(storageLayer.getBytes("bucket", "object")?.includes(bytesToWrite)); + expect(storageLayer.getMetadata("bucket", "object")?.size).equals(bytesToWrite.length); + }); + + it("should delete file from persistence layer when upload is cancelled", () => { + const storageLayer = new StorageLayer("project"); + + const upload = storageLayer.startUpload("bucket", "object", "mime/type", { + contentType: "mime/type", + }); + storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0)); + storageLayer.cancelUpload(upload); + + expect(storageLayer.getMetadata("bucket", "object")).to.equal(undefined); + }); }); From 5a8bd4e4337536821a258fa7d14cdc11d431c478 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 1 Mar 2022 14:14:48 -0800 Subject: [PATCH 0116/1699] Add warning when emulating extensions that use unemulated APIs (#4226) * starting to check APIs * merging * more progress * more progress * Adding check for unemulated APIs * reverting shrinkwrap changes * reverting shrinkwrap changes * clean up unused import * add enable API console link * more pr fixes * adding renamed files * one last round of pr clean up * clean up unused marked --- src/emulator/extensions/validation.ts | 44 +++++++++++++ src/emulator/extensionsEmulator.ts | 45 ++++++++++++- src/ensureApiEnabled.ts | 12 ++++ .../emulators/extensions/validation.spec.ts | 66 +++++++++++++++++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/emulator/extensions/validation.ts create mode 100644 src/test/emulators/extensions/validation.spec.ts diff --git a/src/emulator/extensions/validation.ts b/src/emulator/extensions/validation.ts new file mode 100644 index 00000000000..28d7f637d58 --- /dev/null +++ b/src/emulator/extensions/validation.ts @@ -0,0 +1,44 @@ +import * as planner from "../../deploy/extensions/planner"; +import { check } from "../../ensureApiEnabled"; + +const EMULATED_APIS = [ + "storage-component.googleapis.com", + "firestore.googleapis.com", + "pubsub.googleapis.com", + "identitytoolkit.googleapis.com", + // TODO: Is there a RTDB API we need to add here? I couldn't find one. +]; + +type APIInfo = { + apiName: string; + instanceIds: string[]; + enabled: boolean; +}; +/** + * getUnemulatedAPIs checks a list of InstanceSpecs for APIs that are not emulated. + * It returns a map of API name to list of instanceIds that use that API. + */ +export async function getUnemulatedAPIs( + projectId: string, + instances: planner.InstanceSpec[] +): Promise { + const unemulatedAPIs: Record = {}; + for (const i of instances) { + const extensionVersion = await planner.getExtensionVersion(i); + for (const api of extensionVersion.spec.apis ?? []) { + if (!EMULATED_APIS.includes(api.apiName)) { + if (unemulatedAPIs[api.apiName]) { + unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId); + } else { + const enabled = await check(projectId, api.apiName, "extensions", true); + unemulatedAPIs[api.apiName] = { + apiName: api.apiName, + instanceIds: [i.instanceId], + enabled, + }; + } + } + } + } + return Object.values(unemulatedAPIs); +} diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 25f52235347..83d1dc5b7f6 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -1,6 +1,8 @@ import * as fs from "fs-extra"; import * as os from "os"; import * as path from "path"; +import * as clc from "cli-color"; +import Table = require("cli-table"); import { spawnSync } from "child_process"; import * as planner from "../deploy/extensions/planner"; @@ -10,6 +12,10 @@ import { downloadExtensionVersion } from "./download"; import { EmulatableBackend } from "./functionsEmulator"; import { getExtensionFunctionInfo } from "../extensions/emulator/optionsHelper"; import { EmulatorLogger } from "./emulatorLogger"; +import { Emulators } from "./types"; +import { getUnemulatedAPIs } from "./extensions/validation"; +import { enableApiURI } from "../ensureApiEnabled"; +import { shortenUrl } from "../shortenUrl"; export interface ExtensionEmulatorArgs { projectId: string; @@ -24,6 +30,7 @@ export interface ExtensionEmulatorArgs { export class ExtensionsEmulator { private want: planner.InstanceSpec[] = []; private args: ExtensionEmulatorArgs; + private logger = EmulatorLogger.forEmulator(Emulators.EXTENSIONS); constructor(args: ExtensionEmulatorArgs) { this.args = args; @@ -120,10 +127,11 @@ export class ExtensionsEmulator { * getEmulatableBackends reads firebase.json & .env files for a list of extension instances to emulate, * downloads & builds the necessary source code (if it hasn't previously been cached), * then builds returns a list of emulatableBackends - * @returns A list of emulatableBackends, one for each extension instance to be emulated + * @return A list of emulatableBackends, one for each extension instance to be emulated */ public async getExtensionBackends(): Promise { await this.readManifest(); + await this.checkAndWarnAPIs(this.want); return Promise.all( this.want.map((i: planner.InstanceSpec) => { return this.toEmulatableBackend(i); @@ -169,4 +177,39 @@ export class ExtensionsEmulator { STORAGE_BUCKET: `${projectId}.appspot.com`, }; } + + private async checkAndWarnAPIs(instances: planner.InstanceSpec[]): Promise { + const apisToWarn = await getUnemulatedAPIs(this.args.projectId, instances); + if (apisToWarn.length) { + const table = new Table({ + head: [ + "API Name", + "Instances using this API", + `Enabled on ${this.args.projectId}`, + `Enable this API`, + ], + style: { head: ["yellow"] }, + }); + for (const apiToWarn of apisToWarn) { + // We use a shortened link here instead of a alias because cli-table behaves poorly with aliased links + const enablementUri = await shortenUrl( + enableApiURI(this.args.projectId, apiToWarn.apiName) + ); + table.push([ + apiToWarn.apiName, + apiToWarn.instanceIds, + apiToWarn.enabled ? "Yes" : "No", + apiToWarn.enabled ? "" : clc.bold.underline(enablementUri), + ]); + } + + this.logger.logLabeled( + "WARN", + "Extensions", + `The following Extensions make calls to Google Cloud APIs that do not have Emulators. ` + + `These calls will go to production Google Cloud APIs which may have real effects on ${this.args.projectId}.\n` + + table.toString() + ); + } + } } diff --git a/src/ensureApiEnabled.ts b/src/ensureApiEnabled.ts index 5bcaebb4422..c086a8f558e 100644 --- a/src/ensureApiEnabled.ts +++ b/src/ensureApiEnabled.ts @@ -132,3 +132,15 @@ export async function ensure( } return enableApiWithRetries(projectId, apiName, prefix, silent); } + +/** + * Returns a link to enable an API on a project in Cloud console. This can be used instead of ensure + * in contexts where automatically enabling APIs is not desirable (ie emulator commands). + * + * @param projectId The project to generate an API enablement link for + * @param apiName The name of the API e.g. `someapi.googleapis.com`. + * @return A link to Cloud console to enable the API + */ +export function enableApiURI(projectId: string, apiName: string): string { + return `https://console.cloud.google.com/apis/library/${apiName}?project=${projectId}`; +} diff --git a/src/test/emulators/extensions/validation.spec.ts b/src/test/emulators/extensions/validation.spec.ts new file mode 100644 index 00000000000..457d266626a --- /dev/null +++ b/src/test/emulators/extensions/validation.spec.ts @@ -0,0 +1,66 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; + +import * as utils from "../../../emulator/extensions/validation"; +import * as ensureApiEnabled from "../../../ensureApiEnabled"; +import { InstanceSpec } from "../../../deploy/extensions/planner"; + +function getTestInstanceSpecWithAPI(instanceId: string, apiName: string): InstanceSpec { + return { + instanceId, + params: {}, + extensionVersion: { + name: "publishers/test/extensions/test/versions/0.1.0", + ref: "test/test@0.1.0", + state: "PUBLISHED", + sourceDownloadUri: "test.com", + hash: "abc123", + spec: { + name: "test", + version: "0.1.0", + sourceUrl: "test.com", + resources: [], + params: [], + apis: [{ apiName, reason: "because" }], + }, + }, + }; +} + +describe("ExtensionsEmulator validation utils", () => { + describe(`${utils.getUnemulatedAPIs.name}`, () => { + const testProjectId = "test-project"; + const testAPI = "test.googleapis.com"; + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + const checkStub = sandbox.stub(ensureApiEnabled, "check"); + checkStub.withArgs(testProjectId, testAPI, "extensions", true).resolves(true); + checkStub.throws("Unexpected API checked in test"); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should check only unemulated APIs", async () => { + const instanceIdWithUnemulatedAPI = "unemulated"; + const instanceId2WithUnemulatedAPI = "unemulated2"; + const instanceIdWithEmulatedAPI = "emulated"; + + const result = await utils.getUnemulatedAPIs(testProjectId, [ + getTestInstanceSpecWithAPI(instanceIdWithEmulatedAPI, "firestore.googleapis.com"), + getTestInstanceSpecWithAPI(instanceIdWithUnemulatedAPI, testAPI), + getTestInstanceSpecWithAPI(instanceId2WithUnemulatedAPI, testAPI), + ]); + + expect(result).to.deep.equal([ + { + apiName: testAPI, + instanceIds: [instanceIdWithUnemulatedAPI, instanceId2WithUnemulatedAPI], + enabled: true, + }, + ]); + }); + }); +}); From d7d8dc7c4af9a32c4e57abd7b1d1ab9744f90797 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 2 Mar 2022 11:00:54 -0800 Subject: [PATCH 0117/1699] Update versions of dependencies functions init templates to latest (#4177) Following dependencies have had major version bump: * typescript * firebase-admin * eslint I manually confirmed that firebase-admin v10 poses no issue for functions SDK (as expcted). Fixes https://github.com/firebase/firebase-tools/issues/4155 --- CHANGELOG.md | 1 + .../init/functions/javascript/package.lint.json | 6 +++--- .../init/functions/javascript/package.nolint.json | 4 ++-- .../init/functions/typescript/package.lint.json | 14 +++++++------- .../init/functions/typescript/package.nolint.json | 6 +++--- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c990921ec54..3cdb7a982b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,4 @@ - Fixes Storage Emulator rules resource evaluation (#4214). - Fixes bug where securityLevel is overwritten on https function re-deploys (#4208). - Fixes bug where functions emulator ignored functions.runtime option in firebase.json (#4207). +- Updates functions init template to use latest versions of dependencies of functions init (#4177). diff --git a/templates/init/functions/javascript/package.lint.json b/templates/init/functions/javascript/package.lint.json index f952eb8ad11..70089faf002 100644 --- a/templates/init/functions/javascript/package.lint.json +++ b/templates/init/functions/javascript/package.lint.json @@ -14,11 +14,11 @@ }, "main": "index.js", "dependencies": { - "firebase-admin": "^9.8.0", - "firebase-functions": "^3.14.1" + "firebase-admin": "^10.0.2", + "firebase-functions": "^3.18.0" }, "devDependencies": { - "eslint": "^7.6.0", + "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", "firebase-functions-test": "^0.2.0" }, diff --git a/templates/init/functions/javascript/package.nolint.json b/templates/init/functions/javascript/package.nolint.json index 1dae83651f9..148afc6a8d6 100644 --- a/templates/init/functions/javascript/package.nolint.json +++ b/templates/init/functions/javascript/package.nolint.json @@ -13,8 +13,8 @@ }, "main": "index.js", "dependencies": { - "firebase-admin": "^9.8.0", - "firebase-functions": "^3.14.1" + "firebase-admin": "^10.0.2", + "firebase-functions": "^3.18.0" }, "devDependencies": { "firebase-functions-test": "^0.2.0" diff --git a/templates/init/functions/typescript/package.lint.json b/templates/init/functions/typescript/package.lint.json index 79d96a6bd58..9b3e921f0c1 100644 --- a/templates/init/functions/typescript/package.lint.json +++ b/templates/init/functions/typescript/package.lint.json @@ -14,17 +14,17 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^9.8.0", - "firebase-functions": "^3.14.1" + "firebase-admin": "^10.0.2", + "firebase-functions": "^3.18.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^3.9.1", - "@typescript-eslint/parser": "^3.8.0", - "eslint": "^7.6.0", + "@typescript-eslint/eslint-plugin": "^5.12.0", + "@typescript-eslint/parser": "^5.12.0", + "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-import": "^2.22.0", + "eslint-plugin-import": "^2.25.4", "firebase-functions-test": "^0.2.0", - "typescript": "^3.8.0" + "typescript": "^4.5.4" }, "private": true } diff --git a/templates/init/functions/typescript/package.nolint.json b/templates/init/functions/typescript/package.nolint.json index 35137e1042c..8ae2d79ebef 100644 --- a/templates/init/functions/typescript/package.nolint.json +++ b/templates/init/functions/typescript/package.nolint.json @@ -13,11 +13,11 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^9.8.0", - "firebase-functions": "^3.14.1" + "firebase-admin": "^10.0.2", + "firebase-functions": "^3.18.0" }, "devDependencies": { - "typescript": "^3.8.0", + "typescript": "^4.5.4", "firebase-functions-test": "^0.2.0" }, "private": true From b1099a2c3b0a104124eda5d8bf4b48e3aac38c37 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 2 Mar 2022 11:12:09 -0800 Subject: [PATCH 0118/1699] Dynamically invoke correct trigger when running functions emulator with --inspect-functions flag (#4232) In https://github.com/firebase/firebase-tools/pull/4149, we made large refactor of the Functions Runtime. Namely: 1) We no longer relied on Functions Runtime to parse triggers. Instead, we use [`RuntimeDelegate`](https://github.com/firebase/firebase-tools/blob/2a56d9520241ab9897a59aac22c9fd016251a7cd/src/deploy/functions/runtimes/index.ts#L114), the same procedure used to parse trigger in `firebase deploy` command. 2) Each process running the Functions Runtime was bound to a specific trigger. Unfortunately, this change broke support for `--inspect-function` where users can attach Node Debugger to step through their function when triggered. The debugging experience supported w/ `--inspect-function` relies on the fact that a single process executes all function triggers. This is drastically different to how functions run in Production environment, but it is a very useful feature that makes it easy to step through all functions in a single debug session. I wasn't aware of the debugging capabilities when making the refactor. Unfortunately, the debug feature cuts across a strong assumption I've made for the future of Functions Emulator. This is a rather hasty rollback - I'm going to have to think more deeply about how we want the evolve this feature. Fixes https://github.com/firebase/firebase-tools/issues/4189, https://github.com/firebase/firebase-tools/issues/4166 --- CHANGELOG.md | 1 + .../emulator-tests/functionsEmulator.spec.ts | 6 +- .../functionsEmulatorRuntime.spec.ts | 64 +++++----- src/emulator/functionsEmulator.ts | 97 +++++++++------ src/emulator/functionsEmulatorRuntime.ts | 114 ++++++++++-------- src/emulator/functionsEmulatorShared.ts | 10 ++ src/emulator/functionsEmulatorShell.ts | 2 +- 7 files changed, 172 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cdb7a982b5..c0b1f1ed30f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,4 +7,5 @@ - Fixes Storage Emulator rules resource evaluation (#4214). - Fixes bug where securityLevel is overwritten on https function re-deploys (#4208). - Fixes bug where functions emulator ignored functions.runtime option in firebase.json (#4207). +- Fixes bug where functions emulator triggered wrong functions when started with --inspect-functions flag (#4232). - Updates functions init template to use latest versions of dependencies of functions init (#4177). diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index 90e125f368a..a1457885b78 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -123,18 +123,18 @@ functionsEmulator.setTriggersForTesting( ); // TODO(samstern): This is an ugly way to just override the InvokeRuntimeOpts on each call -const startFunctionRuntime = functionsEmulator.startFunctionRuntime.bind(functionsEmulator); +const invokeTrigger = functionsEmulator.invokeTrigger.bind(functionsEmulator); function useFunctions(triggers: () => {}): void { const serializedTriggers = triggers.toString(); // eslint-disable-next-line @typescript-eslint/unbound-method - functionsEmulator.startFunctionRuntime = ( + functionsEmulator.invokeTrigger = ( backend: EmulatableBackend, trigger: EmulatedTriggerDefinition, proto?: any, runtimeOpts?: InvokeRuntimeOpts ): Promise => { - return startFunctionRuntime(testBackend, trigger, proto, { + return invokeTrigger(testBackend, trigger, proto, { nodeBinary: process.execPath, serializedTriggers, }); diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index ddeaf6ba1e7..ace509fc59c 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -48,7 +48,7 @@ async function countLogEntries(worker: RuntimeWorker): Promise<{ [key: string]: return counts; } -async function startRuntimeWithFunctions( +async function invokeFunction( frb: FunctionsRuntimeBundle, triggers: () => {}, signatureType: SignatureType, @@ -67,7 +67,7 @@ async function startRuntimeWithFunctions( entryPoint: "function_id", platform: "gcfv1" as const, }; - return functionsEmulator.startFunctionRuntime( + return functionsEmulator.invokeTrigger( testBackend, { ...dummyTriggerDef, @@ -129,7 +129,7 @@ describe("FunctionsEmulator-Runtime", () => { describe("Stubs, Mocks, and Helpers (aka Magic, Glee, and Awesomeness)", () => { describe("_InitializeNetworkFiltering(...)", () => { it("should log outgoing unknown HTTP requests via 'http'", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -152,7 +152,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_LONG); it("should log outgoing unknown HTTP requests via 'https'", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -175,7 +175,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_LONG); it("should log outgoing Google API requests", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -210,7 +210,7 @@ describe("FunctionsEmulator-Runtime", () => { }); it("should provide stubbed default app from initializeApp", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -228,7 +228,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide a stubbed app with custom options", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp({ @@ -258,7 +258,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide non-stubbed non-default app from initializeApp", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); // We still need to initialize default for snapshots @@ -276,7 +276,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should route all sub-fields accordingly", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -308,7 +308,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should expose Firestore prod when the emulator is not running", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { const admin = require("firebase-admin"); @@ -340,7 +340,7 @@ describe("FunctionsEmulator-Runtime", () => { port: 9090, }); - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { const admin = require("firebase-admin"); @@ -367,7 +367,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should expose RTDB prod when the emulator is not running", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { const admin = require("firebase-admin"); @@ -397,7 +397,7 @@ describe("FunctionsEmulator-Runtime", () => { port: 9090, }); - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { const admin = require("firebase-admin"); @@ -427,7 +427,7 @@ describe("FunctionsEmulator-Runtime", () => { port: 9090, }); - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { const admin = require("firebase-admin"); @@ -449,7 +449,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should return a real databaseURL when RTDB emulator is not running", async () => { const frb = _.cloneDeep(FunctionRuntimeBundles.onRequest); - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { const admin = require("firebase-admin"); @@ -484,7 +484,7 @@ describe("FunctionsEmulator-Runtime", () => { }); it("should tell the user if they've accessed a non-existent function field", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onCreate, () => { require("firebase-admin").initializeApp(); @@ -513,7 +513,7 @@ describe("FunctionsEmulator-Runtime", () => { describe("HTTPS", () => { it("should handle a GET request", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -533,7 +533,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request with form data", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -564,7 +564,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request with JSON data", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -595,7 +595,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request with text data", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -626,7 +626,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request with any other type", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -658,7 +658,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle a POST request and store rawBody", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -689,7 +689,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should forward request to Express app", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -717,7 +717,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should handle `x-forwarded-host`", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -741,7 +741,7 @@ describe("FunctionsEmulator-Runtime", () => { it("should report GMT time zone", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { return { @@ -761,7 +761,7 @@ describe("FunctionsEmulator-Runtime", () => { describe("Cloud Firestore", () => { it("should provide Change for firestore.onWrite()", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onWrite, () => { require("firebase-admin").initializeApp(); @@ -795,7 +795,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide Change for firestore.onUpdate()", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onUpdate, () => { require("firebase-admin").initializeApp(); @@ -828,7 +828,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide DocumentSnapshot for firestore.onDelete()", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onDelete, () => { require("firebase-admin").initializeApp(); @@ -860,7 +860,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should provide DocumentSnapshot for firestore.onCreate()", async () => { - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( FunctionRuntimeBundles.onWrite, () => { require("firebase-admin").initializeApp(); @@ -895,7 +895,7 @@ describe("FunctionsEmulator-Runtime", () => { describe("Error handling", () => { it("Should handle regular functions for Express handlers", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -921,7 +921,7 @@ describe("FunctionsEmulator-Runtime", () => { it("Should handle async functions for Express handlers", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); @@ -950,7 +950,7 @@ describe("FunctionsEmulator-Runtime", () => { it("Should handle async/runWith functions for Express handlers", async () => { const frb = FunctionRuntimeBundles.onRequest; - const worker = await startRuntimeWithFunctions( + const worker = await invokeFunction( frb, () => { require("firebase-admin").initializeApp(); diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 378198dc9ea..316418a3835 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -359,7 +359,7 @@ export class FunctionsEmulator implements EmulatorInstance { return hub; } - async startFunctionRuntime( + async invokeTrigger( backend: EmulatableBackend, trigger: EmulatedTriggerDefinition, proto?: any, @@ -370,6 +370,12 @@ export class FunctionsEmulator implements EmulatorInstance { ...bundleTemplate, proto, }; + if (this.args.debugPort) { + runtimeBundle.debug = { + functionTarget: trigger.entryPoint, + functionSignature: getSignatureType(trigger), + }; + } if (!backend.nodeBinary) { throw new FirebaseError(`No node binary for ${trigger.id}. This should never happen.`); } @@ -626,6 +632,12 @@ export class FunctionsEmulator implements EmulatorInstance { this.logger.logLabeled("SUCCESS", `functions[${definition.id}]`, msg); } } + + // In debug mode, we eagerly start a runtime process to allow debuggers to attach + // before invoking a function. + if (this.args.debugPort) { + this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary }); + } } addRealtimeDatabaseTrigger( @@ -1006,6 +1018,10 @@ export class FunctionsEmulator implements EmulatorInstance { process.env.PUBSUB_EMULATOR_HOST = pubsubHost; } + if (this.args.debugPort) { + // Start runtime in debug mode to allow triggers to share single runtime process. + envs["FUNCTION_DEBUG_MODE"] = "true"; + } return envs; } @@ -1033,7 +1049,7 @@ export class FunctionsEmulator implements EmulatorInstance { getRuntimeEnvs( backend: EmulatableBackend, - trigger: EmulatedTriggerDefinition + trigger?: EmulatedTriggerDefinition ): Record { return { ...this.getUserEnvs(backend), @@ -1046,7 +1062,7 @@ export class FunctionsEmulator implements EmulatorInstance { async resolveSecretEnvs( backend: EmulatableBackend, - trigger: EmulatedTriggerDefinition + trigger?: EmulatedTriggerDefinition ): Promise> { let secretEnvs: Record = {}; @@ -1063,36 +1079,39 @@ export class FunctionsEmulator implements EmulatorInstance { } } - const secrets: backend.SecretEnvVar[] = trigger.secretEnvironmentVariables || []; - const accesses = secrets - .filter((s) => !secretEnvs[s.secret]) - .map(async (s) => { - this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.key}@latest`); - const value = await accessSecretVersion(this.getProjectId(), s.key, "latest"); - return [s.secret, value]; - }); - const accessResults = await allSettled(accesses); + if (trigger) { + const secrets: backend.SecretEnvVar[] = trigger.secretEnvironmentVariables || []; + const accesses = secrets + .filter((s) => !secretEnvs[s.secret]) + .map(async (s) => { + this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.key}@latest`); + const value = await accessSecretVersion(this.getProjectId(), s.key, "latest"); + return [s.secret, value]; + }); + const accessResults = await allSettled(accesses); + + const errs: string[] = []; + for (const result of accessResults) { + if (result.status === "rejected") { + errs.push(result.reason as string); + } else { + const [k, v] = result.value; + secretEnvs[k] = v; + } + } - const errs: string[] = []; - for (const result of accessResults) { - if (result.status === "rejected") { - errs.push(result.reason as string); - } else { - const [k, v] = result.value; - secretEnvs[k] = v; + if (errs.length > 0) { + this.logger.logLabeled( + "ERROR", + "functions", + "Unable to access secret environment variables from Google Cloud Secret Manager. " + + "Make sure the credential used for the Functions Emulator have access " + + `or provide override values in ${LOCAL_SECRETS_FILE}:\n\t` + + errs.join("\n\t") + ); } } - if (errs.length > 0) { - this.logger.logLabeled( - "ERROR", - "functions", - "Unable to access secret environment variables from Google Cloud Secret Manager. " + - "Make sure the credential used for the Functions Emulator have access " + - `or provide override values in ${LOCAL_SECRETS_FILE}:\n\t` + - errs.join("\n\t") - ); - } return secretEnvs; } @@ -1102,11 +1121,17 @@ export class FunctionsEmulator implements EmulatorInstance { frb: FunctionsRuntimeBundle, opts: InvokeRuntimeOpts ): Promise { - // If we can use an existing worker there is almost nothing to do. - if (this.workerPool.readyForWork(trigger.id)) { - return this.workerPool.submitWork(trigger.id, frb, opts); + if (!this.workerPool.readyForWork(trigger.id)) { + await this.startRuntime(backend, opts, trigger); } + return this.workerPool.submitWork(trigger.id, frb, opts); + } + async startRuntime( + backend: EmulatableBackend, + opts: InvokeRuntimeOpts, + trigger?: EmulatedTriggerDefinition + ) { const emitter = new EventEmitter(); const args = [path.join(__dirname, "functionsEmulatorRuntime")]; @@ -1204,8 +1229,8 @@ export class FunctionsEmulator implements EmulatorInstance { instanceId: backend.extensionInstanceId, ref: backend.extensionVersion?.ref, }; - this.workerPool.addWorker(trigger.id, runtime, extensionLogInfo); - return this.workerPool.submitWork(trigger.id, frb, opts); + this.workerPool.addWorker(trigger?.id, runtime, extensionLogInfo); + return; } async disableBackgroundTriggers() { @@ -1240,7 +1265,7 @@ export class FunctionsEmulator implements EmulatorInstance { } const trigger = record.def; const service = getFunctionService(trigger); - const worker = await this.startFunctionRuntime(record.backend, trigger, proto); + const worker = await this.invokeTrigger(record.backend, trigger, proto); return new Promise((resolve, reject) => { if (projectId !== this.args.projectId) { @@ -1378,7 +1403,7 @@ export class FunctionsEmulator implements EmulatorInstance { ); } } - const worker = await this.startFunctionRuntime(record.backend, trigger); + const worker = await this.invokeTrigger(record.backend, trigger); worker.onLogs((el: EmulatorLog) => { if (el.level === "FATAL") { diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 4522fe02720..0119e15dee8 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -21,9 +21,10 @@ import { } from "./functionsEmulatorShared"; import { compareVersionStrings } from "./functionsEmulatorUtils"; -let functionTrigger: CloudFunction; +let functionModule: any; let FUNCTION_TARGET_NAME: string; let FUNCTION_SIGNATURE: string; +let FUNCTION_DEBUG_MODE: string; let developerPkgJSON: PackageJSON | undefined; @@ -778,7 +779,10 @@ function rawBodySaver(req: express.Request, res: express.Response, buf: Buffer): (req as any).rawBody = buf; } -async function processHTTPS(frb: FunctionsRuntimeBundle): Promise { +async function processHTTPS( + trigger: CloudFunction, + frb: FunctionsRuntimeBundle +): Promise { const ephemeralServer = express(); const functionRouter = express.Router(); // eslint-disable-line new-cap const socketPath = frb.socketPath; @@ -802,7 +806,7 @@ async function processHTTPS(frb: FunctionsRuntimeBundle): Promise { }); }); - await runHTTPS([req, res]); + await runHTTPS(trigger, [req, res]); } catch (err: any) { rejectEphemeralServer(err); } @@ -850,6 +854,7 @@ async function processHTTPS(frb: FunctionsRuntimeBundle): Promise { } async function processBackground( + trigger: CloudFunction, frb: FunctionsRuntimeBundle, signature: SignatureType ): Promise { @@ -857,7 +862,7 @@ async function processBackground( logDebug("ProcessBackground", proto); if (signature === "cloudevent") { - return runCloudEvent(proto); + return runCloudEvent(trigger, proto); } // All formats of the payload should carry a "data" property. The "context" property does @@ -875,7 +880,7 @@ async function processBackground( } } - await runBackground({ data, context }); + await runBackground(trigger, { data, context }); } /** @@ -895,29 +900,29 @@ async function runFunction(func: () => Promise): Promise { } } -async function runBackground(proto: any): Promise { +async function runBackground(trigger: CloudFunction, proto: any): Promise { logDebug("RunBackground", proto); await runFunction(() => { - return functionTrigger(proto.data, proto.context); + return trigger(proto.data, proto.context); }); } -async function runCloudEvent(event: unknown): Promise { +async function runCloudEvent(trigger: CloudFunction, event: unknown): Promise { logDebug("RunCloudEvent", event); await runFunction(() => { - return functionTrigger(event); + return trigger(event); }); } -async function runHTTPS(args: any[]): Promise { +async function runHTTPS(trigger: CloudFunction, args: any[]): Promise { if (args.length < 2) { throw new Error("Function must be passed 2 args."); } await runFunction(() => { - return functionTrigger(args[0], args[1]); + return trigger(args[0], args[1]); }); } @@ -955,7 +960,10 @@ function logDebug(msg: string, data?: any): void { new EmulatorLog("DEBUG", "runtime-status", `[${process.pid}] ${msg}`, data).log(); } -async function invokeTrigger(frb: FunctionsRuntimeBundle): Promise { +async function invokeTrigger( + trigger: CloudFunction, + frb: FunctionsRuntimeBundle +): Promise { new EmulatorLog("INFO", "runtime-status", `Beginning execution of "${FUNCTION_TARGET_NAME}"`, { frb, }).log(); @@ -988,10 +996,10 @@ async function invokeTrigger(frb: FunctionsRuntimeBundle): Promise { switch (FUNCTION_SIGNATURE) { case "event": case "cloudevent": - await processBackground(frb, FUNCTION_SIGNATURE); + await processBackground(trigger, frb, FUNCTION_SIGNATURE); break; case "http": - await processHTTPS(frb); + await processHTTPS(trigger, frb); break; } @@ -1010,24 +1018,28 @@ async function invokeTrigger(frb: FunctionsRuntimeBundle): Promise { async function initializeRuntime( frb: FunctionsRuntimeBundle ): Promise { - FUNCTION_TARGET_NAME = process.env.FUNCTION_TARGET || ""; - if (!FUNCTION_TARGET_NAME) { - new EmulatorLog( - "FATAL", - "runtime-status", - `Environment variable FUNCTION_TARGET cannot be empty. This shouldn't happen.` - ).log(); - await flushAndExit(1); - } + FUNCTION_DEBUG_MODE = process.env.FUNCTION_DEBUG_MODE || ""; - FUNCTION_SIGNATURE = process.env.FUNCTION_SIGNATURE_TYPE || ""; - if (!FUNCTION_SIGNATURE) { - new EmulatorLog( - "FATAL", - "runtime-status", - `Environment variable FUNCTION_SIGNATURE_TYPE cannot be empty. This shouldn't happen.` - ).log(); - await flushAndExit(1); + if (!FUNCTION_DEBUG_MODE) { + FUNCTION_TARGET_NAME = process.env.FUNCTION_TARGET || ""; + if (!FUNCTION_TARGET_NAME) { + new EmulatorLog( + "FATAL", + "runtime-status", + `Environment variable FUNCTION_TARGET cannot be empty. This shouldn't happen.` + ).log(); + await flushAndExit(1); + } + + FUNCTION_SIGNATURE = process.env.FUNCTION_SIGNATURE_TYPE || ""; + if (!FUNCTION_SIGNATURE) { + new EmulatorLog( + "FATAL", + "runtime-status", + `Environment variable FUNCTION_SIGNATURE_TYPE cannot be empty. This shouldn't happen.` + ).log(); + await flushAndExit(1); + } } logDebug(`Disabled runtime features: ${JSON.stringify(frb.disabled_features)}`); @@ -1050,11 +1062,10 @@ async function initializeRuntime( await initializeFirebaseAdminStubs(frb); } -async function loadTrigger( +async function loadTriggers( frb: FunctionsRuntimeBundle, - functionTarget: string, serializedFunctionTrigger?: string -): Promise> { +): Promise { let triggerModule; if (serializedFunctionTrigger) { /* tslint:disable:no-eval */ @@ -1074,13 +1085,7 @@ async function loadTrigger( triggerModule = await dynamicImport(moduleURL); } } - const maybeTrigger = functionTarget.split(".").reduce((mod, functionTargetPart) => { - return mod?.[functionTargetPart]; - }, triggerModule); - if (!maybeTrigger) { - throw new Error(`Failed to find function ${functionTarget} in the loaded module`); - } - return maybeTrigger; + return triggerModule; } async function flushAndExit(code: number) { @@ -1103,32 +1108,41 @@ async function handleMessage(message: string) { return; } - if (!functionTrigger) { + if (!functionModule) { try { await initializeRuntime(runtimeArgs.frb); const serializedTriggers = runtimeArgs.opts ? runtimeArgs.opts.serializedTriggers : undefined; - functionTrigger = await loadTrigger( - runtimeArgs.frb, - FUNCTION_TARGET_NAME, - serializedTriggers - ); + functionModule = await loadTriggers(runtimeArgs.frb, serializedTriggers); } catch (e: any) { logDebug(e); new EmulatorLog( "FATAL", "runtime-status", - `Failed to initialize and load trigger. This shouldn't happen: ${e.message}` + `Failed to initialize and load triggers. This shouldn't happen: ${e.message}` ).log(); await flushAndExit(1); return; } } - // If there's no trigger id it's just a diagnostic call. We can go idle right away. + if (FUNCTION_DEBUG_MODE) { + // In debug mode, all function triggers run in a single process. + // Target trigger is dynamically defined in the FunctionRuntimeBundle. + FUNCTION_TARGET_NAME = runtimeArgs.frb.debug!.functionTarget; + FUNCTION_SIGNATURE = runtimeArgs.frb.debug!.functionSignature; + } + + const trigger = FUNCTION_TARGET_NAME.split(".").reduce((mod, functionTargetPart) => { + return mod?.[functionTargetPart]; + }, functionModule) as CloudFunction; + if (!trigger) { + throw new Error(`Failed to find function ${FUNCTION_TARGET_NAME} in the loaded module`); + } + logDebug(`Beginning invocation function ${FUNCTION_TARGET_NAME}!`); try { - await invokeTrigger(runtimeArgs.frb); + await invokeTrigger(trigger, runtimeArgs.frb); // If we were passed serialized triggers we have to exit the runtime after, // otherwise we can go IDLE and await another request. if (runtimeArgs.opts && runtimeArgs.opts.serializedTriggers) { diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 31d808a63c5..d5dd7d0dc98 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -68,6 +68,16 @@ export interface FunctionsRuntimeBundle { // and none of these extra properties. socketPath?: string; disabled_features?: FunctionsRuntimeFeatures; + // TODO(danielylee): To make debugging in Functions Emulator w/ --inspect-functions flag a good experience, we run + // all functions in a single runtime process. This is drastically different to production environment where each + // function runs in isolated, independent containers. Until we have better design for supporting --inspect-functions + // flag, we begrudgingly include the target trigger info in the runtime bundle so the "debug" runtime process can + // choose which trigger to run at runtime. + // See https://github.com/firebase/firebase-tools/issues/4189. + debug?: { + functionTarget: string; + functionSignature: string; + }; } export interface FunctionsRuntimeFeatures { diff --git a/src/emulator/functionsEmulatorShell.ts b/src/emulator/functionsEmulatorShell.ts index 3e0010576bc..c21c81fe108 100644 --- a/src/emulator/functionsEmulatorShell.ts +++ b/src/emulator/functionsEmulatorShell.ts @@ -64,7 +64,7 @@ export class FunctionsEmulatorShell implements FunctionsShellController { data, }; - this.emu.startFunctionRuntime(this.backend, trigger, proto); + this.emu.invokeTrigger(this.backend, trigger, proto); } private getTrigger(name: string): EmulatedTriggerDefinition { From 7271c958eea82b8d223c0e581a94e3b5b7e57619 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 2 Mar 2022 14:29:39 -0500 Subject: [PATCH 0119/1699] Change all instances of == to === and != to !== (#4235) * Change all instances of == to === * Change all instances of != to !== * Add new linter rule disallowing == and != except for null * Revert changes on security rules * Move new linter rule to permanent rule block --- .eslintrc.js | 1 + scripts/integration-helpers/framework.ts | 4 +-- scripts/triggers-end-to-end-tests/tests.ts | 26 +++++++++---------- src/appdistribution/options-parser-util.ts | 2 +- src/commands/apps-android-sha-create.ts | 4 +-- src/commands/apps-sdkconfig.ts | 2 +- src/commands/database-rules-list.ts | 4 +-- src/commands/ext-export.ts | 2 +- src/commands/functions-config-export.ts | 2 +- src/commands/hosting-clone.ts | 6 ++--- src/commands/remoteconfig-get.ts | 2 +- src/deploy/extensions/deploymentSummary.ts | 6 ++--- src/deploy/extensions/planner.ts | 2 +- src/deploy/extensions/tasks.ts | 2 +- src/deploy/functions/backend.ts | 6 ++--- src/deploy/functions/containerCleaner.ts | 6 ++--- src/deploy/functions/ensure.ts | 2 +- src/deploy/functions/functionsDeployHelper.ts | 4 +-- src/deploy/functions/pricing.ts | 2 +- src/deploy/functions/prompts.ts | 4 +-- src/deploy/functions/release/fabricator.ts | 2 +- src/deploy/functions/release/index.ts | 2 +- src/deploy/functions/release/planner.ts | 16 ++++++------ src/deploy/functions/runtimes/golang/index.ts | 2 +- .../functions/runtimes/node/parseTriggers.ts | 2 +- src/deploy/functions/validate.ts | 6 ++--- src/deploy/remoteconfig/functions.ts | 4 +-- src/emulator/auth/cloudFunctions.ts | 2 +- src/emulator/auth/operations.ts | 2 +- src/emulator/controller.ts | 4 +-- src/emulator/functionsEmulator.ts | 2 +- src/emulator/functionsEmulatorRuntime.ts | 4 +-- src/emulator/functionsEmulatorUtils.ts | 6 ++--- src/emulator/loggingEmulator.ts | 2 +- src/emulator/pubsubEmulator.ts | 2 +- src/emulator/storage/apis/firebase.ts | 18 ++++++------- src/emulator/storage/apis/gcloud.ts | 8 +++--- src/emulator/storage/files.ts | 18 ++++++------- src/emulator/storage/index.ts | 4 +-- src/emulator/storage/metadata.ts | 4 +-- src/emulator/storage/rules/runtime.ts | 14 +++++----- src/error.ts | 2 +- src/extensions/askUserForParam.ts | 2 +- src/extensions/changelog.ts | 4 ++- src/extensions/checkProjectBilling.ts | 2 +- src/extensions/displayExtensionInfo.ts | 2 +- src/extensions/emulator/optionsHelper.ts | 2 +- src/extensions/extensionsApi.ts | 2 +- src/extensions/extensionsHelper.ts | 14 +++++----- src/extensions/metricsUtils.ts | 8 +++--- src/extensions/refs.ts | 2 +- src/extensions/secretsUtils.ts | 6 ++--- src/functional.ts | 2 +- src/functions/env.ts | 2 +- src/gcp/cloudfunctions.ts | 6 ++--- src/gcp/cloudfunctionsv2.ts | 4 +-- src/gcp/cloudtasks.ts | 2 +- src/gcp/docker.ts | 4 +-- src/gcp/run.ts | 4 +-- src/hosting/api.ts | 2 +- src/hosting/proxy.ts | 4 +-- src/init/features/account.ts | 2 +- src/management/database.ts | 2 +- src/rulesDeploy.ts | 2 +- .../functions/release/fabricator.spec.ts | 2 +- src/test/emulators/storage.rules.spec.ts | 2 +- src/test/management/database.spec.ts | 4 +-- src/utils.ts | 2 +- 68 files changed, 154 insertions(+), 151 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d6f8dd98bbc..fa57abe715e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -38,6 +38,7 @@ module.exports = { "no-invalid-this": "off", // Turned off in favor of @typescript-eslint/no-invalid-this. "@typescript-eslint/no-invalid-this": ["error"], + eqeqeq: ["error", "always", { null: "ignore" }], "@typescript-eslint/ban-types": "warn", // TODO(bkendall): remove, allow to error. "@typescript-eslint/explicit-function-return-type": ["warn", { allowExpressions: true }], // TODO(bkendall): SET to error. diff --git a/scripts/integration-helpers/framework.ts b/scripts/integration-helpers/framework.ts index 5b65ff68e0e..efa478e2861 100644 --- a/scripts/integration-helpers/framework.ts +++ b/scripts/integration-helpers/framework.ts @@ -156,7 +156,7 @@ export class TriggerEndToEndTest { startEmulators(additionalArgs: string[] = []): Promise { const cli = new CLIProcess("default", this.workdir); const started = cli.start("emulators:start", this.project, additionalArgs, (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); @@ -241,7 +241,7 @@ export class TriggerEndToEndTest { this.project, additionalArgs, (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); diff --git a/scripts/triggers-end-to-end-tests/tests.ts b/scripts/triggers-end-to-end-tests/tests.ts index d347b10c4c3..23a940a3de4 100755 --- a/scripts/triggers-end-to-end-tests/tests.ts +++ b/scripts/triggers-end-to-end-tests/tests.ts @@ -44,7 +44,7 @@ function readConfig(): FrameworkOptions { function logIncludes(msg: string) { return (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(msg); @@ -440,7 +440,7 @@ describe("import/export end to end", () => { FIREBASE_PROJECT, ["--only", "firestore"], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); @@ -451,7 +451,7 @@ describe("import/export end to end", () => { const exportCLI = new CLIProcess("2", __dirname); const exportPath = fs.mkdtempSync(path.join(os.tmpdir(), "emulator-data")); await exportCLI.start("emulators:export", FIREBASE_PROJECT, [exportPath], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes("Export complete"); @@ -468,7 +468,7 @@ describe("import/export end to end", () => { FIREBASE_PROJECT, ["--only", "firestore", "--import", exportPath], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); @@ -491,7 +491,7 @@ describe("import/export end to end", () => { FIREBASE_PROJECT, ["--only", "database"], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); @@ -540,7 +540,7 @@ describe("import/export end to end", () => { const exportCLI = new CLIProcess("2", __dirname); const exportPath = fs.mkdtempSync(path.join(os.tmpdir(), "emulator-data")); await exportCLI.start("emulators:export", FIREBASE_PROJECT, [exportPath], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes("Export complete"); @@ -562,7 +562,7 @@ describe("import/export end to end", () => { FIREBASE_PROJECT, ["--only", "database", "--import", exportPath, "--export-on-exit"], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); @@ -606,7 +606,7 @@ describe("import/export end to end", () => { const emulatorsCLI = new CLIProcess("1", __dirname); await emulatorsCLI.start("emulators:start", project, ["--only", "auth"], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); @@ -635,7 +635,7 @@ describe("import/export end to end", () => { const exportCLI = new CLIProcess("2", __dirname); const exportPath = fs.mkdtempSync(path.join(os.tmpdir(), "emulator-data")); await exportCLI.start("emulators:export", project, [exportPath], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes("Export complete"); @@ -685,7 +685,7 @@ describe("import/export end to end", () => { project, ["--only", "auth", "--import", exportPath], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); @@ -797,7 +797,7 @@ describe("import/export end to end", () => { const emulatorsCLI = new CLIProcess("1", __dirname); await emulatorsCLI.start("emulators:start", project, ["--only", "auth"], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); @@ -807,7 +807,7 @@ describe("import/export end to end", () => { const exportCLI = new CLIProcess("2", __dirname); const exportPath = fs.mkdtempSync(path.join(os.tmpdir(), "emulator-data")); await exportCLI.start("emulators:export", project, [exportPath], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes("Export complete"); @@ -838,7 +838,7 @@ describe("import/export end to end", () => { project, ["--only", "auth", "--import", exportPath], (data: unknown) => { - if (typeof data != "string" && !Buffer.isBuffer(data)) { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { throw new Error(`data is not a string or buffer (${typeof data})`); } return data.includes(ALL_EMULATORS_STARTED_LOG); diff --git a/src/appdistribution/options-parser-util.ts b/src/appdistribution/options-parser-util.ts index 632e7f7778b..87e2b69e0b7 100644 --- a/src/appdistribution/options-parser-util.ts +++ b/src/appdistribution/options-parser-util.ts @@ -26,7 +26,7 @@ export function getTestersOrGroups(value: string, file: string): string[] { * returns a string[] of emails. */ export function getEmails(emails: string[], file: string): string[] { - if (emails.length == 0) { + if (emails.length === 0) { ensureFileExists(file); const readFile = fs.readFileSync(file, "utf8"); return splitter(readFile); diff --git a/src/commands/apps-android-sha-create.ts b/src/commands/apps-android-sha-create.ts index f42a47efe0a..cab12200a54 100644 --- a/src/commands/apps-android-sha-create.ts +++ b/src/commands/apps-android-sha-create.ts @@ -9,8 +9,8 @@ import { promiseWithSpinner } from "../utils"; function getCertHashType(shaHash: string): string { shaHash = shaHash.replace(/:/g, ""); const shaHashCount = shaHash.length; - if (shaHashCount == 40) return ShaCertificateType.SHA_1.toString(); - if (shaHashCount == 64) return ShaCertificateType.SHA_256.toString(); + if (shaHashCount === 40) return ShaCertificateType.SHA_1.toString(); + if (shaHashCount === 64) return ShaCertificateType.SHA_256.toString(); return ShaCertificateType.SHA_CERTIFICATE_TYPE_UNSPECIFIED.toString(); } diff --git a/src/commands/apps-sdkconfig.ts b/src/commands/apps-sdkconfig.ts index d25e1b7b510..9a41e7ade2b 100644 --- a/src/commands/apps-sdkconfig.ts +++ b/src/commands/apps-sdkconfig.ts @@ -98,7 +98,7 @@ module.exports = new Command("apps:sdkconfig [platform] [appId]") spinner.succeed(); const fileInfo = getAppConfigFile(configData, appPlatform); - if (appPlatform == AppPlatform.WEB) { + if (appPlatform === AppPlatform.WEB) { fileInfo.sdkConfig = configData; } diff --git a/src/commands/database-rules-list.ts b/src/commands/database-rules-list.ts index 2c1fdacfd00..d77d722fd6f 100644 --- a/src/commands/database-rules-list.ts +++ b/src/commands/database-rules-list.ts @@ -20,10 +20,10 @@ export default new Command("database:rules:list") const rulesets = await metadata.listAllRulesets(options.instance); for (const ruleset of rulesets) { const labels = []; - if (ruleset.id == labeled.stable) { + if (ruleset.id === labeled.stable) { labels.push("stable"); } - if (ruleset.id == labeled.canary) { + if (ruleset.id === labeled.canary) { labels.push("canary"); } logger.info(`${ruleset.id} ${ruleset.createdAt} ${labels.join(",")}`); diff --git a/src/commands/ext-export.ts b/src/commands/ext-export.ts index 17704f55db2..ca0aeb2103d 100644 --- a/src/commands/ext-export.ts +++ b/src/commands/ext-export.ts @@ -41,7 +41,7 @@ module.exports = new Command("ext:export") }) ); - if (have.length == 0) { + if (have.length === 0) { logger.info( `No extension instances installed on ${projectId}, so there is nothing to export.` ); diff --git a/src/commands/functions-config-export.ts b/src/commands/functions-config-export.ts index c6d45cbe2f1..753cf93cfba 100644 --- a/src/commands/functions-config-export.ts +++ b/src/commands/functions-config-export.ts @@ -127,7 +127,7 @@ export default new Command("functions:config:export") } const errMsg = configExport.hydrateEnvs(pInfos, prefix); - if (errMsg.length == 0) { + if (errMsg.length === 0) { break; } prefix = await promptForPrefix(errMsg); diff --git a/src/commands/hosting-clone.ts b/src/commands/hosting-clone.ts index a09a3e7f647..5c61801c399 100644 --- a/src/commands/hosting-clone.ts +++ b/src/commands/hosting-clone.ts @@ -46,8 +46,8 @@ export default new Command("hosting:clone ") sourceChannelId = normalizeName(sourceChannelId); } - const equalSiteIds = sourceSiteId == targetSiteId; - const equalChannelIds = sourceChannelId == targetChannelId; + const equalSiteIds = sourceSiteId === targetSiteId; + const equalChannelIds = sourceChannelId === targetChannelId; if (equalSiteIds && equalChannelIds) { throw new FirebaseError( `Source and destination cannot be equal. Please pick a different source or desination.` @@ -106,7 +106,7 @@ export default new Command("hosting:clone ") } const currentTargetVersionName = tChannel.release?.version?.name; - if (equalSiteIds && sourceVersionName == currentTargetVersionName) { + if (equalSiteIds && sourceVersionName === currentTargetVersionName) { utils.logSuccess( `Channels ${bold(sourceChannelId)} and ${bold( targetChannel diff --git a/src/commands/remoteconfig-get.ts b/src/commands/remoteconfig-get.ts index 9c2175eec7b..4cde7f7465a 100644 --- a/src/commands/remoteconfig-get.ts +++ b/src/commands/remoteconfig-get.ts @@ -20,7 +20,7 @@ const tableHead = ["Entry Name", "Value"]; const MAX_DISPLAY_ITEMS = 20; function checkValidOptionalNumber(versionNumber?: string): string | undefined { - if (!versionNumber || typeof Number(versionNumber) == "number") { + if (!versionNumber || typeof Number(versionNumber) === "number") { return versionNumber; } throw new FirebaseError(`Could not interpret "${versionNumber}" as a valid number.`); diff --git a/src/deploy/extensions/deploymentSummary.ts b/src/deploy/extensions/deploymentSummary.ts index 6421224b00d..16917417930 100644 --- a/src/deploy/extensions/deploymentSummary.ts +++ b/src/deploy/extensions/deploymentSummary.ts @@ -10,8 +10,8 @@ export const humanReadable = (dep: planner.InstanceSpec) => const humanReadableUpdate = (from: planner.InstanceSpec, to: planner.InstanceSpec) => { if ( - from.ref?.publisherId == to.ref?.publisherId && - from.ref?.extensionId == to.ref?.extensionId + from.ref?.publisherId === to.ref?.publisherId && + from.ref?.extensionId === to.ref?.extensionId ) { return `\t${clc.bold(from.instanceId)} (${refs.toExtensionVersionRef(from.ref!)} => ${ to.ref?.version @@ -37,7 +37,7 @@ export function updatesSummary( ): string { const instancesToUpdate = toUpdate .map((to) => { - const from = have.find((exists) => exists.instanceId == to.instanceId); + const from = have.find((exists) => exists.instanceId === to.instanceId); return humanReadableUpdate(from!, to); }) .join("\n"); diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 717debcadbd..29fcde84e60 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -126,7 +126,7 @@ export async function want(args: { * @param version a semver or semver range */ export async function resolveVersion(ref: refs.Ref): Promise { - if (!ref.version || ref.version == "latest") { + if (!ref.version || ref.version === "latest") { return "latest"; } const extensionRef = refs.toExtensionRef(ref); diff --git a/src/deploy/extensions/tasks.ts b/src/deploy/extensions/tasks.ts index 514581ba4bc..36cd96bf92f 100644 --- a/src/deploy/extensions/tasks.ts +++ b/src/deploy/extensions/tasks.ts @@ -6,7 +6,7 @@ import * as utils from "../../utils"; import { ErrorHandler } from "./errors"; import { InstanceSpec } from "./planner"; -const isRetryable = (err: any) => err.status == 429 || err.status == 409; +const isRetryable = (err: any) => err.status === 429 || err.status === 409; export type DeploymentType = "create" | "update" | "configure" | "delete"; export interface ExtensionDeploymentTask { diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index f9955e516b9..f6336dbe124 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -314,7 +314,7 @@ export function of(...endpoints: Endpoint[]): Backend { */ export function isEmptyBackend(backend: Backend): boolean { return ( - Object.keys(backend.requiredAPIs).length == 0 && Object.keys(backend.endpoints).length === 0 + Object.keys(backend.requiredAPIs).length === 0 && Object.keys(backend.endpoints).length === 0 ); } @@ -442,7 +442,7 @@ export async function checkAvailability(context: Context, want: Backend): Promis const gcfV1Regions = new Set(); const gcfV2Regions = new Set(); for (const ep of allEndpoints(want)) { - if (ep.platform == "gcfv1") { + if (ep.platform === "gcfv1") { gcfV1Regions.add(ep.region); } else { gcfV2Regions.add(ep.region); @@ -568,7 +568,7 @@ export function compareFunctions( left: TargetIds & { platform: FunctionsPlatform }, right: TargetIds & { platform: FunctionsPlatform } ): number { - if (left.platform != right.platform) { + if (left.platform !== right.platform) { return right.platform < left.platform ? -1 : 1; } if (left.region < right.region) { diff --git a/src/deploy/functions/containerCleaner.ts b/src/deploy/functions/containerCleaner.ts index 4c14f2fb537..eaf2d04118d 100644 --- a/src/deploy/functions/containerCleaner.ts +++ b/src/deploy/functions/containerCleaner.ts @@ -88,7 +88,7 @@ export async function cleanupBuildImages( "Unhandled error cleaning up build images. This could result in a small monthly bill if not corrected. "; message += "You can attempt to delete these images by redeploying or you can delete them manually at"; - if (failedDomains.size == 1) { + if (failedDomains.size === 1) { message += " " + failedDomains.values().next().value; } else { message += [...failedDomains].map((domain) => "\n\t" + domain).join(""); @@ -295,7 +295,7 @@ export async function listGcfPaths( .reduce((acc, val) => [...acc, ...val], []) .filter((loc) => locationsSet.has(loc)); - if (failedSubdomains.length == subdomains.size) { + if (failedSubdomains.length === subdomains.size) { throw new FirebaseError("Failed to search all subdomains."); } else if (failedSubdomains.length > 0) { throw new FirebaseError( @@ -343,7 +343,7 @@ export async function deleteGcfArtifacts( }); await Promise.all(deleteLocations); - if (failedSubdomains.length == subdomains.size) { + if (failedSubdomains.length === subdomains.size) { throw new FirebaseError("Failed to search all subdomains."); } else if (failedSubdomains.length > 0) { throw new FirebaseError( diff --git a/src/deploy/functions/ensure.ts b/src/deploy/functions/ensure.ts index a14884d1e9a..23bd8d1e541 100644 --- a/src/deploy/functions/ensure.ts +++ b/src/deploy/functions/ensure.ts @@ -149,7 +149,7 @@ export async function secretAccess( for (const serviceAccount of serviceAccounts) { wantSecrets[secret]?.delete(serviceAccount); } - if (wantSecrets[secret]?.size == 0) { + if (wantSecrets[secret]?.size === 0) { delete wantSecrets[secret]; } } diff --git a/src/deploy/functions/functionsDeployHelper.ts b/src/deploy/functions/functionsDeployHelper.ts index edc3553e840..dafa2e45b9e 100644 --- a/src/deploy/functions/functionsDeployHelper.ts +++ b/src/deploy/functions/functionsDeployHelper.ts @@ -11,7 +11,7 @@ export function functionMatchesGroup(func: backend.TargetIds, groupChunks: strin const functionNameChunks = func.id.split("-").slice(0, groupChunks.length); // Should never happen. It would mean the user has asked to deploy something that is // a sub-function. E.g. function foo-bar and group chunks [foo, bar, baz]. - if (functionNameChunks.length != groupChunks.length) { + if (functionNameChunks.length !== groupChunks.length) { return false; } for (let i = 0; i < groupChunks.length; i += 1) { @@ -30,7 +30,7 @@ export function getFilterGroups(options: { only?: string }): string[][] { const only = options.only!.split(","); const onlyFunctions = only.filter((filter) => { const opts = filter.split(":"); - return opts[0] == "functions" && opts[1]; + return opts[0] === "functions" && opts[1]; }); return onlyFunctions.map((filter) => { return filter.split(":")[1].split(/[.-]/); diff --git a/src/deploy/functions/pricing.ts b/src/deploy/functions/pricing.ts index 37208388200..41837131e44 100644 --- a/src/deploy/functions/pricing.ts +++ b/src/deploy/functions/pricing.ts @@ -133,7 +133,7 @@ export function canCalculateMinInstanceCost(endpoint: backend.Endpoint): boolean return true; } - if (endpoint.platform == "gcfv1") { + if (endpoint.platform === "gcfv1") { if (!MB_TO_GHZ[endpoint.availableMemoryMb || 256]) { return false; } diff --git a/src/deploy/functions/prompts.ts b/src/deploy/functions/prompts.ts index dc25e253c04..d686d6aaca9 100644 --- a/src/deploy/functions/prompts.ts +++ b/src/deploy/functions/prompts.ts @@ -35,7 +35,7 @@ export async function promptForFailurePolicies( return !(existing && backend.isEventTriggered(existing) && existing.eventTrigger.retry); }); - if (newRetryEndpoints.length == 0) { + if (newRetryEndpoints.length === 0) { return; } @@ -193,7 +193,7 @@ export async function promptForMinInstances( costLine = `With these options, your minimum bill will be $${cost} in a 30-day month`; } let cudAnnotation = ""; - if (backend.someEndpoint(want, (fn) => fn.platform == "gcfv2" && !!fn.minInstances)) { + if (backend.someEndpoint(want, (fn) => fn.platform === "gcfv2" && !!fn.minInstances)) { cudAnnotation = "\nThis bill can be lowered with a one year commitment. See https://cloud.google.com/run/cud for more"; } diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 08320fd47a1..7f9bc832bf6 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -313,7 +313,7 @@ export class Fabricator { } const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; - if (mem >= backend.MIN_MEMORY_FOR_CONCURRENCY && endpoint.concurrency != 1) { + if (mem >= backend.MIN_MEMORY_FOR_CONCURRENCY && endpoint.concurrency !== 1) { await this.setConcurrency( endpoint, serviceName, diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index 833bc461584..3ba4fe226d5 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -83,7 +83,7 @@ export async function release( const allErrors = summary.results.filter((r) => r.error).map((r) => r.error) as Error[]; if (allErrors.length) { - const opts = allErrors.length == 1 ? { original: allErrors[0] } : { children: allErrors }; + const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors }; throw new FirebaseError("There was an error deploying functions", { ...opts, exit: 2 }); } } diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index dcb6e1d33d9..c2f38154af1 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -136,10 +136,10 @@ export function upgradedToGCFv2WithoutSettingConcurrency( * a user listens to a different bucket, which happens to have a different region. */ export function changedTriggerRegion(want: backend.Endpoint, have: backend.Endpoint): boolean { - if (want.platform != "gcfv2") { + if (want.platform !== "gcfv2") { return false; } - if (have.platform != "gcfv2") { + if (have.platform !== "gcfv2") { return false; } if (!backend.isEventTriggered(want)) { @@ -148,7 +148,7 @@ export function changedTriggerRegion(want: backend.Endpoint, have: backend.Endpo if (!backend.isEventTriggered(have)) { return false; } - return want.eventTrigger.region != have.eventTrigger.region; + return want.eventTrigger.region !== have.eventTrigger.region; } /** Whether a user changed the Pub/Sub topic of a GCFv2 function (which isn't allowed in the API). */ @@ -165,13 +165,13 @@ export function changedV2PubSubTopic(want: backend.Endpoint, have: backend.Endpo if (!backend.isEventTriggered(have)) { return false; } - if (want.eventTrigger.eventType != gcfv2.PUBSUB_PUBLISH_EVENT) { + if (want.eventTrigger.eventType !== gcfv2.PUBSUB_PUBLISH_EVENT) { return false; } if (have.eventTrigger.eventType !== gcfv2.PUBSUB_PUBLISH_EVENT) { return false; } - return have.eventTrigger.eventFilters["resource"] != want.eventTrigger.eventFilters["resource"]; + return have.eventTrigger.eventFilters["resource"] !== want.eventTrigger.eventFilters["resource"]; } /** Whether a user upgraded a scheduled function (which goes from Pub/Sub to HTTPS). */ @@ -216,14 +216,14 @@ export function checkForIllegalUpdate(want: backend.Endpoint, have: backend.Endp }; const wantType = triggerType(want); const haveType = triggerType(have); - if (wantType != haveType) { + if (wantType !== haveType) { throw new FirebaseError( `[${getFunctionLabel( want )}] Changing from ${haveType} function to ${wantType} function is not allowed. Please delete your function and create a new one instead.` ); } - if (want.platform == "gcfv1" && have.platform == "gcfv2") { + if (want.platform === "gcfv1" && have.platform === "gcfv2") { throw new FirebaseError( `[${getFunctionLabel(want)}] Functions cannot be downgraded from GCFv2 to GCFv1` ); @@ -241,7 +241,7 @@ export function checkForIllegalUpdate(want: backend.Endpoint, have: backend.Endp * upgrading to v2 in tests before production is ready */ export function checkForV2Upgrade(want: backend.Endpoint, have: backend.Endpoint): void { - if (want.platform == "gcfv2" && have.platform == "gcfv1") { + if (want.platform === "gcfv2" && have.platform === "gcfv1") { throw new FirebaseError( `[${getFunctionLabel( have diff --git a/src/deploy/functions/runtimes/golang/index.ts b/src/deploy/functions/runtimes/golang/index.ts index 50edabe7c0b..f7b96f23037 100644 --- a/src/deploy/functions/runtimes/golang/index.ts +++ b/src/deploy/functions/runtimes/golang/index.ts @@ -87,7 +87,7 @@ export class Delegate { }, stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "pipe"], }); - if (genBinary.status != 0) { + if (genBinary.status !== 0) { throw new FirebaseError("Failed to run codegen", { children: [new Error(genBinary.stderr.toString())], }); diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index e7293883589..9c57d892ff0 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -182,7 +182,7 @@ export function addResourcesToBackend( // +!! is 1 for truthy values and 0 for falsy values const triggerCount = +!!annotation.httpsTrigger + +!!annotation.eventTrigger + +!!annotation.taskQueueTrigger; - if (triggerCount != 1) { + if (triggerCount !== 1) { throw new FirebaseError( "Unexpected annotation generated by the Firebase Functions SDK. This should never happen." ); diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 66e4370ae11..b9f8b8769a9 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -16,7 +16,7 @@ export function endpointsAreValid(wantBackend: backend.Backend): void { // Our SDK doesn't let people articulate this, but it's theoretically possible in the manifest syntax. const gcfV1WithConcurrency = backend .allEndpoints(wantBackend) - .filter((endpoint) => (endpoint.concurrency || 1) != 1 && endpoint.platform == "gcfv1") + .filter((endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1") .map((endpoint) => endpoint.id); if (gcfV1WithConcurrency.length) { const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency.join( @@ -28,7 +28,7 @@ export function endpointsAreValid(wantBackend: backend.Backend): void { const tooSmallForConcurrency = backend .allEndpoints(wantBackend) .filter((endpoint) => { - if ((endpoint.concurrency || 1) == 1) { + if ((endpoint.concurrency || 1) === 1) { return false; } const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; @@ -145,7 +145,7 @@ async function validateSecretVersions(projectId: string, endpoints: backend.Endp for (const result of results) { if (result.status === "fulfilled") { const sv = result.value; - if (sv.state != "ENABLED") { + if (sv.state !== "ENABLED") { errs.push( new FirebaseError( `Expected secret ${sv.secret.name}@${sv.versionId} to be in state ENABLED not ${sv.state}.` diff --git a/src/deploy/remoteconfig/functions.ts b/src/deploy/remoteconfig/functions.ts index 34445825038..4efcbb9fd48 100644 --- a/src/deploy/remoteconfig/functions.ts +++ b/src/deploy/remoteconfig/functions.ts @@ -34,10 +34,10 @@ export function validateInputRemoteConfigTemplate( template: RemoteConfigTemplate ): RemoteConfigTemplate { const templateCopy = JSON.parse(JSON.stringify(template)); - if (!templateCopy || templateCopy == "null" || templateCopy == "undefined") { + if (!templateCopy || templateCopy === "null" || templateCopy === "undefined") { throw new FirebaseError(`Invalid Remote Config template: ${JSON.stringify(templateCopy)}`); } - if (typeof templateCopy.etag !== "string" || templateCopy.etag == "") { + if (typeof templateCopy.etag !== "string" || templateCopy.etag === "") { throw new FirebaseError("ETag must be a non-empty string"); } if (templateCopy.conditions && !Array.isArray(templateCopy.conditions)) { diff --git a/src/emulator/auth/cloudFunctions.ts b/src/emulator/auth/cloudFunctions.ts index c7e94bbab6a..757a7a066b8 100644 --- a/src/emulator/auth/cloudFunctions.ts +++ b/src/emulator/auth/cloudFunctions.ts @@ -49,7 +49,7 @@ export class AuthCloudFunction { err = e; } - if (err || res?.status != 200) { + if (err || res?.status !== 200) { this.logger.logLabeled( "WARN", "functions", diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index 5bb0cd78165..ebe0e50680e 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -1990,7 +1990,7 @@ function mfaSignInFinalize( let { user, signInProvider } = parsePendingCredential(state, reqBody.mfaPendingCredential); const enrollment = user.mfaInfo?.find( - (enrollment) => enrollment.unobfuscatedPhoneInfo == phoneNumber + (enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber ); assert(enrollment && enrollment.mfaEnrollmentId, "MFA_ENROLLMENT_NOT_FOUND"); diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 71b4c1cf5b5..c7ae31f8092 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -48,7 +48,7 @@ import { previews } from "../previews"; const START_LOGGING_EMULATOR = utils.envOverride( "START_LOGGING_EMULATOR", "false", - (val) => val == "true" + (val) => val === "true" ); async function getAndCheckAddress(emulator: Emulators, options: Options): Promise
{ @@ -82,7 +82,7 @@ async function getAndCheckAddress(emulator: Emulators, options: Options): Promis if (!portOpen) { if (findAvailablePort) { const newPort = await portUtils.findAvailablePort(host, port); - if (newPort != port) { + if (newPort !== port) { loggerForEmulator.logLabeled( "WARN", emulator, diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 316418a3835..a76d43cc406 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -1140,7 +1140,7 @@ export class FunctionsEmulator implements EmulatorInstance { } if (this.args.debugPort) { - if (process.env.FIREPIT_VERSION && process.execPath == opts.nodeBinary) { + if (process.env.FIREPIT_VERSION && process.execPath === opts.nodeBinary) { const requestedMajorNodeVersion = this.getNodeBinary(backend); this.logger.log( "WARN", diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 0119e15dee8..e3b4c17a516 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -103,8 +103,8 @@ interface ProxyTarget extends Object { px.when("incremented", (original) => original["value"] + 1); const obj = px.finalize(); - obj.value == 1; - obj.incremented == 2; + obj.value === 1; + obj.incremented === 2; */ class Proxied { /** diff --git a/src/emulator/functionsEmulatorUtils.ts b/src/emulator/functionsEmulatorUtils.ts index abd7b2c60c0..ed7bcb340a3 100644 --- a/src/emulator/functionsEmulatorUtils.ts +++ b/src/emulator/functionsEmulatorUtils.ts @@ -117,15 +117,15 @@ export function compareVersionStrings(a?: string, b?: string) { const versionA = parseVersionString(a); const versionB = parseVersionString(b); - if (versionA.major != versionB.major) { + if (versionA.major !== versionB.major) { return versionA.major - versionB.major; } - if (versionA.minor != versionB.minor) { + if (versionA.minor !== versionB.minor) { return versionA.minor - versionB.minor; } - if (versionA.patch != versionB.patch) { + if (versionA.patch !== versionB.patch) { return versionA.patch - versionB.patch; } diff --git a/src/emulator/loggingEmulator.ts b/src/emulator/loggingEmulator.ts index 973a8c63652..747f95246b7 100644 --- a/src/emulator/loggingEmulator.ts +++ b/src/emulator/loggingEmulator.ts @@ -119,7 +119,7 @@ class WebSocketTransport extends TransportStream { const splat = [info.message, ...(info[SPLAT] || [])] .map((value) => { - if (typeof value == "string") { + if (typeof value === "string") { try { bundle.data = { ...bundle.data, ...JSON.parse(value) }; return null; diff --git a/src/emulator/pubsubEmulator.ts b/src/emulator/pubsubEmulator.ts index c72ed9b274d..b8747f5acdf 100644 --- a/src/emulator/pubsubEmulator.ts +++ b/src/emulator/pubsubEmulator.ts @@ -128,7 +128,7 @@ export class PubsubEmulator implements EmulatorInstance { } private ensureFunctionsClient() { - if (this.client != undefined) return; + if (this.client !== undefined) return; const funcEmulator = EmulatorRegistry.get(Emulators.FUNCTIONS); if (!funcEmulator) { diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 1c74e531f7d..5c02e1b30fd 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -164,11 +164,11 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } let isGZipped = false; - if (md.contentEncoding == "gzip") { + if (md.contentEncoding === "gzip") { isGZipped = true; } - if (req.query.alt == "media") { + if (req.query.alt === "media") { let data = storageLayer.getBytes(req.params.bucketId, req.params.objectId); if (!data) { res.sendStatus(404); @@ -322,7 +322,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { let md: StoredFileMetadata | undefined; if (createTokenParam) { - if (createTokenParam != "true") { + if (createTokenParam !== "true") { res.sendStatus(400); return; } @@ -352,7 +352,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { const name = req.query.name.toString(); const uploadType = req.header("x-goog-upload-protocol"); - if (uploadType == "multipart") { + if (uploadType === "multipart") { const contentType = req.header("content-type"); if (!contentType || !contentType.startsWith("multipart/related")) { res.sendStatus(400); @@ -421,7 +421,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }); } - if (md.downloadTokens.length == 0) { + if (md.downloadTokens.length === 0) { md.addDownloadToken(); } @@ -435,7 +435,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return; } - if (uploadCommand == "start") { + if (uploadCommand === "start") { let objectContentType = req.header("x-goog-upload-header-content-type") || req.header("x-goog-upload-content-type"); @@ -480,7 +480,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } const uploadId = req.query.upload_id.toString(); - if (uploadCommand == "query") { + if (uploadCommand === "query") { const upload = storageLayer.queryUpload(uploadId); if (!upload) { res.sendStatus(400); @@ -492,7 +492,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return; } - if (uploadCommand == "cancel") { + if (uploadCommand === "cancel") { const upload = storageLayer.queryUpload(uploadId); if (upload) { const cancelled = storageLayer.cancelUpload(upload); @@ -563,7 +563,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { const uploadedFile = storageLayer.finalizeUpload(upload); const md = uploadedFile.metadata; - if (md.downloadTokens.length == 0) { + if (md.downloadTokens.length === 0) { md.addDownloadToken(); } diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index 68883f35546..ec95847c29b 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -44,7 +44,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return; } - if (req.query.alt == "media") { + if (req.query.alt === "media") { return sendFileBytes(md, storageLayer, req, res); } @@ -184,11 +184,11 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return; } - if (req.query.uploadType == "resumable") { + if (req.query.uploadType === "resumable") { const upload = storageLayer.startUpload(req.params.bucketId, name, contentType, req.body); const emulatorInfo = EmulatorRegistry.getInfo(Emulators.STORAGE); - if (emulatorInfo == undefined) { + if (emulatorInfo === undefined) { res.sendStatus(500); return; } @@ -287,7 +287,7 @@ function sendFileBytes( return; } - const isGZipped = md.contentEncoding == "gzip"; + const isGZipped = md.contentEncoding === "gzip"; if (isGZipped) { data = gunzipSync(data); } diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index ae7c7fa37de..9e87f8ba790 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -146,7 +146,7 @@ export class StorageLayer { } async listBuckets(): Promise { - if (this._buckets.size == 0) { + if (this._buckets.size === 0) { let adminSdkConfig = await getProjectAdminSdkConfigOrCached(this._projectId); if (!adminSdkConfig) { adminSdkConfig = constructDefaultAdminSdkConfig(this._projectId); @@ -272,7 +272,7 @@ export class StorageLayer { const file = this._files.get(filePath); - if (file == undefined) { + if (file === undefined) { return false; } else { this._files.delete(filePath); @@ -365,7 +365,7 @@ export class StorageLayer { let items = []; const prefixes = new Set(); for (const [, file] of this._files) { - if (file.metadata.bucket != bucket) { + if (file.metadata.bucket !== bucket) { continue; } @@ -380,7 +380,7 @@ export class StorageLayer { } const startAtIndex = name.indexOf(delimiter); - if (startAtIndex == -1) { + if (startAtIndex === -1) { if (!file.metadata.name.endsWith("/")) { items.push(file.metadata.name); } @@ -392,8 +392,8 @@ export class StorageLayer { items.sort(); if (pageToken) { - const idx = items.findIndex((v) => v == pageToken); - if (idx != -1) { + const idx = items.findIndex((v) => v === pageToken); + if (idx !== -1) { items = items.slice(idx); } } @@ -436,7 +436,7 @@ export class StorageLayer { let items = []; for (const [, file] of this._files) { - if (file.metadata.bucket != bucket) { + if (file.metadata.bucket !== bucket) { continue; } @@ -455,8 +455,8 @@ export class StorageLayer { items.sort(); if (pageToken) { - const idx = items.findIndex((v) => v == pageToken); - if (idx != -1) { + const idx = items.findIndex((v) => v === pageToken); + if (idx !== -1) { items = items.slice(idx); } } diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index a7d3b0565d9..0dafd213463 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -55,14 +55,14 @@ export class StorageEmulator implements EmulatorInstance { await this._rulesRuntime.start(this.args.auto_download); this._app = await createApp(this.args.projectId, this); - if (typeof this.args.rules == "string") { + if (typeof this.args.rules === "string") { const rulesFile = this.args.rules; this.updateRulesSource(rulesFile); } else { this._rulesetSource = this.args.rules; } - if (!this._rulesetSource || this._rulesetSource.files.length == 0) { + if (!this._rulesetSource || this._rulesetSource.files.length === 0) { throw new FirebaseError("Can not initialize Storage emulator without a rules source / file."); } else if (this._rulesetSource.files.length > 1) { throw new FirebaseError( diff --git a/src/emulator/storage/metadata.ts b/src/emulator/storage/metadata.ts index 24e04c560da..a38da2865bf 100644 --- a/src/emulator/storage/metadata.ts +++ b/src/emulator/storage/metadata.ts @@ -222,9 +222,9 @@ export class StoredFileMetadata { return; } - const remainingTokens = this.downloadTokens.filter((t) => t != token); + const remainingTokens = this.downloadTokens.filter((t) => t !== token); this.downloadTokens = remainingTokens; - if (remainingTokens.length == 0) { + if (remainingTokens.length === 0) { // if empty after deleting, always add a new token. this.addDownloadToken(); } diff --git a/src/emulator/storage/rules/runtime.ts b/src/emulator/storage/rules/runtime.ts index 9934f20a285..1ebdefcb3a9 100644 --- a/src/emulator/storage/rules/runtime.ts +++ b/src/emulator/storage/rules/runtime.ts @@ -51,7 +51,7 @@ export class StorageRulesetInstance { permitted?: boolean; issues: StorageRulesIssues; }> { - if (opts.method == RulesetOperationMethod.LIST && this.rulesVersion < 2) { + if (opts.method === RulesetOperationMethod.LIST && this.rulesVersion < 2) { const issues = new StorageRulesIssues(); issues.warnings.push( "Permission denied. List operations are only allowed for rules_version='2'." @@ -169,7 +169,7 @@ export class StorageRulesRuntime { this._childprocess.stdout?.on("data", (buf: Buffer) => { const serializedRuntimeActionResponse = buf.toString("UTF8").trim(); - if (serializedRuntimeActionResponse != "") { + if (serializedRuntimeActionResponse !== "") { let rap; try { rap = JSON.parse(serializedRuntimeActionResponse) as RuntimeActionResponse; @@ -317,16 +317,16 @@ export class StorageRulesRuntime { } function toExpressionValue(obj: any): ExpressionValue { - if (typeof obj == "string") { + if (typeof obj === "string") { return { string_value: obj }; } - if (typeof obj == "boolean") { + if (typeof obj === "boolean") { return { bool_value: obj }; } - if (typeof obj == "number") { - if (Math.floor(obj) == obj) { + if (typeof obj === "number") { + if (Math.floor(obj) === obj) { return { int_value: obj }; } else { return { float_value: obj }; @@ -361,7 +361,7 @@ function toExpressionValue(obj: any): ExpressionValue { }; } - if (typeof obj == "object") { + if (typeof obj === "object") { const fields: { [s: string]: ExpressionValue } = {}; Object.keys(obj).forEach((key: string) => { fields[key] = toExpressionValue(obj[key]); diff --git a/src/error.ts b/src/error.ts index 27faf34fee8..e965fa51408 100644 --- a/src/error.ts +++ b/src/error.ts @@ -53,7 +53,7 @@ export function isBillingError(e: { return !!e.context?.body?.error?.details?.find((d) => { return ( d.violations?.find((v) => v.type === "serviceusage/billing-enabled") || - d.reason == "UREQ_PROJECT_BILLING_NOT_FOUND" + d.reason === "UREQ_PROJECT_BILLING_NOT_FOUND" ); }); } diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index ba1b0e441f7..40a33ce9f97 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -21,7 +21,7 @@ export function checkResponse(response: string, spec: Param): boolean { let valid = true; let responses: string[]; - if (spec.required && (response == "" || response == undefined)) { + if (spec.required && (response === "" || response === undefined)) { utils.logWarning(`Param ${spec.param} is required, but no value was provided.`); return false; } diff --git a/src/extensions/changelog.ts b/src/extensions/changelog.ts index ae2d88562b4..28cdde73d53 100644 --- a/src/extensions/changelog.ts +++ b/src/extensions/changelog.ts @@ -84,7 +84,9 @@ export function breakingChangesInUpdate(versionsInUpdate: string[]): string[] { for (let i = 1; i < semvers.length; i++) { const hasMajorBump = semvers[i - 1].major < semvers[i].major; const hasMinorBumpInPreview = - semvers[i - 1].major == 0 && semvers[i].major == 0 && semvers[i - 1].minor < semvers[i].minor; + semvers[i - 1].major === 0 && + semvers[i].major === 0 && + semvers[i - 1].minor < semvers[i].minor; if (hasMajorBump || hasMinorBumpInPreview) { breakingVersions.push(semvers[i].raw); } diff --git a/src/extensions/checkProjectBilling.ts b/src/extensions/checkProjectBilling.ts index 09d580b3394..2edbc81835b 100644 --- a/src/extensions/checkProjectBilling.ts +++ b/src/extensions/checkProjectBilling.ts @@ -68,7 +68,7 @@ Please select the one that you would like to associate with this project:`, const billingURL = `https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`; billingEnabled = await openBillingAccount(projectId, billingURL, true); } else { - const billingAccount = accounts.find((a) => a.displayName == answer); + const billingAccount = accounts.find((a) => a.displayName === answer); billingEnabled = await cloudbilling.setBillingAccount(projectId, billingAccount!.name); } diff --git a/src/extensions/displayExtensionInfo.ts b/src/extensions/displayExtensionInfo.ts index 483dd74de6c..8b618d27e22 100644 --- a/src/extensions/displayExtensionInfo.ts +++ b/src/extensions/displayExtensionInfo.ts @@ -250,7 +250,7 @@ export async function displayUpdateChangesRequiringConfirmation(args: { } function compareResources(resource1: extensionsApi.Resource, resource2: extensionsApi.Resource) { - return resource1.name == resource2.name && resource1.type == resource2.type; + return resource1.name === resource2.name && resource1.type === resource2.type; } function getResourceReadableName(resource: extensionsApi.Resource): string { diff --git a/src/extensions/emulator/optionsHelper.ts b/src/extensions/emulator/optionsHelper.ts index 27ffe24a67a..8a36a8ed498 100644 --- a/src/extensions/emulator/optionsHelper.ts +++ b/src/extensions/emulator/optionsHelper.ts @@ -197,7 +197,7 @@ function getFunctionSourceDirectory(functionResources: Resource[]): string { } if (!sourceDirectory) { sourceDirectory = dir; - } else if (sourceDirectory != dir) { + } else if (sourceDirectory !== dir) { throw new FirebaseError( `Found function resources with different sourceDirectories: '${sourceDirectory}' and '${dir}'. The extensions emulator only supports a single sourceDirectory.` ); diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index e6b37b9494c..4ffc431a6d6 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -578,7 +578,7 @@ export async function getPublisherProfile( ): Promise { const res = await apiClient.get(`/projects/${projectId}/publisherProfile`, { queryParams: - publisherId == undefined + publisherId === undefined ? undefined : { publisherId, diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index cb6af813f88..1967e0e5ce2 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -159,7 +159,7 @@ export function populateDefaultParams(paramVars: Record, paramSp for (const param of paramSpecs) { if (!paramVars[param.param]) { - if (param.default != undefined && param.required) { + if (param.default !== undefined && param.required) { newParams[param.param] = param.default; } else if (param.required) { throw new FirebaseError( @@ -268,7 +268,7 @@ export function validateSpec(spec: any) { }. Valid types are ${_.values(SpecParamType).join(", ")}` ); } - if (!param.type || param.type == SpecParamType.STRING) { + if (!param.type || param.type === SpecParamType.STRING) { // ParamType defaults to STRING if (param.options) { errors.push( @@ -280,7 +280,7 @@ export function validateSpec(spec: any) { } if ( param.type && - (param.type == SpecParamType.SELECT || param.type == SpecParamType.MULTISELECT) + (param.type === SpecParamType.SELECT || param.type === SpecParamType.MULTISELECT) ) { if (param.validationRegex) { errors.push( @@ -297,7 +297,7 @@ export function validateSpec(spec: any) { ); } for (const opt of param.options || []) { - if (opt.value == undefined) { + if (opt.value === undefined) { errors.push( `Option for param${ param.param ? ` ${param.param}` : "" @@ -306,7 +306,7 @@ export function validateSpec(spec: any) { } } } - if (param.type && param.type == SpecParamType.SELECTRESOURCE) { + if (param.type && param.type === SpecParamType.SELECTRESOURCE) { if (!param.resourceType) { errors.push( `Param${param.param ? ` ${param.param}` : ""} must have resourceType because it is type ${ @@ -389,7 +389,7 @@ export async function publishExtensionVersionFromLocalSource(args: { force: boolean; }): Promise { const extensionSpec = await getLocalExtensionSpec(args.rootDirectory); - if (extensionSpec.name != args.extensionId) { + if (extensionSpec.name !== args.extensionId) { throw new FirebaseError( `Extension ID '${clc.bold( args.extensionId @@ -502,7 +502,7 @@ export async function publishExtensionVersionFromLocalSource(args: { publishSpinner.succeed(` Successfully published ${clc.bold(ref)}`); } catch (err: any) { publishSpinner.fail(); - if (err.status == 404) { + if (err.status === 404) { throw new FirebaseError( marked( `Couldn't find publisher ID '${clc.bold( diff --git a/src/extensions/metricsUtils.ts b/src/extensions/metricsUtils.ts index 1667b89d141..cdb4c17e5ca 100644 --- a/src/extensions/metricsUtils.ts +++ b/src/extensions/metricsUtils.ts @@ -12,7 +12,7 @@ export function parseTimeseriesResponse(series: TimeSeriesResponse): Array= 28 && s.points[27].value.int64Value != undefined) { + if (s.points.length >= 28 && s.points[27].value.int64Value !== undefined) { value28dAgo = parseBucket(s.points[27].value.int64Value); } - if (s.points.length >= 7 && s.points[6].value.int64Value != undefined) { + if (s.points.length >= 7 && s.points[6].value.int64Value !== undefined) { value7dAgo = parseBucket(s.points[6].value.int64Value); } - if (s.points.length >= 1 && s.points[0].value.int64Value != undefined) { + if (s.points.length >= 1 && s.points[0].value.int64Value !== undefined) { valueToday = parseBucket(s.points[0].value.int64Value); } diff --git a/src/extensions/refs.ts b/src/extensions/refs.ts index 232da321d1f..40adf890353 100644 --- a/src/extensions/refs.ts +++ b/src/extensions/refs.ts @@ -40,7 +40,7 @@ export function parse(refOrName: string): Ref { function parseRef(ref: string): Ref | undefined { const parts = refRegex.exec(ref); // Exec additionally returns original string, index, & input values. - if (parts && (parts.length == 5 || parts.length == 7)) { + if (parts && (parts.length === 5 || parts.length === 7)) { const publisherId = parts[1]; const extensionId = parts[2]; const version = parts[4]; diff --git a/src/extensions/secretsUtils.ts b/src/extensions/secretsUtils.ts index 66e008021f9..6c1b170c274 100644 --- a/src/extensions/secretsUtils.ts +++ b/src/extensions/secretsUtils.ts @@ -14,7 +14,7 @@ export async function ensureSecretManagerApiEnabled(options: any): Promise } export function usesSecrets(spec: extensionsApi.ExtensionSpec): boolean { - return spec.params && !!spec.params.find((p) => p.type == extensionsApi.ParamType.SECRET); + return spec.params && !!spec.params.find((p) => p.type === extensionsApi.ParamType.SECRET); } export async function grantFirexServiceAgentSecretAdminRole( @@ -54,7 +54,7 @@ export function getActiveSecrets( params: Record ): string[] { return spec.params - .map((p) => (p.type == extensionsApi.ParamType.SECRET ? params[p.param] : "")) + .map((p) => (p.type === extensionsApi.ParamType.SECRET ? params[p.param] : "")) .filter((pv) => !!pv); } @@ -66,7 +66,7 @@ export function getSecretLabels(instanceId: string): Record { export function prettySecretName(secretResourceName: string): string { const nameTokens = secretResourceName.split("/"); - if (nameTokens.length != 4 && nameTokens.length != 6) { + if (nameTokens.length !== 4 && nameTokens.length !== 6) { // not a familiar format, return as is logger.debug(`unable to parse secret secretResourceName: ${secretResourceName}`); return secretResourceName; diff --git a/src/functional.ts b/src/functional.ts index acc9762ee9c..97de8d3d117 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -67,7 +67,7 @@ export function reduceFlat(accum: T[] | undefined, next: unknown): T[] * [...zip([1, 2, 3], ['a', 'b', 'c'])] = [[1, 'a], [2, 'b'], [3, 'c']] */ export function* zip(left: T[], right: V[]): Generator<[T, V]> { - if (left.length != right.length) { + if (left.length !== right.length) { throw new Error("Cannot zip between two lists of differen lengths"); } for (let i = 0; i < left.length; i++) { diff --git a/src/functions/env.ts b/src/functions/env.ts index b92625dece8..8733578ebd9 100644 --- a/src/functions/env.ts +++ b/src/functions/env.ts @@ -254,7 +254,7 @@ export function loadUserEnvs({ isEmulator, }: UserEnvsOpts): Record { const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator); - if (envFiles.length == 0) { + if (envFiles.length === 0) { return {}; } diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 137f8d5cf28..e731f55f28f 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -290,7 +290,7 @@ export async function setInvokerCreate( fnName: string, invoker: string[] ): Promise { - if (invoker.length == 0) { + if (invoker.length === 0) { throw new FirebaseError("Invoker cannot be an empty array"); } const invokerMembers = proto.getInvokerMembers(invoker, projectId); @@ -318,7 +318,7 @@ export async function setInvokerUpdate( fnName: string, invoker: string[] ): Promise { - if (invoker.length == 0) { + if (invoker.length === 0) { throw new FirebaseError("Invoker cannot be an empty array"); } const invokerMembers = proto.getInvokerMembers(invoker, projectId); @@ -542,7 +542,7 @@ export function functionFromEndpoint( endpoint: backend.Endpoint, sourceUploadUrl: string ): Omit { - if (endpoint.platform != "gcfv1") { + if (endpoint.platform !== "gcfv1") { throw new FirebaseError( "Trying to create a v1 CloudFunction with v2 API. This should never happen" ); diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 8f9097afd9a..01051b08594 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -309,7 +309,7 @@ async function listFunctionsInternal( let pageToken = ""; while (true) { const url = `projects/${projectId}/locations/${region}/functions`; - const opts = pageToken == "" ? {} : { queryParams: { pageToken } }; + const opts = pageToken === "" ? {} : { queryParams: { pageToken } }; const res = await client.get(url, opts); functions.push(...(res.body.functions || [])); for (const region of res.body.unreachable || []) { @@ -369,7 +369,7 @@ export async function deleteFunction(cloudFunction: string): Promise } export function functionFromEndpoint(endpoint: backend.Endpoint, source: StorageSource) { - if (endpoint.platform != "gcfv2") { + if (endpoint.platform !== "gcfv2") { throw new FirebaseError( "Trying to create a v2 CloudFunction with v1 API. This should never happen" ); diff --git a/src/gcp/cloudtasks.ts b/src/gcp/cloudtasks.ts index 25317765e73..67cb0dc07a4 100644 --- a/src/gcp/cloudtasks.ts +++ b/src/gcp/cloudtasks.ts @@ -173,7 +173,7 @@ export async function setEnqueuer( const invokerMembers = proto.getInvokerMembers(invoker, project); while (true) { const policy: iam.Policy = { - bindings: existing.bindings.filter((binding) => binding.role != ENQUEUER_ROLE), + bindings: existing.bindings.filter((binding) => binding.role !== ENQUEUER_ROLE), etag: existing.etag, version: existing.version, }; diff --git a/src/gcp/docker.ts b/src/gcp/docker.ts index d0b324dd990..623f14c3e8d 100644 --- a/src/gcp/docker.ts +++ b/src/gcp/docker.ts @@ -104,7 +104,7 @@ export class Client { if (!response.body) { return; } - if (response.body.errors?.length != 0) { + if (response.body.errors?.length !== 0) { throw new FirebaseError(`Failed to delete tag ${tag} at path ${path}`, { children: response.body.errors, }); @@ -116,7 +116,7 @@ export class Client { if (!response.body) { return; } - if (response.body.errors?.length != 0) { + if (response.body.errors?.length !== 0) { throw new FirebaseError(`Failed to delete image ${digest} at path ${path}`, { children: response.body.errors, }); diff --git a/src/gcp/run.ts b/src/gcp/run.ts index 2ab5b39a550..9142ab37985 100644 --- a/src/gcp/run.ts +++ b/src/gcp/run.ts @@ -192,7 +192,7 @@ export async function setInvokerCreate( invoker: string[], httpClient: Client = client // for unit testing ) { - if (invoker.length == 0) { + if (invoker.length === 0) { throw new FirebaseError("Invoker cannot be an empty array"); } const invokerMembers = proto.getInvokerMembers(invoker, projectId); @@ -222,7 +222,7 @@ export async function setInvokerUpdate( invoker: string[], httpClient: Client = client // for unit testing ) { - if (invoker.length == 0) { + if (invoker.length === 0) { throw new FirebaseError("Invoker cannot be an empty array"); } const invokerMembers = proto.getInvokerMembers(invoker, projectId); diff --git a/src/hosting/api.ts b/src/hosting/api.ts index deab6953025..2e5241d5079 100644 --- a/src/hosting/api.ts +++ b/src/hosting/api.ts @@ -485,7 +485,7 @@ export async function removeAuthDomain(project: string, url: string): Promise domain != targetDomain); + const authDomains = domains.filter((domain: string) => domain !== targetDomain); return updateAuthDomains(project, authDomains); } diff --git a/src/hosting/proxy.ts b/src/hosting/proxy.ts index cecd4620558..a107ffc8442 100644 --- a/src/hosting/proxy.ts +++ b/src/hosting/proxy.ts @@ -75,7 +75,7 @@ export function proxyRequestHandler(url: string, rewriteIdentifier: string): Req continue; } const value = req.headers[key]; - if (value == undefined) { + if (value === undefined) { headers.delete(key); } else if (Array.isArray(value)) { headers.delete(key); @@ -155,7 +155,7 @@ export function proxyRequestHandler(url: string, rewriteIdentifier: string): Req const locationURL = new URL(location); // Only assume we can fix the location header if the origin of the // "fixed" header is the same as the origin of the outbound request. - if (locationURL.origin == u.origin) { + if (locationURL.origin === u.origin) { const unborkedLocation = location.replace(locationURL.origin, ""); proxyRes.response.headers.set("location", unborkedLocation); } diff --git a/src/init/features/account.ts b/src/init/features/account.ts index be999547d0d..ee239817bba 100644 --- a/src/init/features/account.ts +++ b/src/init/features/account.ts @@ -38,7 +38,7 @@ async function promptForAccount() { choices, }); - if (emailChoice == "__add__") { + if (emailChoice === "__add__") { const newAccount = await loginAdditionalAccount(/* useLocalhost= */ true); if (!newAccount) { throw new FirebaseError("Failed to add new account", { exit: 1 }); diff --git a/src/management/database.ts b/src/management/database.ts index 60a603f1bf6..8d3015d48c6 100644 --- a/src/management/database.ts +++ b/src/management/database.ts @@ -276,7 +276,7 @@ function convertDatabaseInstance(serverInstance: any): DatabaseInstance { throw new FirebaseError(`DatabaseInstance response is missing field "name"`); } const m = serverInstance.name.match(INSTANCE_RESOURCE_NAME_REGEX); - if (!m || m.length != 4) { + if (!m || m.length !== 4) { throw new FirebaseError( `Error parsing instance resource name: ${serverInstance.name}, matches: ${m}` ); diff --git a/src/rulesDeploy.ts b/src/rulesDeploy.ts index 322cb3397e5..2924553af1d 100644 --- a/src/rulesDeploy.ts +++ b/src/rulesDeploy.ts @@ -192,7 +192,7 @@ export class RulesDeploy { * @param filename The filename to release. * @param resourceName The release name to release these as. * @param subResourceName An optional sub-resource name to append to the - * release name. This is required if resourceName == FIREBASE_STORAGE. + * release name. This is required if resourceName === FIREBASE_STORAGE. */ async release( filename: string, diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index e6fe910411f..ac55749cded 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -1084,7 +1084,7 @@ describe("Fabricator", () => { // Will fail when it hits actual API calls const summary = await fab.applyPlan(plan); - const ep1Result = summary.results.find((r) => r.endpoint.region == ep1.region); + const ep1Result = summary.results.find((r) => r.endpoint.region === ep1.region); expect(ep1Result?.error).to.be.instanceOf(reporter.DeploymentError); expect(ep1Result?.error?.message).to.match(/create function/); diff --git a/src/test/emulators/storage.rules.spec.ts b/src/test/emulators/storage.rules.spec.ts index 5dc97f8688e..125211d8962 100644 --- a/src/test/emulators/storage.rules.spec.ts +++ b/src/test/emulators/storage.rules.spec.ts @@ -361,7 +361,7 @@ async function testIfPermitted( runtimeVariableOverrides ); - if (permitted == undefined) { + if (permitted === undefined) { throw new Error(JSON.stringify(issues, undefined, 2)); } diff --git a/src/test/management/database.spec.ts b/src/test/management/database.spec.ts index fa50128d67e..56d318e42b6 100644 --- a/src/test/management/database.spec.ts +++ b/src/test/management/database.spec.ts @@ -51,10 +51,10 @@ const INSTANCE_RESPONSE_EUROPE_WEST1 = { }; function generateDatabaseUrl(instanceName: string, location: DatabaseLocation): string { - if (location == DatabaseLocation.ANY) { + if (location === DatabaseLocation.ANY) { throw new Error("can't generate url for any location"); } - if (location == DatabaseLocation.US_CENTRAL1) { + if (location === DatabaseLocation.US_CENTRAL1) { return `https://${instanceName}.firebaseio.com`; } return `https://${instanceName}.${location}.firebasedatabase.app`; diff --git a/src/utils.ts b/src/utils.ts index 34e06f36589..d6d1fc96eab 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -477,7 +477,7 @@ export function setupLoggers() { level: "info", format: winston.format.printf((info) => [info.message, ...(info[SPLAT] || [])] - .filter((chunk) => typeof chunk == "string") + .filter((chunk) => typeof chunk === "string") .join(" ") ), }) From 23dc81fd065f4cfaa172cff535cafdd9a8c11c23 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 2 Mar 2022 19:52:49 +0000 Subject: [PATCH 0120/1699] 10.2.2 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c99c88be21c..edfd93c344a 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.2.1", + "version": "10.2.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.2.1", + "version": "10.2.2", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 03b0b6acbc9..e546e5a0dbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.2.1", + "version": "10.2.2", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 91c833b12052a137f02713d6c4ac79631404400a Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 2 Mar 2022 19:53:12 +0000 Subject: [PATCH 0121/1699] [firebase-release] Removed change log and reset repo after 10.2.2 release --- CHANGELOG.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b1f1ed30f..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +0,0 @@ -- Updates reserved environment variables for CF3 to include 'EVENTARC_CLOUD_EVENT_SOURCE' (#4196). -- Fixes arg order for `firebase emulators:start --only storage` (#4195). -- Fixes iOS auth for resumable uploads in Storage Emulator (#4184). -- Fixes Storage Emulator crash on iOS auth error for resumable uploads (#4210). -- Fixes bug where environment variable for gen 2 functions weren't updated on deploy (#4209). -- Fixes an issue in the storage emulator where a file upload would trigger functions with a metadata update handler (#4213). -- Fixes Storage Emulator rules resource evaluation (#4214). -- Fixes bug where securityLevel is overwritten on https function re-deploys (#4208). -- Fixes bug where functions emulator ignored functions.runtime option in firebase.json (#4207). -- Fixes bug where functions emulator triggered wrong functions when started with --inspect-functions flag (#4232). -- Updates functions init template to use latest versions of dependencies of functions init (#4177). From 057557cd80d2d8b0349c5ef01ccdeb4b9f143cc5 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 2 Mar 2022 14:26:42 -0800 Subject: [PATCH 0122/1699] Add check for unemulated trigger types (#4241) * Add check for unemulated trigger types * no force yet * PR fixes * format --- src/config.ts | 6 +- src/emulator/controller.ts | 6 +- src/emulator/extensions/validation.ts | 45 +++++ src/emulator/extensionsEmulator.ts | 35 +++- src/emulator/functionsEmulatorShared.ts | 2 +- .../emulators/extensions/validation.spec.ts | 169 +++++++++++++++++- 6 files changed, 249 insertions(+), 14 deletions(-) diff --git a/src/config.ts b/src/config.ts index 63fc757b5b2..edfe7fff985 100644 --- a/src/config.ts +++ b/src/config.ts @@ -46,9 +46,9 @@ export class Config { * @param src incoming firebase.json source, parsed by not validated. * @param options command-line options. */ - constructor(src: any, options: any) { - this.options = options || {}; - this.projectDir = options.projectDir || detectProjectRoot(options); + constructor(src: any, options: any = {}) { + this.options = options; + this.projectDir = this.options.projectDir || detectProjectRoot(this.options); this._src = src; if (this._src.firebase) { diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index c7ae31f8092..1cdcc6e90f2 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -458,7 +458,11 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) extensions: options.config.get("extensions"), }); const extensionsBackends = await extensionEmulator.getExtensionBackends(); - emulatableBackends.push(...extensionsBackends); + const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers( + options, + extensionsBackends + ); + emulatableBackends.push(...filteredExtensionsBackends); // Log the command for analytics void track("Emulator Run", Emulators.EXTENSIONS); diff --git a/src/emulator/extensions/validation.ts b/src/emulator/extensions/validation.ts index 28d7f637d58..1c31ffd2dc1 100644 --- a/src/emulator/extensions/validation.ts +++ b/src/emulator/extensions/validation.ts @@ -1,5 +1,12 @@ import * as planner from "../../deploy/extensions/planner"; +import { shouldStart } from "../controller"; +import { Constants } from "../constants"; import { check } from "../../ensureApiEnabled"; +import { getFunctionService } from "../functionsEmulatorShared"; +import { EmulatableBackend } from "../functionsEmulator"; +import { ParsedTriggerDefinition } from "../functionsEmulatorShared"; +import { Emulators } from "../types"; +import { Options } from "../../options"; const EMULATED_APIS = [ "storage-component.googleapis.com", @@ -42,3 +49,41 @@ export async function getUnemulatedAPIs( } return Object.values(unemulatedAPIs); } + +/** + * Checks a EmulatableBackend for any functions that trigger off of emulators that are not running or not implemented. + * @param backend + */ +export function checkForUnemulatedTriggerTypes( + backend: EmulatableBackend, + options: Options +): string[] { + const triggers = backend.predefinedTriggers ?? []; + const unemulatedTriggers = triggers + .filter((definition: ParsedTriggerDefinition) => { + if (definition.httpsTrigger) { + // HTTPS triggers can always be emulated. + return false; + } + if (definition.eventTrigger) { + const service: string = getFunctionService(definition); + switch (service) { + case Constants.SERVICE_FIRESTORE: + return !shouldStart(options, Emulators.FIRESTORE); + case Constants.SERVICE_REALTIME_DATABASE: + return !shouldStart(options, Emulators.DATABASE); + case Constants.SERVICE_PUBSUB: + return !shouldStart(options, Emulators.PUBSUB); + case Constants.SERVICE_AUTH: + return !shouldStart(options, Emulators.AUTH); + case Constants.SERVICE_STORAGE: + return !shouldStart(options, Emulators.STORAGE); + default: + return true; + } + } + }) + .map((definition) => Constants.getServiceName(getFunctionService(definition))); + // Remove duplicates + return [...new Set(unemulatedTriggers)]; +} diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 83d1dc5b7f6..4c997561311 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -6,6 +6,7 @@ import Table = require("cli-table"); import { spawnSync } from "child_process"; import * as planner from "../deploy/extensions/planner"; +import { Options } from "../options"; import { FirebaseError } from "../error"; import { toExtensionVersionRef } from "../extensions/refs"; import { downloadExtensionVersion } from "./download"; @@ -13,7 +14,7 @@ import { EmulatableBackend } from "./functionsEmulator"; import { getExtensionFunctionInfo } from "../extensions/emulator/optionsHelper"; import { EmulatorLogger } from "./emulatorLogger"; import { Emulators } from "./types"; -import { getUnemulatedAPIs } from "./extensions/validation"; +import { checkForUnemulatedTriggerTypes, getUnemulatedAPIs } from "./extensions/validation"; import { enableApiURI } from "../ensureApiEnabled"; import { shortenUrl } from "../shortenUrl"; @@ -212,4 +213,36 @@ export class ExtensionsEmulator { ); } } + + /** + * Filters out Extension backends that include any unemulated triggers. + * @param backends a list of backends to filter + * @return a list of backends that include only emulated triggers. + */ + public filterUnemulatedTriggers( + options: Options, + backends: EmulatableBackend[] + ): EmulatableBackend[] { + let foundUnemulatedTrigger = false; + const filteredBackends = backends.filter((backend) => { + const unemulatedServices = checkForUnemulatedTriggerTypes(backend, options); + if (unemulatedServices.length) { + foundUnemulatedTrigger = true; + const msg = ` ignored becuase it includes ${unemulatedServices.join( + ", " + )} triggered functions, and the ${unemulatedServices.join( + ", " + )} emulator does not exist or is not running.`; + this.logger.logLabeled("WARN", `extensions[${backend.extensionInstanceId}]`, msg); + } + return unemulatedServices.length === 0; + }); + if (foundUnemulatedTrigger) { + const msg = + "No Cloud Functions for these instances will be emulated, because partially emulating an Extension can lead to unexpected behavior. "; + // TODO(joehanley): "To partially emulate these Extension instance anyway, rerun this command with --force"; + this.logger.log("WARN", msg); + } + return filteredBackends; + } } diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index d5dd7d0dc98..8cf751a52b2 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -265,7 +265,7 @@ export function getTemporarySocketPath(pid: number, cwd: string): string { } } -export function getFunctionService(def: EmulatedTriggerDefinition): string { +export function getFunctionService(def: ParsedTriggerDefinition): string { if (def.eventTrigger) { return def.eventTrigger.service ?? getServiceFromEventType(def.eventTrigger.eventType); } diff --git a/src/test/emulators/extensions/validation.spec.ts b/src/test/emulators/extensions/validation.spec.ts index 457d266626a..e3126b85b41 100644 --- a/src/test/emulators/extensions/validation.spec.ts +++ b/src/test/emulators/extensions/validation.spec.ts @@ -1,11 +1,32 @@ import { expect } from "chai"; import * as sinon from "sinon"; -import * as utils from "../../../emulator/extensions/validation"; +import * as validation from "../../../emulator/extensions/validation"; import * as ensureApiEnabled from "../../../ensureApiEnabled"; +import * as controller from "../../../emulator/controller"; import { InstanceSpec } from "../../../deploy/extensions/planner"; +import { EmulatableBackend } from "../../../emulator/functionsEmulator"; +import { Emulators } from "../../../emulator/types"; +import { EventTrigger, ParsedTriggerDefinition } from "../../../emulator/functionsEmulatorShared"; +import { Options } from "../../../options"; +import { RC } from "../../../rc"; +import { Config } from "../../../config"; -function getTestInstanceSpecWithAPI(instanceId: string, apiName: string): InstanceSpec { +const TEST_OPTIONS: Options = { + cwd: ".", + configPath: ".", + only: "", + except: "", + force: false, + filteredTargets: [""], + nonInteractive: true, + interactive: false, + json: false, + debug: false, + rc: new RC(), + config: new Config("."), +}; +function fakeInstanceSpecWithAPI(instanceId: string, apiName: string): InstanceSpec { return { instanceId, params: {}, @@ -27,8 +48,31 @@ function getTestInstanceSpecWithAPI(instanceId: string, apiName: string): Instan }; } -describe("ExtensionsEmulator validation utils", () => { - describe(`${utils.getUnemulatedAPIs.name}`, () => { +function getTestEmulatableBackend( + predefinedTriggers: ParsedTriggerDefinition[] +): EmulatableBackend { + return { + functionsDir: ".", + env: {}, + predefinedTriggers, + }; +} + +function getTestParsedTriggerDefinition(args: { + httpsTrigger?: {}; + eventTrigger?: EventTrigger; +}): ParsedTriggerDefinition { + return { + entryPoint: "test", + platform: "gcfv1", + name: "test", + eventTrigger: args.eventTrigger, + httpsTrigger: args.httpsTrigger, + }; +} + +describe("ExtensionsEmulator validation validation", () => { + describe(`${validation.getUnemulatedAPIs.name}`, () => { const testProjectId = "test-project"; const testAPI = "test.googleapis.com"; const sandbox = sinon.createSandbox(); @@ -48,10 +92,10 @@ describe("ExtensionsEmulator validation utils", () => { const instanceId2WithUnemulatedAPI = "unemulated2"; const instanceIdWithEmulatedAPI = "emulated"; - const result = await utils.getUnemulatedAPIs(testProjectId, [ - getTestInstanceSpecWithAPI(instanceIdWithEmulatedAPI, "firestore.googleapis.com"), - getTestInstanceSpecWithAPI(instanceIdWithUnemulatedAPI, testAPI), - getTestInstanceSpecWithAPI(instanceId2WithUnemulatedAPI, testAPI), + const result = await validation.getUnemulatedAPIs(testProjectId, [ + fakeInstanceSpecWithAPI(instanceIdWithEmulatedAPI, "firestore.googleapis.com"), + fakeInstanceSpecWithAPI(instanceIdWithUnemulatedAPI, testAPI), + fakeInstanceSpecWithAPI(instanceId2WithUnemulatedAPI, testAPI), ]); expect(result).to.deep.equal([ @@ -63,4 +107,113 @@ describe("ExtensionsEmulator validation utils", () => { ]); }); }); + + describe(`${validation.checkForUnemulatedTriggerTypes.name}`, () => { + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + const shouldStartStub = sandbox.stub(controller, "shouldStart"); + shouldStartStub.withArgs(sinon.match.any, Emulators.STORAGE).returns(true); + shouldStartStub.withArgs(sinon.match.any, Emulators.DATABASE).returns(true); + shouldStartStub.withArgs(sinon.match.any, Emulators.FIRESTORE).returns(false); + shouldStartStub.withArgs(sinon.match.any, Emulators.AUTH).returns(false); + }); + + afterEach(() => { + sandbox.restore(); + }); + + const tests: { + desc: string; + input: ParsedTriggerDefinition[]; + want: string[]; + }[] = [ + { + desc: "should return trigger types for emulators that are not running", + input: [ + getTestParsedTriggerDefinition({ + eventTrigger: { + resource: "test/{*}", + eventType: "providers/cloud.firestore/eventTypes/document.create", + }, + }), + getTestParsedTriggerDefinition({ + eventTrigger: { + resource: "test", + eventType: "providers/firebase.auth/eventTypes/user.create", + }, + }), + ], + want: ["firestore", "auth"], + }, + { + desc: "should return trigger types that don't have an emulator", + input: [ + getTestParsedTriggerDefinition({ + eventTrigger: { + resource: "test", + eventType: "providers/google.firebase.analytics/eventTypes/event.log", + }, + }), + ], + want: ["analytics"], + }, + { + desc: "should not return duplicates", + input: [ + getTestParsedTriggerDefinition({ + eventTrigger: { + resource: "test/{*}", + eventType: "providers/cloud.firestore/eventTypes/document.create", + }, + }), + getTestParsedTriggerDefinition({ + eventTrigger: { + resource: "test/{*}", + eventType: "providers/cloud.firestore/eventTypes/document.create", + }, + }), + ], + want: ["firestore"], + }, + { + desc: "should not return trigger types for emulators that are running", + input: [ + getTestParsedTriggerDefinition({ + eventTrigger: { + resource: "test/{*}", + eventType: "google.storage.object.finalize", + }, + }), + getTestParsedTriggerDefinition({ + eventTrigger: { + resource: "test/{*}", + eventType: "providers/google.firebase.database/eventTypes/ref.write", + }, + }), + ], + want: [], + }, + { + desc: "should not return trigger types for https triggers", + input: [ + getTestParsedTriggerDefinition({ + httpsTrigger: {}, + }), + ], + want: [], + }, + ]; + + for (const test of tests) { + it(test.desc, () => { + const result = validation.checkForUnemulatedTriggerTypes( + getTestEmulatableBackend(test.input), + TEST_OPTIONS + ); + + expect(result).to.have.members(test.want); + }); + } + }); }); From 69988fa588c6910d150fe353b9647b6f400f15ca Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Thu, 3 Mar 2022 09:10:04 -0500 Subject: [PATCH 0123/1699] Refactor object data and metadata get to StorageLayer (#4231) * Refactor object data and metadata get to StorageLayer * formatting * link * lint * pass rules provider instead of rules to StorageLayer * address comments * lint * comment * comment --- src/emulator/storage/apis/firebase.ts | 128 +++++------------------ src/emulator/storage/errors.ts | 5 + src/emulator/storage/files.ts | 49 ++++++++- src/emulator/storage/index.ts | 6 +- src/emulator/storage/rules/utils.ts | 86 +++++++++++++++ src/test/emulators/storage/files.spec.ts | 58 +++++++++- 6 files changed, 229 insertions(+), 103 deletions(-) create mode 100644 src/emulator/storage/errors.ts create mode 100644 src/emulator/storage/rules/utils.ts diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 5c02e1b30fd..a18d4eab2f4 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -6,47 +6,9 @@ import * as mime from "mime"; import { Request, Response, Router } from "express"; import { StorageEmulator } from "../index"; import { EmulatorRegistry } from "../../registry"; -import { StorageRulesetInstance } from "../rules/runtime"; import { RulesetOperationMethod } from "../rules/types"; - -async function isPermitted(opts: { - ruleset?: StorageRulesetInstance; - file: { - before?: RulesResourceMetadata; - after?: RulesResourceMetadata; - }; - path: string; - method: RulesetOperationMethod; - authorization?: string; -}): Promise { - if (!opts.ruleset) { - EmulatorLogger.forEmulator(Emulators.STORAGE).log( - "WARN", - `Can not process SDK request with no loaded ruleset` - ); - return false; - } - - // Skip auth for UI - if (["Bearer owner", "Firebase owner"].includes(opts.authorization || "")) { - return true; - } - - const { permitted, issues } = await opts.ruleset.verify({ - method: opts.method, - path: opts.path, - file: opts.file, - token: opts.authorization ? opts.authorization.split(" ")[1] : undefined, - }); - - if (issues.exist()) { - issues.all.forEach((warningOrError) => { - EmulatorLogger.forEmulator(Emulators.STORAGE).log("WARN", warningOrError); - }); - } - - return !!permitted; -} +import { isPermitted } from "../rules/utils"; +import { NotFoundError, ForbiddenError } from "../errors"; /** * @param emulator @@ -126,86 +88,54 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }); firebaseStorageAPI.get("/b/:bucketId/o/:objectId", async (req, res) => { - const decodedObjectId = decodeURIComponent(req.params.objectId); - const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/"); - const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId); - - const rulesFiles: { - before?: RulesResourceMetadata; - } = {}; - - if (md) { - rulesFiles.before = md.asRulesResource(); - } - - // Query values are used for GETs from Web SDKs - const isPermittedViaHeader = await isPermitted({ - ruleset: emulator.rules, - method: RulesetOperationMethod.GET, - path: operationPath, - file: rulesFiles, - authorization: req.header("authorization"), - }); - - // Token headers are used for GETs from Mobile SDKs - const isPermittedViaToken = - req.query.token && md && md.downloadTokens.includes(req.query.token.toString()); - - const isRequestPermitted: boolean = isPermittedViaHeader || !!isPermittedViaToken; - - if (!isRequestPermitted) { - res.sendStatus(403); - return; - } - - if (!md) { - res.sendStatus(404); - return; + let metadata: StoredFileMetadata; + let data: Buffer; + try { + // Both object data and metadata get can use the same handler since they share auth logic. + ({ metadata, data } = await storageLayer.handleGetObject({ + bucketId: req.params.bucketId, + decodedObjectId: decodeURIComponent(req.params.objectId), + authorization: req.header("authorization"), + downloadToken: req.query.token?.toString(), + })); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } else if (err instanceof ForbiddenError) { + return res.sendStatus(403); + } + throw err; } - let isGZipped = false; - if (md.contentEncoding === "gzip") { - isGZipped = true; + if (!metadata!.downloadTokens.length) { + metadata!.addDownloadToken(); } + // Object data request if (req.query.alt === "media") { - let data = storageLayer.getBytes(req.params.bucketId, req.params.objectId); - if (!data) { - res.sendStatus(404); - return; - } - + const isGZipped = metadata.contentEncoding === "gzip"; if (isGZipped) { data = gunzipSync(data); } - res.setHeader("Accept-Ranges", "bytes"); - res.setHeader("Content-Type", md.contentType); - setObjectHeaders(res, md, { "Content-Encoding": isGZipped ? "identity" : undefined }); + res.setHeader("Content-Type", metadata.contentType); + setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined }); const byteRange = [...(req.header("range") || "").split("bytes="), "", ""]; - const [rangeStart, rangeEnd] = byteRange[1].split("-"); - if (rangeStart) { const range = { start: parseInt(rangeStart), end: rangeEnd ? parseInt(rangeEnd) : data.byteLength, }; res.setHeader("Content-Range", `bytes ${range.start}-${range.end - 1}/${data.byteLength}`); - res.status(206).end(data.slice(range.start, range.end)); - } else { - res.end(data); + return res.status(206).end(data.slice(range.start, range.end)); } - - return; - } - - if (!md.downloadTokens.length) { - md.addDownloadToken(); + return res.end(data); } - res.json(new OutgoingFirebaseMetadata(md)); + // Object metadata request + return res.json(new OutgoingFirebaseMetadata(metadata)); }); const handleMetadataUpdate = async (req: Request, res: Response) => { diff --git a/src/emulator/storage/errors.ts b/src/emulator/storage/errors.ts new file mode 100644 index 00000000000..f1021ca7daa --- /dev/null +++ b/src/emulator/storage/errors.ts @@ -0,0 +1,5 @@ +/** Error that signals that a resource could not be found */ +export class NotFoundError extends Error {} + +/** Error that signals that a necessary permission was lacking. */ +export class ForbiddenError extends Error {} diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 9e87f8ba790..34badd42b3b 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -8,6 +8,7 @@ import { IncomingMetadata, StoredFileMetadata, } from "./metadata"; +import { NotFoundError, ForbiddenError } from "./errors"; import * as path from "path"; import * as fs from "fs"; import * as fse from "fs-extra"; @@ -18,6 +19,9 @@ import { constructDefaultAdminSdkConfig, getProjectAdminSdkConfigOrCached, } from "../adminSdkConfig"; +import { StorageRulesetInstance } from "./rules/runtime"; +import { RulesetOperationMethod } from "./rules/types"; +import { isPermitted, RulesValidator } from "./rules/utils"; interface BucketsList { buckets: { @@ -120,6 +124,20 @@ export enum UploadStatus { FINISHED, } +/** Parsed request object for {@link StorageLayer#handleGetObject}. */ +export type GetObjectRequest = { + bucketId: string; + decodedObjectId: string; + authorization?: string; + downloadToken?: string; +}; + +/** Response object for {@link StorageLayer#handleGetObject}. */ +export type GetObjectResponse = { + metadata: StoredFileMetadata; + data: Buffer; +}; + export class StorageLayer { private _files!: Map; private _uploads!: Map; @@ -127,7 +145,7 @@ export class StorageLayer { private _persistence!: Persistence; private _cloudFunctions: StorageCloudFunctions; - constructor(private _projectId: string) { + constructor(private _projectId: string, private _validator: RulesValidator) { this.reset(); this._cloudFunctions = new StorageCloudFunctions(this._projectId); } @@ -157,6 +175,35 @@ export class StorageLayer { return [...this._buckets.values()]; } + /** + * Returns an stored object and its metadata. + * @throws {NotFoundError} if object does not exist + * @throws {ForbiddenError} if request is unauthorized + */ + public async handleGetObject(request: GetObjectRequest): Promise { + const metadata = this.getMetadata(request.bucketId, request.decodedObjectId); + + // If a valid download token is present, skip Firebase Rules auth. Mainly used by the js sdk. + let authorized = (metadata?.downloadTokens || []).includes(request.downloadToken ?? ""); + if (!authorized) { + authorized = await this._validator.validate( + ["b", request.bucketId, "o", request.decodedObjectId].join("/"), + RulesetOperationMethod.GET, + { before: metadata?.asRulesResource() }, + request.authorization + ); + } + if (!authorized) { + throw new ForbiddenError("Failed auth"); + } + + if (!metadata) { + throw new NotFoundError("File not found"); + } + + return { metadata: metadata!, data: this.getBytes(request.bucketId, request.decodedObjectId)! }; + } + public getMetadata(bucket: string, object: string): StoredFileMetadata | undefined { const key = this.path(bucket, object); const val = this._files.get(key); diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index 0dafd213463..a1343024a56 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -12,6 +12,7 @@ import { Source } from "./rules/types"; import { FirebaseError } from "../../error"; import { getDownloadDetails } from "../downloadableEmulators"; import express = require("express"); +import { getRulesValidator } from "./rules/utils"; export interface StorageEmulatorArgs { projectId: string; @@ -35,7 +36,10 @@ export class StorageEmulator implements EmulatorInstance { constructor(private args: StorageEmulatorArgs) { const downloadDetails = getDownloadDetails(Emulators.STORAGE); this._rulesRuntime = new StorageRulesRuntime(); - this._storageLayer = new StorageLayer(args.projectId); + this._storageLayer = new StorageLayer( + args.projectId, + getRulesValidator(() => this.rules) + ); } get storageLayer(): StorageLayer { diff --git a/src/emulator/storage/rules/utils.ts b/src/emulator/storage/rules/utils.ts new file mode 100644 index 00000000000..11457626217 --- /dev/null +++ b/src/emulator/storage/rules/utils.ts @@ -0,0 +1,86 @@ +import { StorageRulesetInstance } from "./runtime"; +import { RulesResourceMetadata } from "../metadata"; +import { RulesetOperationMethod } from "./types"; +import { EmulatorLogger } from "../../emulatorLogger"; +import { Emulators } from "../../types"; + +/** Variable overrides to be passed to the rules evaluator. */ +export type RulesVariableOverrides = { + before?: RulesResourceMetadata; + after?: RulesResourceMetadata; +}; +/** A simple interface for fetching Rules verdicts. */ +export interface RulesValidator { + validate( + path: string, + method: RulesetOperationMethod, + variableOverrides: RulesVariableOverrides, + authorization?: string + ): Promise; +} + +/** Provider for Storage security rules. */ +export type RulesetProvider = () => StorageRulesetInstance | undefined; + +/** + * Returns a {@link RulesValidator} that pulls a Ruleset from a + * {@link RulesetProvider} on each run. + */ +export function getRulesValidator(rulesetProvider: RulesetProvider): RulesValidator { + return { + validate: async ( + path: string, + method: RulesetOperationMethod, + variableOverrides: RulesVariableOverrides, + authorization?: string + ) => { + return await isPermitted({ + ruleset: rulesetProvider(), + file: variableOverrides, + path, + method, + authorization, + }); + }, + }; +} + +/** Authorizes file access based on security rules. */ +export async function isPermitted(opts: { + ruleset?: StorageRulesetInstance; + file: { + before?: RulesResourceMetadata; + after?: RulesResourceMetadata; + }; + path: string; + method: RulesetOperationMethod; + authorization?: string; +}): Promise { + if (!opts.ruleset) { + EmulatorLogger.forEmulator(Emulators.STORAGE).log( + "WARN", + `Can not process SDK request with no loaded ruleset` + ); + return false; + } + + // Skip auth for UI + if (["Bearer owner", "Firebase owner"].includes(opts.authorization || "")) { + return true; + } + + const { permitted, issues } = await opts.ruleset.verify({ + method: opts.method, + path: opts.path, + file: opts.file, + token: opts.authorization ? opts.authorization.split(" ")[1] : undefined, + }); + + if (issues.exist()) { + issues.all.forEach((warningOrError) => { + EmulatorLogger.forEmulator(Emulators.STORAGE).log("WARN", warningOrError); + }); + } + + return !!permitted; +} diff --git a/src/test/emulators/storage/files.spec.ts b/src/test/emulators/storage/files.spec.ts index 48834ad5029..e8944fa3986 100644 --- a/src/test/emulators/storage/files.spec.ts +++ b/src/test/emulators/storage/files.spec.ts @@ -2,6 +2,15 @@ import { expect } from "chai"; import { StoredFileMetadata } from "../../../emulator/storage/metadata"; import { StorageCloudFunctions } from "../../../emulator/storage/cloudFunctions"; import { StorageLayer } from "../../../emulator/storage/files"; +import { ForbiddenError, NotFoundError } from "../../../emulator/storage/errors"; + +const ALWAYS_TRUE_RULES_VALIDATOR = { + validate: () => Promise.resolve(true), +}; + +const ALWAYS_FALSE_RULES_VALIDATOR = { + validate: async () => Promise.resolve(false), +}; describe("files", () => { it("can serialize and deserialize metadata", () => { @@ -26,7 +35,7 @@ describe("files", () => { }); it("should store file in memory when upload is finalized", () => { - const storageLayer = new StorageLayer("project"); + const storageLayer = new StorageLayer("project", ALWAYS_TRUE_RULES_VALIDATOR); const bytesToWrite = "Hello, World!"; const upload = storageLayer.startUpload("bucket", "object", "mime/type", { @@ -40,7 +49,7 @@ describe("files", () => { }); it("should delete file from persistence layer when upload is cancelled", () => { - const storageLayer = new StorageLayer("project"); + const storageLayer = new StorageLayer("project", ALWAYS_TRUE_RULES_VALIDATOR); const upload = storageLayer.startUpload("bucket", "object", "mime/type", { contentType: "mime/type", @@ -50,4 +59,49 @@ describe("files", () => { expect(storageLayer.getMetadata("bucket", "object")).to.equal(undefined); }); + + describe("#handleGetObject()", () => { + it("should return data and metadata", async () => { + const storageLayer = new StorageLayer("project", ALWAYS_TRUE_RULES_VALIDATOR); + storageLayer.oneShotUpload( + "bucket", + "dir%2Fobject", + "mime/type", + { + contentType: "mime/type", + }, + Buffer.from("Hello, World!") + ); + + const { metadata, data } = await storageLayer.handleGetObject({ + bucketId: "bucket", + decodedObjectId: "dir%2Fobject", + }); + + expect(metadata.contentType).to.equal("mime/type"); + expect(data.toString()).to.equal("Hello, World!"); + }); + + it("should throw an error if request is not authorized", () => { + const storageLayer = new StorageLayer("project", ALWAYS_FALSE_RULES_VALIDATOR); + + expect( + storageLayer.handleGetObject({ + bucketId: "bucket", + decodedObjectId: "dir%2Fobject", + }) + ).to.be.rejectedWith(ForbiddenError); + }); + + it("should throw an error if the object does not exist", () => { + const storageLayer = new StorageLayer("project", ALWAYS_TRUE_RULES_VALIDATOR); + + expect( + storageLayer.handleGetObject({ + bucketId: "bucket", + decodedObjectId: "dir%2Fobject", + }) + ).to.be.rejectedWith(NotFoundError); + }); + }); }); From 11d168bdd2851fcf43fe354e391aa9237a6e1a6b Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Thu, 3 Mar 2022 09:52:10 -0500 Subject: [PATCH 0124/1699] Track ext:install with url source usage (#4242) * Update ext-install.ts * Update ext-install.ts * pr fixes --- src/commands/ext-install.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 50f38b6a930..8411ac77e76 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -1,4 +1,3 @@ -import * as _ from "lodash"; import * as clc from "cli-color"; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const { marked } = require("marked"); @@ -31,6 +30,7 @@ import { promptForValidInstanceId, isLocalOrURLPath, diagnoseAndFixProject, + isUrlPath, } from "../extensions/extensionsHelper"; import { update } from "../extensions/updateHelper"; import { getRandomString } from "../extensions/utils"; @@ -83,13 +83,19 @@ export default new Command("ext:install [extensionName]") } let source; let extVersion; + + // TODO(b/220900194): Remove tracking of url install once we remove this feature. + if (isUrlPath(extensionName)) { + void track("Extension Install", "Install by url path", options.interactive ? 1 : 0); + } + // If the user types in URL, or a local path (prefixed with ~/, ../, or ./), install from local/URL source. // Otherwise, treat the input as an extension reference and proceed with reference-based installation. if (isLocalOrURLPath(extensionName)) { - track("Extension Install", "Install by Source", options.interactive ? 1 : 0); + void track("Extension Install", "Install by Source", options.interactive ? 1 : 0); source = await infoInstallBySource(projectId, extensionName); } else { - track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); + void track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); extVersion = await infoInstallByReference(extensionName, options.interactive); } if ( @@ -175,7 +181,7 @@ async function infoInstallByReference( const ref = refs.parse(extensionName); const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref)); if (!ref.version) { - track("Extension Install", "Install by Extension Version Ref", interactive ? 1 : 0); + void track("Extension Install", "Install by Extension Version Ref", interactive ? 1 : 0); extensionName = `${extensionName}@latest`; } const extVersion = await extensionsApi.getExtensionVersion(extensionName); From 79374124d1599ccf5ef03e807ad0970f7062b604 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Thu, 3 Mar 2022 14:06:33 -0500 Subject: [PATCH 0125/1699] Refactor persistence out of StorageLayer (#4248) * refactor persistence out into StorageEmulator * fix lint and method references * fix tests --- src/emulator/storage/files.ts | 115 ++--------------------- src/emulator/storage/index.ts | 17 +++- src/emulator/storage/persistence.ts | 103 ++++++++++++++++++++ src/emulator/storage/server.ts | 2 +- src/test/emulators/storage/files.spec.ts | 18 +++- 5 files changed, 140 insertions(+), 115 deletions(-) create mode 100644 src/emulator/storage/persistence.ts diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 34badd42b3b..f72276212c0 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -1,5 +1,3 @@ -import { openSync, closeSync, readSync, unlinkSync, renameSync, existsSync, mkdirSync } from "fs"; -import { tmpdir } from "os"; import { v4 } from "uuid"; import { ListItem, ListResponse } from "./list"; import { @@ -12,16 +10,15 @@ import { NotFoundError, ForbiddenError } from "./errors"; import * as path from "path"; import * as fs from "fs"; import * as fse from "fs-extra"; -import * as rimraf from "rimraf"; import { StorageCloudFunctions } from "./cloudFunctions"; import { logger } from "../../logger"; import { constructDefaultAdminSdkConfig, getProjectAdminSdkConfigOrCached, } from "../adminSdkConfig"; -import { StorageRulesetInstance } from "./rules/runtime"; import { RulesetOperationMethod } from "./rules/types"; -import { isPermitted, RulesValidator } from "./rules/utils"; +import { RulesValidator } from "./rules/utils"; +import { Persistence } from "./persistence"; interface BucketsList { buckets: { @@ -142,17 +139,19 @@ export class StorageLayer { private _files!: Map; private _uploads!: Map; private _buckets!: Map; - private _persistence!: Persistence; private _cloudFunctions: StorageCloudFunctions; - constructor(private _projectId: string, private _validator: RulesValidator) { + constructor( + private _projectId: string, + private _validator: RulesValidator, + private _persistence: Persistence + ) { this.reset(); this._cloudFunctions = new StorageCloudFunctions(this._projectId); } public reset(): void { this._files = new Map(); - this._persistence = new Persistence(`${tmpdir()}/firebase/storage/blobs`); this._uploads = new Map(); this._buckets = new Map(); } @@ -299,7 +298,7 @@ export class StorageLayer { if (!upload) { return undefined; } - this._persistence.appendBytes(upload.fileLocation, bytes, upload.currentBytesUploaded); + this._persistence.appendBytes(upload.fileLocation, bytes); upload.currentBytesUploaded += bytes.byteLength; return upload; } @@ -658,101 +657,3 @@ export class StorageLayer { } } } - -export class Persistence { - private _dirPath: string; - constructor(dirPath: string) { - this._dirPath = dirPath; - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { - recursive: true, - }); - } - } - - public get dirPath(): string { - return this._dirPath; - } - - appendBytes(fileName: string, bytes: Buffer, fileOffset?: number): string { - const filepath = this.getDiskPath(fileName); - - const encodedSlashIndex = filepath.toLowerCase().lastIndexOf("%2f"); - const dirPath = - encodedSlashIndex >= 0 ? filepath.substring(0, encodedSlashIndex) : path.dirname(filepath); - - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { - recursive: true, - }); - } - let fd; - - try { - // TODO: This is more technically correct, but corrupts multipart files - // fd = openSync(path, "w+"); - // writeSync(fd, bytes, 0, bytes.byteLength, fileOffset); - - fs.appendFileSync(filepath, bytes); - return filepath; - } finally { - if (fd) { - closeSync(fd); - } - } - } - - readBytes(fileName: string, size: number, fileOffset?: number): Buffer { - const path = this.getDiskPath(fileName); - let fd; - try { - fd = openSync(path, "r"); - const buf = Buffer.alloc(size); - const offset = fileOffset && fileOffset > 0 ? fileOffset : 0; - readSync(fd, buf, 0, size, offset); - return buf; - } finally { - if (fd) { - closeSync(fd); - } - } - } - - deleteFile(fileName: string, failSilently = false): void { - try { - unlinkSync(this.getDiskPath(fileName)); - } catch (err: any) { - if (!failSilently) { - throw err; - } - } - } - - deleteAll(): Promise { - return new Promise((resolve, reject) => { - rimraf(this._dirPath, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - } - - renameFile(oldName: string, newName: string): void { - const dirPath = this.getDiskPath(path.dirname(newName)); - - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { - recursive: true, - }); - } - - renameSync(this.getDiskPath(oldName), this.getDiskPath(newName)); - } - - getDiskPath(fileName: string): string { - return path.join(this._dirPath, fileName); - } -} diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index a1343024a56..3d4039d2bb1 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -1,4 +1,4 @@ -import * as path from "path"; +import { tmpdir } from "os"; import * as utils from "../../utils"; import { Constants } from "../constants"; import { EmulatorInfo, EmulatorInstance, Emulators } from "../types"; @@ -13,6 +13,7 @@ import { FirebaseError } from "../../error"; import { getDownloadDetails } from "../downloadableEmulators"; import express = require("express"); import { getRulesValidator } from "./rules/utils"; +import { Persistence } from "./persistence"; export interface StorageEmulatorArgs { projectId: string; @@ -31,14 +32,17 @@ export class StorageEmulator implements EmulatorInstance { private _logger = EmulatorLogger.forEmulator(Emulators.STORAGE); private _rulesRuntime: StorageRulesRuntime; + private _persistence: Persistence; private _storageLayer: StorageLayer; constructor(private args: StorageEmulatorArgs) { const downloadDetails = getDownloadDetails(Emulators.STORAGE); this._rulesRuntime = new StorageRulesRuntime(); + this._persistence = new Persistence(this.getPersistenceTmpDir()); this._storageLayer = new StorageLayer( args.projectId, - getRulesValidator(() => this.rules) + getRulesValidator(() => this.rules), + this._persistence ); } @@ -54,6 +58,11 @@ export class StorageEmulator implements EmulatorInstance { return this._logger; } + reset(): void { + this._storageLayer.reset(); + this._persistence.reset(this.getPersistenceTmpDir()); + } + async start(): Promise { const { host, port } = this.getInfo(); await this._rulesRuntime.start(this.args.auto_download); @@ -179,4 +188,8 @@ export class StorageEmulator implements EmulatorInstance { getApp(): express.Express { return this._app!; } + + private getPersistenceTmpDir(): string { + return `${tmpdir()}/firebase/storage/blobs`; + } } diff --git a/src/emulator/storage/persistence.ts b/src/emulator/storage/persistence.ts new file mode 100644 index 00000000000..9651490beb3 --- /dev/null +++ b/src/emulator/storage/persistence.ts @@ -0,0 +1,103 @@ +import { openSync, closeSync, readSync, unlinkSync, renameSync, existsSync, mkdirSync } from "fs"; +import * as rimraf from "rimraf"; +import * as fs from "fs"; +import * as path from "path"; + +/** Helper for disk I/O operations. */ +export class Persistence { + private _dirPath!: string; + constructor(dirPath: string) { + this.reset(dirPath); + } + + public reset(dirPath: string) { + this._dirPath = dirPath; + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { + recursive: true, + }); + } + } + + public get dirPath(): string { + return this._dirPath; + } + + appendBytes(fileName: string, bytes: Buffer): string { + const filepath = this.getDiskPath(fileName); + + const encodedSlashIndex = filepath.toLowerCase().lastIndexOf("%2f"); + const dirPath = + encodedSlashIndex >= 0 ? filepath.substring(0, encodedSlashIndex) : path.dirname(filepath); + + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { + recursive: true, + }); + } + let fd; + + try { + fs.appendFileSync(filepath, bytes); + return filepath; + } finally { + if (fd) { + closeSync(fd); + } + } + } + + readBytes(fileName: string, size: number, fileOffset?: number): Buffer { + const path = this.getDiskPath(fileName); + let fd; + try { + fd = openSync(path, "r"); + const buf = Buffer.alloc(size); + const offset = fileOffset && fileOffset > 0 ? fileOffset : 0; + readSync(fd, buf, 0, size, offset); + return buf; + } finally { + if (fd) { + closeSync(fd); + } + } + } + + deleteFile(fileName: string, failSilently = false): void { + try { + unlinkSync(this.getDiskPath(fileName)); + } catch (err: any) { + if (!failSilently) { + throw err; + } + } + } + + deleteAll(): Promise { + return new Promise((resolve, reject) => { + rimraf(this._dirPath, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + renameFile(oldName: string, newName: string): void { + const dirPath = this.getDiskPath(path.dirname(newName)); + + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { + recursive: true, + }); + } + + renameSync(this.getDiskPath(oldName), this.getDiskPath(newName)); + } + + getDiskPath(fileName: string): string { + return path.join(this._dirPath, fileName); + } +} diff --git a/src/emulator/storage/server.ts b/src/emulator/storage/server.ts index a02fa959b21..5ba0af0889b 100644 --- a/src/emulator/storage/server.ts +++ b/src/emulator/storage/server.ts @@ -107,7 +107,7 @@ export function createApp( }); app.post("/internal/reset", (req, res) => { - storageLayer.reset(); + emulator.reset(); res.sendStatus(200); }); diff --git a/src/test/emulators/storage/files.spec.ts b/src/test/emulators/storage/files.spec.ts index e8944fa3986..0aaa88e85df 100644 --- a/src/test/emulators/storage/files.spec.ts +++ b/src/test/emulators/storage/files.spec.ts @@ -1,8 +1,12 @@ import { expect } from "chai"; +import { tmpdir } from "os"; + import { StoredFileMetadata } from "../../../emulator/storage/metadata"; import { StorageCloudFunctions } from "../../../emulator/storage/cloudFunctions"; import { StorageLayer } from "../../../emulator/storage/files"; import { ForbiddenError, NotFoundError } from "../../../emulator/storage/errors"; +import { Persistence } from "../../../emulator/storage/persistence"; +import { RulesValidator } from "../../../emulator/storage/rules/utils"; const ALWAYS_TRUE_RULES_VALIDATOR = { validate: () => Promise.resolve(true), @@ -35,7 +39,7 @@ describe("files", () => { }); it("should store file in memory when upload is finalized", () => { - const storageLayer = new StorageLayer("project", ALWAYS_TRUE_RULES_VALIDATOR); + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); const bytesToWrite = "Hello, World!"; const upload = storageLayer.startUpload("bucket", "object", "mime/type", { @@ -49,7 +53,7 @@ describe("files", () => { }); it("should delete file from persistence layer when upload is cancelled", () => { - const storageLayer = new StorageLayer("project", ALWAYS_TRUE_RULES_VALIDATOR); + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); const upload = storageLayer.startUpload("bucket", "object", "mime/type", { contentType: "mime/type", @@ -62,7 +66,7 @@ describe("files", () => { describe("#handleGetObject()", () => { it("should return data and metadata", async () => { - const storageLayer = new StorageLayer("project", ALWAYS_TRUE_RULES_VALIDATOR); + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); storageLayer.oneShotUpload( "bucket", "dir%2Fobject", @@ -83,7 +87,7 @@ describe("files", () => { }); it("should throw an error if request is not authorized", () => { - const storageLayer = new StorageLayer("project", ALWAYS_FALSE_RULES_VALIDATOR); + const storageLayer = getStorageLayer(ALWAYS_FALSE_RULES_VALIDATOR); expect( storageLayer.handleGetObject({ @@ -94,7 +98,7 @@ describe("files", () => { }); it("should throw an error if the object does not exist", () => { - const storageLayer = new StorageLayer("project", ALWAYS_TRUE_RULES_VALIDATOR); + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); expect( storageLayer.handleGetObject({ @@ -104,4 +108,8 @@ describe("files", () => { ).to.be.rejectedWith(NotFoundError); }); }); + + const getStorageLayer = (rulesValidator: RulesValidator) => + new StorageLayer("project", rulesValidator, new Persistence(getPersistenceTmpDir())); + const getPersistenceTmpDir = () => `${tmpdir()}/firebase/storage/blobs`; }); From fa667f8a5c9df65bfb7edf34477bf17ecd41237b Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Thu, 3 Mar 2022 14:38:33 -0500 Subject: [PATCH 0126/1699] Refactor Multipart request body parsing (#4247) * multipart and firebase.ts integration * update gcloud.ts * merge * address pr comments * return type * return type --- src/emulator/storage/apis/firebase.ts | 91 ++++++------ src/emulator/storage/apis/gcloud.ts | 104 +++++++------- src/emulator/storage/multipart.ts | 140 +++++++++++++++++++ src/test/emulators/storage/multipart.spec.ts | 104 ++++++++++++++ 4 files changed, 336 insertions(+), 103 deletions(-) create mode 100644 src/emulator/storage/multipart.ts create mode 100644 src/test/emulators/storage/multipart.spec.ts diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index a18d4eab2f4..fc5df887bdb 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -7,8 +7,9 @@ import { Request, Response, Router } from "express"; import { StorageEmulator } from "../index"; import { EmulatorRegistry } from "../../registry"; import { RulesetOperationMethod } from "../rules/types"; -import { isPermitted } from "../rules/utils"; +import { parseObjectUploadMultipartRequest } from "../multipart"; import { NotFoundError, ForbiddenError } from "../errors"; +import { isPermitted } from "../rules/utils"; /** * @param emulator @@ -211,6 +212,22 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { ); }); + const reqBodyToBuffer = async (req: Request): Promise => { + if (req.body instanceof Buffer) { + return Buffer.from(req.body); + } + const bufs: Buffer[] = []; + req.on("data", (data) => { + bufs.push(data); + }); + await new Promise((resolve) => { + req.on("end", () => { + resolve(); + }); + }); + return Buffer.concat(bufs); + }; + const handleUpload = async (req: Request, res: Response) => { if (req.query.create_token || req.query.delete_token) { const decodedObjectId = decodeURIComponent(req.params.objectId); @@ -283,44 +300,36 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { const uploadType = req.header("x-goog-upload-protocol"); if (uploadType === "multipart") { - const contentType = req.header("content-type"); - if (!contentType || !contentType.startsWith("multipart/related")) { - res.sendStatus(400); - return; - } - - const boundary = `--${contentType.split("boundary=")[1]}`; - const bodyString = req.body.toString(); - const bodyStringParts = bodyString.split(boundary).filter((v: string) => v); - - const metadataString = bodyStringParts[0].split("\r\n")[3]; - const blobParts = bodyStringParts[1].split("\r\n"); - const blobContentTypeString = blobParts[1]; - if (!blobContentTypeString || !blobContentTypeString.startsWith("Content-Type: ")) { - res.sendStatus(400); - return; + const contentTypeHeader = req.header("content-type"); + if (!contentTypeHeader) { + return res.sendStatus(400); } - const blobContentType = blobContentTypeString.slice("Content-Type: ".length); - const bodyBuffer = req.body as Buffer; - const metadataSegment = `${boundary}${bodyString.split(boundary)[1]}`; - const dataSegment = `${boundary}${bodyString.split(boundary).slice(2)[0]}`; - const dataSegmentHeader = (dataSegment.match(/.+Content-Type:.+?\r\n\r\n/s) || [])[0]; - - if (!dataSegmentHeader) { - res.sendStatus(400); - return; + let metadataRaw: string; + let dataRaw: Buffer; + try { + ({ metadataRaw, dataRaw } = parseObjectUploadMultipartRequest( + contentTypeHeader!, + await reqBodyToBuffer(req) + )); + } catch (err) { + if (err instanceof Error) { + return res.status(400).json({ + error: { + code: 400, + message: err.toString(), + }, + }); + } + throw err; } - - const bufferOffset = metadataSegment.length + dataSegmentHeader.length; - - const blobBytes = Buffer.from(bodyBuffer.slice(bufferOffset, -`\r\n${boundary}--`.length)); + const metadata = JSON.parse(metadataRaw)!; const md = storageLayer.oneShotUpload( req.params.bucketId, name, - blobContentType, - JSON.parse(metadataString), - Buffer.from(blobBytes) + metadata.contentType!, + metadata, + dataRaw ); if (!md) { @@ -435,21 +444,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { let upload; if (uploadCommand.includes("upload")) { - if (!(req.body instanceof Buffer)) { - const bufs: Buffer[] = []; - req.on("data", (data) => { - bufs.push(data); - }); - - await new Promise((resolve) => { - req.on("end", () => { - req.body = Buffer.concat(bufs); - resolve(); - }); - }); - } - - upload = storageLayer.uploadBytes(uploadId, req.body); + upload = storageLayer.uploadBytes(uploadId, await reqBodyToBuffer(req)); if (!upload) { res.sendStatus(400); diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index ec95847c29b..1a54152616f 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -11,6 +11,7 @@ import { StorageEmulator } from "../index"; import { EmulatorLogger } from "../../emulatorLogger"; import { StorageLayer } from "../files"; import type { Request, Response } from "express"; +import { parseObjectUploadMultipartRequest } from "../multipart"; /** * @param emulator @@ -103,27 +104,31 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { res.status(200).send(); }); - gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => { - if (!req.query.upload_id) { - res.sendStatus(400); - return; + const reqBodyToBuffer = async (req: Request): Promise => { + if (req.body instanceof Buffer) { + return Buffer.from(req.body); } - - const uploadId = req.query.upload_id.toString(); - const bufs: Buffer[] = []; req.on("data", (data) => { bufs.push(data); }); - await new Promise((resolve) => { req.on("end", () => { - req.body = Buffer.concat(bufs); resolve(); }); }); + return Buffer.concat(bufs); + }; + + gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => { + if (!req.query.upload_id) { + res.sendStatus(400); + return; + } - const upload = storageLayer.uploadBytes(uploadId, req.body); + const uploadId = req.query.upload_id.toString(); + + const upload = storageLayer.uploadBytes(uploadId, await reqBodyToBuffer(req)); if (!upload) { res.sendStatus(400); return; @@ -166,7 +171,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { .status(200); }); - gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", (req, res) => { + gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => { if (!req.query.name) { res.sendStatus(400); return; @@ -177,15 +182,20 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { name = name.slice(1); } - const contentType = req.header("content-type") || req.header("x-upload-content-type"); + const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type"); - if (!contentType) { + if (!contentTypeHeader) { res.sendStatus(400); return; } if (req.query.uploadType === "resumable") { - const upload = storageLayer.startUpload(req.params.bucketId, name, contentType, req.body); + const upload = storageLayer.startUpload( + req.params.bucketId, + name, + contentTypeHeader, + req.body + ); const emulatorInfo = EmulatorRegistry.getInfo(Emulators.STORAGE); if (emulatorInfo === undefined) { @@ -199,55 +209,39 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return; } - if (!contentType.startsWith("multipart/related")) { - res.sendStatus(400); - return; - } - - const boundary = `--${contentType.split("boundary=")[1]}`; - const bodyString = req.body.toString(); - - const bodyStringParts = bodyString.split(boundary).filter((v: string) => v); - - const metadataString = bodyStringParts[0].split(/\r?\n/)[3]; - const blobParts = bodyStringParts[1].split(/\r?\n/); - const blobContentTypeString = blobParts[1]; - - if (!blobContentTypeString || !blobContentTypeString.startsWith("Content-Type: ")) { - res.sendStatus(400); - return; - } - - const blobContentType = blobContentTypeString.slice("Content-Type: ".length); - const bodyBuffer = req.body as Buffer; - - const metadataSegment = `${boundary}${bodyString.split(boundary)[1]}`; - const dataSegment = `${boundary}${bodyString.split(boundary).slice(2)[0]}`; - const dataSegmentHeader = (dataSegment.match(/.+Content-Type:.+?\r?\n\r?\n/s) || [])[0]; - - if (!dataSegmentHeader) { - res.sendStatus(400); - return; + // Multipart upload + let metadataRaw: string; + let dataRaw: Buffer; + try { + ({ metadataRaw, dataRaw } = parseObjectUploadMultipartRequest( + contentTypeHeader!, + await reqBodyToBuffer(req) + )); + } catch (err) { + if (err instanceof Error) { + return res.status(400).json({ + error: { + code: 400, + message: err.toString(), + }, + }); + } + throw err; } - - const bufferOffset = metadataSegment.length + dataSegmentHeader.length; - - const blobBytes = Buffer.from(bodyBuffer.slice(bufferOffset, -`\r\n${boundary}--`.length)); - - const metadata = storageLayer.oneShotUpload( + const incomingMetadata = JSON.parse(metadataRaw)!; + const storedMetadata = storageLayer.oneShotUpload( req.params.bucketId, name, - blobContentType, - JSON.parse(metadataString), - blobBytes + incomingMetadata.contentType!, + incomingMetadata, + dataRaw ); - - if (!metadata) { + if (!storedMetadata) { res.sendStatus(400); return; } - res.status(200).json(new CloudStorageObjectMetadata(metadata)).send(); + res.status(200).json(new CloudStorageObjectMetadata(storedMetadata)).send(); return; }); diff --git a/src/emulator/storage/multipart.ts b/src/emulator/storage/multipart.ts new file mode 100644 index 00000000000..54a68fc57e5 --- /dev/null +++ b/src/emulator/storage/multipart.ts @@ -0,0 +1,140 @@ +/** + * Represents a parsed multipart form body for an upload object request. + * + * Note: This class and others in files deal directly with buffers as + * converting to String can append unwanted encoding data to the blob data + * passed in the original request. + */ +export type ObjectUploadMultipartData = { + metadataRaw: string; + dataRaw: Buffer; +}; + +/** + * Represents a parsed multipart request body. Request bodies can have an + * arbitrary number of parts. + */ +type MultipartRequestBody = MultipartRequestBodyPart[]; + +const LINE_SEPARATOR = `\r\n`; + +/** + * Returns an array of Buffers constructed by splitting a Buffer on a delimiter. + * @param maxResults Returns at most this many results. Any slices remaining in the + * original buffer will be returned as a single Buffer at the end + */ +function splitBufferByDelimiter(buffer: Buffer, delimiter: string, maxResults = -1): Buffer[] { + // Iterate through delimited slices and save to separate Buffers + let offset = 0; + let nextDelimiterIndex = buffer.indexOf(delimiter, offset); + const bufferParts: Buffer[] = []; + while (nextDelimiterIndex !== -1) { + if (maxResults === 0) { + return bufferParts; + } else if (maxResults === 1) { + // Save the rest of the buffer as one slice and return. + bufferParts.push(Buffer.from(buffer.slice(offset))); + return bufferParts; + } + bufferParts.push(Buffer.from(buffer.slice(offset, nextDelimiterIndex))); + offset = nextDelimiterIndex + delimiter.length; + nextDelimiterIndex = buffer.indexOf(delimiter, offset); + maxResults -= 1; + } + bufferParts.push(Buffer.from(buffer.slice(offset))); + return bufferParts; +} + +/** + * Parses a multipart request body buffer into a {@link MultipartRequestBody}. + * @param boundaryId the boundary id of the multipart request + * @param body multipart request body as a Buffer + */ +function parseMultipartRequestBody(boundaryId: string, body: Buffer): MultipartRequestBody { + const boundaryString = `--${boundaryId}`; + const bodyParts = splitBufferByDelimiter(body, boundaryString).map((buf) => { + // Remove the \r\n and the beginning of each part left from the boundary line. + return Buffer.from(buf.slice(2)); + }); + // A valid split request body should have two extra Buffers, one at the beginning and end. + const parsedParts: MultipartRequestBodyPart[] = []; + for (const bodyPart of bodyParts.slice(1, bodyParts.length - 1)) { + parsedParts.push(parseMultipartRequestBodyPart(bodyPart)); + } + return parsedParts; +} + +/** + * Represents a single boundary-delineated multipart request body part, + * Ex: """Content-Type: application/json\r + * \r + * {"contentType":"text/plain"}\r + * """ + */ +type MultipartRequestBodyPart = { + // From the example above: "Content-Type: application/json" + contentTypeRaw: string; + // From the example above: '{"contentType":"text/plain"}' + dataRaw: Buffer; +}; + +/** + * Parses a string into a {@link MultipartRequestBodyPart}. We expect 3 sections + * delineated by '\r\n': + * 1: content type + * 2: white space + * 3: free form data + * @param bodyPart a multipart request body part as a Buffer + */ +function parseMultipartRequestBodyPart(bodyPart: Buffer): MultipartRequestBodyPart { + // The free form data section may have \r\n data in it so glob it together rather than + // splitting the entire body part buffer. + const sections = splitBufferByDelimiter(bodyPart, LINE_SEPARATOR, /* maxResults = */ 3); + + const contentTypeRaw = sections[0].toString(); + if (!contentTypeRaw.startsWith("Content-Type: ")) { + throw new Error(`Failed to parse multipart request body part. Missing content type.`); + } + + // Remove trailing '\r\n' from the last line since splitBufferByDelimiter will not with + // maxResults set. + const dataRaw = Buffer.from(sections[2]).slice(0, sections[2].byteLength - LINE_SEPARATOR.length); + return { contentTypeRaw, dataRaw }; +} + +/** + * Parses a multipart form request for a file upload into its parts. + * @param contentTypeHeader value of ContentType header passed in request. + * Example: "multipart/related; boundary=b1d5b2e3-1845-4338-9400-6ac07ce53c1e" + * @param body string value of the body of the multipart request. + * Example: """--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r + * Content-Type: application/json\r + * \r + * {"contentType":"text/plain"}\r + * --b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r + * Content-Type: text/plain\r + * \r + * �ZDn�QF�&�\r + * --b1d5b2e3-1845-4338-9400-6ac07ce53c1e--\r + * """ + */ +export function parseObjectUploadMultipartRequest( + contentTypeHeader: string, + body: Buffer +): ObjectUploadMultipartData { + if (!contentTypeHeader.startsWith("multipart/related")) { + throw new Error(`Invalid Content-Type: ${contentTypeHeader}`); + } + const boundaryId = contentTypeHeader.split("boundary=")[1]; + if (!boundaryId) { + throw new Error(`Invalid Content-Type header: ${contentTypeHeader}`); + } + const parsedBody = parseMultipartRequestBody(boundaryId, body); + if (parsedBody.length !== 2) { + throw new Error(`Unexpected number of parts in request body`); + } + return { + metadataRaw: parsedBody[0].dataRaw.toString(), + dataRaw: Buffer.from(parsedBody[1].dataRaw), + }; +} diff --git a/src/test/emulators/storage/multipart.spec.ts b/src/test/emulators/storage/multipart.spec.ts new file mode 100644 index 00000000000..eb983e166db --- /dev/null +++ b/src/test/emulators/storage/multipart.spec.ts @@ -0,0 +1,104 @@ +import { expect } from "chai"; +import { parseObjectUploadMultipartRequest } from "../../../emulator/storage/multipart"; +import { randomBytes } from "crypto"; + +describe("Storage Multipart Request Parser", () => { + const CONTENT_TYPE_HEADER = "multipart/related; boundary=b1d5b2e3-1845-4338-9400-6ac07ce53c1e"; + const BODY = Buffer.from(`--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +Content-Type: application/json\r +\r +{"contentType":"text/plain"}\r +--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +Content-Type: text/plain\r +\r +hello there! +\r +--b1d5b2e3-1845-4338-9400-6ac07ce53c1e--\r +`); + + describe("#parseObjectUploadMultipartRequest()", () => { + it("parses an upload object multipart request successfully", () => { + const { metadataRaw, dataRaw } = parseObjectUploadMultipartRequest(CONTENT_TYPE_HEADER, BODY); + + expect(metadataRaw).to.equal('{"contentType":"text/plain"}'); + expect(dataRaw.toString()).to.equal("hello there!\n"); + }); + + it("parses an upload object multipart request with non utf-8 data successfully", () => { + const bodyPart1 = Buffer.from(`--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +Content-Type: application/json\r +\r +{"contentType":"text/plain"}\r +--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +Content-Type: text/plain\r +\r +`); + const data = Buffer.concat( + [Buffer.from(randomBytes(100)), Buffer.from("\r\n"), Buffer.from(randomBytes(100))], + 202 + ); + const bodyPart2 = Buffer.from(`\r\n--b1d5b2e3-1845-4338-9400-6ac07ce53c1e--\r\n`); + const body = Buffer.concat([bodyPart1, data, bodyPart2]); + + const { dataRaw } = parseObjectUploadMultipartRequest(CONTENT_TYPE_HEADER, body); + + expect(dataRaw.byteLength).to.equal(data.byteLength); + }); + + it("fails to parse with invalid Content-Type value", () => { + const invalidContentTypeHeader = "blah"; + expect(() => parseObjectUploadMultipartRequest(invalidContentTypeHeader, BODY)).to.throw( + "Invalid Content-Type" + ); + }); + + it("fails to parse with invalid boundary value", () => { + const invalidContentTypeHeader = "multipart/related; boundary="; + expect(() => parseObjectUploadMultipartRequest(invalidContentTypeHeader, BODY)).to.throw( + "Invalid Content-Type" + ); + }); + + it("fails to parse when body has wrong number of parts", () => { + const invalidBody = Buffer.from(`--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +Content-Type: application/json\r +\r +{"contentType":"text/plain"}\r +--b1d5b2e3-1845-4338-9400-6ac07ce53c1e--\r +`); + expect(() => parseObjectUploadMultipartRequest(CONTENT_TYPE_HEADER, invalidBody)).to.throw( + "Unexpected number of parts" + ); + }); + + it("fails to parse when body part has invalid content type", () => { + const invalidBody = Buffer.from(`--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +bogus content type\r +\r +{"contentType":"text/plain"}\r +--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +bogus content type\r +\r +hello there! +\r +--b1d5b2e3-1845-4338-9400-6ac07ce53c1e--\r +`); + expect(() => parseObjectUploadMultipartRequest(CONTENT_TYPE_HEADER, invalidBody)).to.throw( + "Missing content type." + ); + }); + + it("fails to parse when body part is malformed", () => { + const invalidBody = Buffer.from(`--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +\r +{"contentType":"text/plain"}\r +--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r +\r +--b1d5b2e3-1845-4338-9400-6ac07ce53c1e--\r +`); + expect(() => parseObjectUploadMultipartRequest(CONTENT_TYPE_HEADER, invalidBody)).to.throw( + "Failed to parse multipart request body part" + ); + }); + }); +}); From a98d160c3ef22a355770bafb8c923e920fd11850 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 3 Mar 2022 15:02:04 -0800 Subject: [PATCH 0127/1699] Migrate endpoint annotation eventTrigger.eventFilters to list of filters (#4240) Previously, `eventFilter` attribute was an object: ``` eventTrigger: { eventType: "an.event", eventFilter: { resource: "my-storage-bucket", appId: "12345", } } ``` We now prefer eventFilter as a list: ``` eventTrigger: { eventType: "an.event", eventFilter: [ { attribute: "resource", value: "my-storage-bucket", }. { attribute: "appId", value: "12345", }. ] } ``` Most of the code here are simply makes necessary changes to operate on a list instead of an object. One new behavior worth noting: Manifest discovery now construct full topic path (e.g. `my-topic` -> `projects/my-project/topics/topic`. (Surprisingly, we don't need to do the same for storage buckets since bucket names are globally unique) --- src/deploy/functions/backend.ts | 25 +++++-- src/deploy/functions/eventTypes.ts | 8 --- src/deploy/functions/release/planner.ts | 14 ++-- .../functions/runtimes/discovery/v1alpha1.ts | 8 ++- .../functions/runtimes/node/parseTriggers.ts | 39 ++++++++--- src/deploy/functions/services/index.ts | 9 +-- src/deploy/functions/services/storage.ts | 16 +++-- src/deploy/functions/triggerRegionHelper.ts | 2 +- src/emulator/functionsEmulatorShared.ts | 51 +++++++++------ src/functions/events/v2.ts | 10 +++ src/gcp/cloudfunctions.ts | 17 +++-- src/gcp/cloudfunctionsv2.ts | 35 +++++++--- src/test/deploy/functions/checkIam.spec.ts | 54 ++++++++++----- src/test/deploy/functions/prepare.spec.ts | 20 ++++-- src/test/deploy/functions/prompts.spec.ts | 9 ++- .../functions/release/fabricator.spec.ts | 45 ++++++++----- .../deploy/functions/release/planner.spec.ts | 54 +++++++++------ .../deploy/functions/release/reporter.spec.ts | 2 +- .../runtimes/discovery/v1alpha1.spec.ts | 54 +++++++++++++-- .../runtimes/node/parseTriggers.spec.ts | 18 +++-- .../functions/triggerRegionHelper.spec.ts | 54 ++++++++++----- src/test/gcp/cloudfunctions.spec.ts | 27 +++++--- src/test/gcp/cloudfunctionsv2.spec.ts | 65 ++++++++++++++----- 23 files changed, 441 insertions(+), 195 deletions(-) delete mode 100644 src/deploy/functions/eventTypes.ts create mode 100644 src/functions/events/v2.ts diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index f6336dbe124..d09bad32ba1 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -48,8 +48,16 @@ export interface CallableTriggered { callableTrigger: CallableTrigger; } -/** Well known keys in the eventFilter attribute of an event trigger */ -export type EventFilterKey = "resource"; +type EventFilterAttribute = "resource" | "topic" | "bucket" | string; + +// One or more event filters restrict the set of events delivered to an EventTrigger. +interface EventFilter { + attribute: EventFilterAttribute; + value: string; + + // if left unspecified, equality is used. + operator?: "match-path-pattern"; +} /** API agnostic version of a Cloud Function's event trigger. */ export interface EventTrigger { @@ -72,7 +80,7 @@ export interface EventTrigger { * V2 will have arbitrary filters and some EventArc filters will be * top-level keys in the GCF API (e.g. "pubsubTopic"). */ - eventFilters: Record; + eventFilters: EventFilter[]; /** Should failures in a function execution cause an event to be retried. */ retry: boolean; @@ -560,7 +568,16 @@ export const missingEndpoint = return !hasEndpoint(backend)(endpoint); }; -/** A standard method for sorting endpoints for display. +/** A helper utility to find event filter of given attribute */ +export function findEventFilter( + endpoint: Endpoint & EventTriggered, + attribute: EventFilterAttribute +): EventFilter | undefined { + return endpoint.eventTrigger.eventFilters.find((ef) => ef.attribute === attribute); +} + +/** + * A standard method for sorting endpoints for display. * Future versions might consider sorting region by pricing tier before * alphabetically */ diff --git a/src/deploy/functions/eventTypes.ts b/src/deploy/functions/eventTypes.ts deleted file mode 100644 index ba907cd4845..00000000000 --- a/src/deploy/functions/eventTypes.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const STORAGE_V2_EVENTS = [ - "google.cloud.storage.object.v1.finalized", - "google.cloud.storage.object.v1.archived", - "google.cloud.storage.object.v1.deleted", - "google.cloud.storage.object.v1.metadataUpdated", -]; - -export const PUBSUB_V2_EVENT = "google.cloud.pubsub.topic.v1.messagePublished"; diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index c2f38154af1..39029af9734 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -1,10 +1,9 @@ -import { functionMatchesAnyGroup } from "../functionsDeployHelper"; -import { getFunctionLabel } from "../functionsDeployHelper"; +import { functionMatchesAnyGroup, getFunctionLabel } from "../functionsDeployHelper"; import { isFirebaseManaged } from "../../../deploymentTool"; import { FirebaseError } from "../../../error"; import * as utils from "../../../utils"; import * as backend from "../backend"; -import * as gcfv2 from "../../../gcp/cloudfunctionsv2"; +import * as v2events from "../../../functions/events/v2"; export interface EndpointUpdate { endpoint: backend.Endpoint; @@ -165,13 +164,16 @@ export function changedV2PubSubTopic(want: backend.Endpoint, have: backend.Endpo if (!backend.isEventTriggered(have)) { return false; } - if (want.eventTrigger.eventType !== gcfv2.PUBSUB_PUBLISH_EVENT) { + if (want.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) { return false; } - if (have.eventTrigger.eventType !== gcfv2.PUBSUB_PUBLISH_EVENT) { + if (have.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) { return false; } - return have.eventTrigger.eventFilters["resource"] !== want.eventTrigger.eventFilters["resource"]; + + return ( + backend.findEventFilter(have, "topic")?.value !== backend.findEventFilter(want, "topic")?.value + ); } /** Whether a user upgraded a scheduled function (which goes from Pub/Sub to HTTPS). */ diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 503423dc49c..0ae3d0de8d0 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -121,13 +121,19 @@ function parseEndpoints( if (backend.isEventTriggered(ep)) { requireKeys(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters"); assertKeyTypes(prefix + ".eventTrigger", ep.eventTrigger, { - eventFilters: "object", + eventFilters: "array", eventType: "string", retry: "boolean", region: "string", serviceAccountEmail: "string", }); triggered = { eventTrigger: ep.eventTrigger }; + for (const eventFilter of triggered.eventTrigger.eventFilters) { + if (eventFilter.attribute === "topic" && !eventFilter.value.startsWith("projects/")) { + // Construct full pubsub topic name. + eventFilter.value = `projects/${project}/topics/${eventFilter.value}`; + } + } } else if (backend.isHttpsTriggered(ep)) { assertKeyTypes(prefix + ".httpsTrigger", ep.httpsTrigger, { invoker: "array", diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 9c57d892ff0..8c689703f8f 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -9,7 +9,7 @@ import * as api from "../../../../api"; import * as proto from "../../../../gcp/proto"; import * as args from "../../args"; import * as runtimes from "../../runtimes"; -import { STORAGE_V2_EVENTS } from "../../eventTypes"; +import * as v2events from "../../../../functions/events/v2"; const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js"); @@ -131,7 +131,7 @@ function parseTriggers( }); } -// Currently we always use JS trigger parsing +/** Currently we always use JS trigger parsing */ export function useStrategy(context: args.Context): Promise { return Promise.resolve(true); } @@ -211,19 +211,40 @@ export function addResourcesToBackend( triggered = { eventTrigger: { eventType: annotation.eventTrigger!.eventType, - eventFilters: { - resource: annotation.eventTrigger!.resource, - }, + eventFilters: [ + { + attribute: "resource", + value: annotation.eventTrigger!.resource, + }, + ], retry: !!annotation.failurePolicy, }, }; // TODO: yank this edge case for a v2 trigger on the pre-container contract // once we use container contract for the functionsv2 experiment. - if (STORAGE_V2_EVENTS.find((event) => event === (annotation.eventTrigger?.eventType || ""))) { - triggered.eventTrigger.eventFilters = { - bucket: annotation.eventTrigger!.resource, - }; + if (annotation.platform === "gcfv2") { + if (annotation.eventTrigger!.eventType === v2events.PUBSUB_PUBLISH_EVENT) { + triggered.eventTrigger.eventFilters = [ + { + attribute: "topic", + value: annotation.eventTrigger!.resource, + }, + ]; + } + + if ( + v2events.STORAGE_EVENTS.find( + (event) => event === (annotation.eventTrigger?.eventType || "") + ) + ) { + triggered.eventTrigger.eventFilters = [ + { + attribute: "bucket", + value: annotation.eventTrigger!.resource, + }, + ]; + } } } diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 434e647b4b8..1e6abb6eb9f 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -1,5 +1,6 @@ import * as backend from "../backend"; import * as iam from "../../../gcp/iam"; +import * as v2events from "../../../functions/events/v2"; import { obtainStorageBindings, ensureStorageTriggerRegion } from "./storage"; const noop = (): Promise => Promise.resolve(); @@ -11,7 +12,7 @@ export interface Service { // dispatch functions requiredProjectBindings: ((pId: any, p: any) => Promise>) | undefined; - ensureTriggerRegion: (ep: backend.Endpoint, et: backend.EventTrigger) => Promise; + ensureTriggerRegion: (ep: backend.Endpoint & backend.EventTriggered) => Promise; } /** A noop service object, useful for v1 events */ @@ -37,7 +38,7 @@ export const StorageService = { }; /** Mapping from event type string to service object */ -export const EVENT_SERVICE_MAPPING: Record = { +export const EVENT_SERVICE_MAPPING: Record = { "google.cloud.pubsub.topic.v1.messagePublished": PubSubService, "google.cloud.storage.object.v1.finalized": StorageService, "google.cloud.storage.object.v1.archived": StorageService, @@ -48,12 +49,12 @@ export const EVENT_SERVICE_MAPPING: Record = { /** * Find the Service object for the given endpoint * @param endpoint the endpoint that we want the service for - * @returns a Service object that corresponds to the event type of the endpoint or noop + * @return a Service object that corresponds to the event type of the endpoint or noop */ export function serviceForEndpoint(endpoint: backend.Endpoint): Service { if (!backend.isEventTriggered(endpoint)) { return NoOpService; } - return EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType] || NoOpService; + return EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType as v2events.Event] || NoOpService; } diff --git a/src/deploy/functions/services/storage.ts b/src/deploy/functions/services/storage.ts index 1dd282d24c5..200a3a995d6 100644 --- a/src/deploy/functions/services/storage.ts +++ b/src/deploy/functions/services/storage.ts @@ -38,15 +38,19 @@ export async function obtainStorageBindings( * @param eventTrigger the endpoints event trigger */ export async function ensureStorageTriggerRegion( - endpoint: backend.Endpoint, - eventTrigger: backend.EventTrigger + endpoint: backend.Endpoint & backend.EventTriggered ): Promise { + const { eventTrigger } = endpoint; if (!eventTrigger.region) { logger.debug("Looking up bucket region for the storage event trigger"); - try { - const bucket: { location: string } = await storage.getBucket( - eventTrigger.eventFilters.bucket! + const bucketFilter = backend.findEventFilter(endpoint, "bucket"); + if (!bucketFilter) { + throw new FirebaseError( + "Storage event trigger unexpectedly missing event filter with bucket attribute." ); + } + try { + const bucket: { location: string } = await storage.getBucket(bucketFilter.value); eventTrigger.region = bucket.location.toLowerCase(); logger.debug("Setting the event trigger region to", eventTrigger.region, "."); } catch (err: any) { @@ -57,7 +61,7 @@ export async function ensureStorageTriggerRegion( if ( endpoint.region !== eventTrigger.region && eventTrigger.region !== "us-central1" && // GCF allows any trigger to be in us-central1 - !regionInLocation(endpoint.region, eventTrigger.region!) + !regionInLocation(endpoint.region, eventTrigger.region) ) { throw new FirebaseError( `A function in region ${endpoint.region} cannot listen to a bucket in region ${eventTrigger.region}` diff --git a/src/deploy/functions/triggerRegionHelper.ts b/src/deploy/functions/triggerRegionHelper.ts index 2842c9f6bae..01713629431 100644 --- a/src/deploy/functions/triggerRegionHelper.ts +++ b/src/deploy/functions/triggerRegionHelper.ts @@ -13,7 +13,7 @@ export async function ensureTriggerRegions(want: backend.Backend): Promise if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)) { continue; } - regionLookups.push(serviceForEndpoint(ep).ensureTriggerRegion(ep, ep.eventTrigger)); + regionLookups.push(serviceForEndpoint(ep).ensureTriggerRegion(ep)); } await Promise.all(regionLookups); } diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 8cf751a52b2..668b8e1d521 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -5,23 +5,17 @@ import * as path from "path"; import * as express from "express"; import * as fs from "fs"; +import * as backend from "../deploy/functions/backend"; import { Constants } from "./constants"; import { InvokeRuntimeOpts } from "./functionsEmulator"; -import { - Endpoint, - FunctionsPlatform, - isEventTriggered, - isHttpsTriggered, - isScheduleTriggered, - SecretEnvVar, -} from "../deploy/functions/backend"; import { copyIfPresent } from "../gcp/proto"; +import { logger } from "../logger"; export type SignatureType = "http" | "event" | "cloudevent"; export interface ParsedTriggerDefinition { entryPoint: string; - platform: FunctionsPlatform; + platform: backend.FunctionsPlatform; name: string; timeout?: string | number; // Can be "3s" for some reason lol regions?: string[]; @@ -35,7 +29,7 @@ export interface ParsedTriggerDefinition { export interface EmulatedTriggerDefinition extends ParsedTriggerDefinition { id: string; // An unique-id per-function, generated from the name and the region. region: string; - secretEnvironmentVariables?: SecretEnvVar[]; // Secret env vars needs to be specially loaded in the Emulator. + secretEnvironmentVariables?: backend.SecretEnvVar[]; // Secret env vars needs to be specially loaded in the Emulator. } export interface EventSchedule { @@ -133,7 +127,9 @@ export class EmulatedTrigger { * @param Endpoints A list of all CloudFunctions in the deployment. * @return A list of all CloudFunctions in the deployment. */ -export function emulatedFunctionsFromEndpoints(endpoints: Endpoint[]): EmulatedTriggerDefinition[] { +export function emulatedFunctionsFromEndpoints( + endpoints: backend.Endpoint[] +): EmulatedTriggerDefinition[] { const regionDefinitions: EmulatedTriggerDefinition[] = []; for (const endpoint of endpoints) { if (!endpoint.region) { @@ -160,29 +156,42 @@ export function emulatedFunctionsFromEndpoints(endpoints: Endpoint[]): EmulatedT ); // TODO: This transformation is confusing but must be kept since the Firestore/RTDB trigger registration // process requires it in this form. Need to work in Firestore emulator for a proper fix... - if (isHttpsTriggered(endpoint)) { + if (backend.isHttpsTriggered(endpoint)) { def.httpsTrigger = endpoint.httpsTrigger; - } else if (isEventTriggered(endpoint)) { + } else if (backend.isEventTriggered(endpoint)) { const eventTrigger = endpoint.eventTrigger; if (endpoint.platform === "gcfv1") { + const resourceFilter = backend.findEventFilter(endpoint, "resource"); + if (!resourceFilter) { + logger.debug( + `Invalid event trigger ${JSON.stringify( + endpoint + )}, expected event filter with resource attribute. Skipping.` + ); + // Silently skip invalid trigger. + continue; + } def.eventTrigger = { eventType: eventTrigger.eventType, - resource: eventTrigger.eventFilters.resource, + resource: resourceFilter.value, }; } else { - // Only pubsub and storage events are supported for gcfv2. - const { resource, topic, bucket } = endpoint.eventTrigger.eventFilters; - const eventResource = resource || topic || bucket; - if (!eventResource) { - // Unsupported event type for GCFv2 + const [eventFilter] = endpoint.eventTrigger.eventFilters; + if (!eventFilter) { + logger.debug( + `Invalid event trigger ${JSON.stringify( + endpoint + )}, expected at least one event filter. Skipping.` + ); + // Silently skip invalid trigger. continue; } def.eventTrigger = { eventType: eventTrigger.eventType, - resource: eventResource, + resource: eventFilter.value, }; } - } else if (isScheduleTriggered(endpoint)) { + } else if (backend.isScheduleTriggered(endpoint)) { // TODO: This is an awkward transformation. Emulator does not understand scheduled triggers - maybe it should? def.eventTrigger = { eventType: "pubsub", resource: "" }; def.schedule = endpoint.scheduleTrigger as EventSchedule; diff --git a/src/functions/events/v2.ts b/src/functions/events/v2.ts new file mode 100644 index 00000000000..abfab630e99 --- /dev/null +++ b/src/functions/events/v2.ts @@ -0,0 +1,10 @@ +export const PUBSUB_PUBLISH_EVENT = "google.cloud.pubsub.topic.v1.messagePublished" as const; + +export const STORAGE_EVENTS = [ + "google.cloud.storage.object.v1.finalized", + "google.cloud.storage.object.v1.archived", + "google.cloud.storage.object.v1.deleted", + "google.cloud.storage.object.v1.metadataUpdated", +] as const; + +export type Event = typeof PUBSUB_PUBLISH_EVENT | typeof STORAGE_EVENTS[number]; diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index e731f55f28f..8f5260222d3 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -482,9 +482,12 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { eventTrigger: { eventType: gcfFunction.eventTrigger!.eventType, - eventFilters: { - resource: gcfFunction.eventTrigger!.resource, - }, + eventFilters: [ + { + attribute: "resource", + value: gcfFunction.eventTrigger!.resource, + }, + ], retry: !!gcfFunction.eventTrigger!.failurePolicy?.retry, }, }; @@ -563,9 +566,15 @@ export function functionFromEndpoint( proto.copyIfPresent(gcfFunction, endpoint, "labels"); if (backend.isEventTriggered(endpoint)) { + const resourceFilter = backend.findEventFilter(endpoint, "resource"); + if (!resourceFilter) { + throw new FirebaseError( + "Invalid event trigger definition. Expected event filter with 'resource' attribute." + ); + } gcfFunction.eventTrigger = { eventType: endpoint.eventTrigger.eventType, - resource: endpoint.eventTrigger.eventFilters.resource, + resource: resourceFilter.value, // Service is unnecessary and deprecated }; diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 01051b08594..83c2db6296d 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -4,6 +4,7 @@ import { Client } from "../apiv2"; import { FirebaseError } from "../error"; import { functionsV2Origin } from "../api"; import { logger } from "../logger"; +import { PUBSUB_PUBLISH_EVENT } from "../functions/events/v2"; import * as backend from "../deploy/functions/backend"; import * as runtimes from "../deploy/functions/runtimes"; import * as proto from "./proto"; @@ -17,8 +18,6 @@ const client = new Client({ apiVersion: API_VERSION, }); -export const PUBSUB_PUBLISH_EVENT = "google.cloud.pubsub.topic.v1.messagePublished"; - export type VpcConnectorEgressSettings = "PRIVATE_RANGES_ONLY" | "ALL_TRAFFIC"; export type IngressSettings = "ALLOW_ALL" | "ALLOW_INTERNAL_ONLY" | "ALLOW_INTERNAL_AND_GCLB"; export type FunctionState = "ACTIVE" | "FAILED" | "DEPLOYING" | "DELETING" | "UNKONWN"; @@ -436,12 +435,25 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage eventType: endpoint.eventTrigger.eventType, }; if (gcfFunction.eventTrigger.eventType === PUBSUB_PUBLISH_EVENT) { - gcfFunction.eventTrigger.pubsubTopic = endpoint.eventTrigger.eventFilters.resource; - } else { - gcfFunction.eventTrigger.eventFilters = []; - for (const [attribute, value] of Object.entries(endpoint.eventTrigger.eventFilters)) { - gcfFunction.eventTrigger.eventFilters.push({ attribute, value }); + const pubsubFilter = backend.findEventFilter(endpoint, "topic"); + if (!pubsubFilter) { + throw new FirebaseError( + "Invalid pubsub endpoint. Expected eventFilter with 'topic' attribute but found none." + ); } + gcfFunction.eventTrigger.pubsubTopic = pubsubFilter.value; + + for (const filter of endpoint.eventTrigger.eventFilters) { + if (filter.attribute === "topic") { + continue; + } + if (!gcfFunction.eventTrigger.eventFilters) { + gcfFunction.eventTrigger.eventFilters = []; + } + gcfFunction.eventTrigger.eventFilters.push(filter); + } + } else { + gcfFunction.eventTrigger.eventFilters = endpoint.eventTrigger.eventFilters; } proto.renameIfPresent( gcfFunction.eventTrigger, @@ -488,15 +500,18 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { eventTrigger: { eventType: gcfFunction.eventTrigger.eventType, - eventFilters: {}, + eventFilters: [], retry: false, }, }; if (gcfFunction.eventTrigger.pubsubTopic) { - trigger.eventTrigger.eventFilters.resource = gcfFunction.eventTrigger.pubsubTopic; + trigger.eventTrigger.eventFilters.push({ + attribute: "topic", + value: gcfFunction.eventTrigger.pubsubTopic, + }); } else { for (const { attribute, value } of gcfFunction.eventTrigger.eventFilters || []) { - trigger.eventTrigger.eventFilters[attribute] = value; + trigger.eventTrigger.eventFilters.push({ attribute, value }); } } proto.renameIfPresent( diff --git a/src/test/deploy/functions/checkIam.spec.ts b/src/test/deploy/functions/checkIam.spec.ts index 2c4da1473fc..a0cd0ce32e4 100644 --- a/src/test/deploy/functions/checkIam.spec.ts +++ b/src/test/deploy/functions/checkIam.spec.ts @@ -94,9 +94,12 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, }, ...SPEC, @@ -117,9 +120,12 @@ describe("checkIam", () => { platform: "gcfv1", eventTrigger: { eventType: "google.storage.object.create", - eventFilters: { - resource: "projects/_/buckets/myBucket", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/_/buckets/myBucket", + }, + ], retry: false, }, ...SPEC, @@ -137,9 +143,12 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, }, ...SPEC, @@ -163,9 +172,12 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, }, ...SPEC, @@ -176,9 +188,12 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.metadataUpdated", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, }, ...SPEC, @@ -216,9 +231,12 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, }, ...SPEC, diff --git a/src/test/deploy/functions/prepare.spec.ts b/src/test/deploy/functions/prepare.spec.ts index 24a00941c65..e2234f563c5 100644 --- a/src/test/deploy/functions/prepare.spec.ts +++ b/src/test/deploy/functions/prepare.spec.ts @@ -79,9 +79,12 @@ describe("prepare", () => { ...ENDPOINT_BASE, eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "bucket", + }, + ], retry: false, }, }; @@ -98,15 +101,18 @@ describe("prepare", () => { ...ENDPOINT_BASE, eventTrigger: { eventType: "google.cloud.storage.object.v1.finalzied", - eventFilters: { - bucket: "us-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "us-bucket", + }, + ], retry: false, }, }; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const have: backend.Endpoint & backend.EventTriggered = JSON.parse(JSON.stringify(want)); - have.eventTrigger.eventFilters["bucket"] = "us-central1-bucket"; + have.eventTrigger.eventFilters = [{ attribute: "bucket", value: "us-central1-bucket" }]; have.eventTrigger.region = "us-central1"; prepare.inferDetailsFromExisting(backend.of(want), backend.of(have), /* usedDotEnv= */ false); diff --git a/src/test/deploy/functions/prompts.spec.ts b/src/test/deploy/functions/prompts.spec.ts index eb25cf5f4dc..42b11146aa7 100644 --- a/src/test/deploy/functions/prompts.spec.ts +++ b/src/test/deploy/functions/prompts.spec.ts @@ -11,9 +11,12 @@ import { RC } from "../../../rc"; const SAMPLE_EVENT_TRIGGER: backend.EventTrigger = { eventType: "google.pubsub.topic.publish", - eventFilters: { - resource: "projects/a/topics/b", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/a/topics/b", + }, + ], retry: false, }; diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index ac55749cded..c670b8caf55 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -14,6 +14,7 @@ import * as cloudtasksNS from "../../../../gcp/cloudtasks"; import * as backend from "../../../../deploy/functions/backend"; import * as scraper from "../../../../deploy/functions/release/sourceTokenScraper"; import * as planner from "../../../../deploy/functions/release/planner"; +import * as v2events from "../../../../functions/events/v2"; describe("Fabricator", () => { // Stub all GCP APIs to make sure this test is hermetic @@ -309,10 +310,13 @@ describe("Fabricator", () => { const ep = endpoint( { eventTrigger: { - eventType: gcfv2.PUBSUB_PUBLISH_EVENT, - eventFilters: { - resource: "topic", - }, + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: [ + { + attribute: "topic", + value: "topic", + }, + ], retry: false, }, }, @@ -332,10 +336,13 @@ describe("Fabricator", () => { const ep = endpoint( { eventTrigger: { - eventType: gcfv2.PUBSUB_PUBLISH_EVENT, - eventFilters: { - resource: "topic", - }, + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: [ + { + attribute: "topic", + value: "topic", + }, + ], retry: false, }, }, @@ -724,10 +731,13 @@ describe("Fabricator", () => { // all APIs throw by default const ep = endpoint({ eventTrigger: { - eventType: gcfNSV2.PUBSUB_PUBLISH_EVENT, - eventFilters: { - resource: "topic", - }, + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: [ + { + attribute: "topic", + value: "topic", + }, + ], retry: false, }, }); @@ -777,10 +787,13 @@ describe("Fabricator", () => { // all APIs throw by default const ep = endpoint({ eventTrigger: { - eventType: gcfNSV2.PUBSUB_PUBLISH_EVENT, - eventFilters: { - resource: "topic", - }, + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: [ + { + attribute: "topic", + value: "topic", + }, + ], retry: false, }, }); diff --git a/src/test/deploy/functions/release/planner.spec.ts b/src/test/deploy/functions/release/planner.spec.ts index d5dd56b6bbc..9bab37ab298 100644 --- a/src/test/deploy/functions/release/planner.spec.ts +++ b/src/test/deploy/functions/release/planner.spec.ts @@ -4,8 +4,8 @@ import * as sinon from "sinon"; import * as backend from "../../../../deploy/functions/backend"; import * as planner from "../../../../deploy/functions/release/planner"; import * as deploymentTool from "../../../../deploymentTool"; -import * as gcfv2 from "../../../../gcp/cloudfunctionsv2"; import * as utils from "../../../../utils"; +import * as v2events from "../../../../functions/events/v2"; describe("planner", () => { let logLabeledBullet: sinon.SinonStub; @@ -50,10 +50,13 @@ describe("planner", () => { const original: backend.Endpoint = { ...func("a", "b", { eventTrigger: { - eventType: gcfv2.PUBSUB_PUBLISH_EVENT, - eventFilters: { - resource: "topic", - }, + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: [ + { + attribute: "topic", + value: "topic", + }, + ], retry: false, }, }), @@ -61,7 +64,7 @@ describe("planner", () => { }; const changed = JSON.parse(JSON.stringify(original)) as backend.Endpoint; if (backend.isEventTriggered(changed)) { - changed.eventTrigger.eventFilters["resource"] = "anotherTopic"; + changed.eventTrigger.eventFilters = [{ attribute: "topic", value: "anotherTopic" }]; } expect(planner.calculateUpdate(changed, original)).to.deep.equal({ endpoint: changed, @@ -86,10 +89,13 @@ describe("planner", () => { it("knows to delete & recreate when trigger regions change", () => { const original: backend.Endpoint = func("a", "b", { eventTrigger: { - eventType: "google.cloud.storage.object.v1.finalzied", - eventFilters: { - bucket: "mybucket", - }, + eventType: "google.cloud.storage.object.v1.finalized", + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], region: "us-west1", retry: false, }, @@ -98,9 +104,12 @@ describe("planner", () => { const changed: backend.Endpoint = func("a", "b", { eventTrigger: { eventType: "google.cloud.storage.object.v1.finalzied", - eventFilters: { - bucket: "bucket2", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], region: "us", retry: false, }, @@ -258,7 +267,7 @@ describe("planner", () => { const want = func("a", "b", { eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: {}, + eventFilters: [], retry: false, }, }); @@ -272,7 +281,7 @@ describe("planner", () => { const have = func("a", "b", { eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: {}, + eventFilters: [], retry: false, }, }); @@ -290,7 +299,7 @@ describe("planner", () => { it("should not throw if a event triggered function keeps the same trigger", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.publish", - eventFilters: {}, + eventFilters: [], retry: false, }; const want = func("a", "b", { eventTrigger }); @@ -322,10 +331,13 @@ describe("planner", () => { it("detects changes to v2 pubsub topics", () => { const eventTrigger: backend.EventTrigger = { - eventType: gcfv2.PUBSUB_PUBLISH_EVENT, - eventFilters: { - resource: "projects/p/topics/t", - }, + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: [ + { + attribute: "topic", + value: "projects/p/topic/t", + }, + ], retry: false, }; @@ -355,7 +367,7 @@ describe("planner", () => { // to modify only 'want' want = JSON.parse(JSON.stringify(want)) as backend.Endpoint; if (backend.isEventTriggered(want)) { - want.eventTrigger.eventFilters.resource = "projects/p/topics/t2"; + want.eventTrigger.eventFilters = [{ attribute: "topic", value: "projects/p/topics/t2" }]; } expect(planner.changedV2PubSubTopic(want, have)).to.be.true; }); diff --git a/src/test/deploy/functions/release/reporter.spec.ts b/src/test/deploy/functions/release/reporter.spec.ts index d2727022e7d..7df26b767e1 100644 --- a/src/test/deploy/functions/release/reporter.spec.ts +++ b/src/test/deploy/functions/release/reporter.spec.ts @@ -88,7 +88,7 @@ describe("reporter", () => { platform: "gcfv2", eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: {}, + eventFilters: [], retry: false, }, }) diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index c759d49e554..6e881e96370 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -99,7 +99,12 @@ describe("backendFromV1Alpha1", () => { describe("Event triggers", () => { const validTrigger: backend.EventTrigger = { eventType: "google.pubsub.v1.topic.publish", - eventFilters: { resource: "projects/p/topics/t" }, + eventFilters: [ + { + attribute: "resource", + value: "projects/p/topics/t", + }, + ], retry: true, region: "global", serviceAccountEmail: "root@", @@ -322,9 +327,12 @@ describe("backendFromV1Alpha1", () => { it("copies event triggers", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.v1.publish", - eventFilters: { - resource: "projects/project/topics/topic", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/project/topics/topic", + }, + ], region: "us-central1", serviceAccountEmail: "sa@", retry: true, @@ -343,6 +351,44 @@ describe("backendFromV1Alpha1", () => { expect(parsed).to.deep.equal(expected); }); + it("copies event triggers with full resource path", () => { + const eventTrigger: backend.EventTrigger = { + eventType: "google.pubsub.topic.v1.publish", + eventFilters: [ + { + attribute: "topic", + value: "my-topic", + }, + ], + region: "us-central1", + serviceAccountEmail: "sa@", + retry: true, + }; + const yaml: v1alpha1.Manifest = { + specVersion: "v1alpha1", + endpoints: { + id: { + ...MIN_ENDPOINT, + eventTrigger, + }, + }, + }; + const expected = backend.of({ + ...DEFAULTED_ENDPOINT, + eventTrigger: { + ...eventTrigger, + eventFilters: [ + { + attribute: "topic", + value: `projects/${PROJECT}/topics/my-topic`, + }, + ], + }, + }); + const parsed = v1alpha1.backendFromV1Alpha1(yaml, PROJECT, REGION, RUNTIME); + expect(parsed).to.deep.equal(expected); + }); + it("copies optional fields", () => { const fields: backend.ServiceConfiguration = { concurrency: 42, diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index a1c504a1c3f..942e88d6bb0 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -108,9 +108,12 @@ describe("addResourcesToBackend", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.publish", - eventFilters: { - resource: "projects/project/topics/topic", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/project/topics/topic", + }, + ], retry: !!failurePolicy, }; const expected: backend.Backend = backend.of({ ...BASIC_ENDPOINT, eventTrigger }); @@ -179,9 +182,12 @@ describe("addResourcesToBackend", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.publish", - eventFilters: { - resource: "projects/p/topics/t", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/p/topics/t", + }, + ], retry: false, }; diff --git a/src/test/deploy/functions/triggerRegionHelper.spec.ts b/src/test/deploy/functions/triggerRegionHelper.spec.ts index 92803844c01..c58ffbd7b14 100644 --- a/src/test/deploy/functions/triggerRegionHelper.spec.ts +++ b/src/test/deploy/functions/triggerRegionHelper.spec.ts @@ -30,9 +30,12 @@ describe("TriggerRegionHelper", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, }, ...SPEC, @@ -50,9 +53,12 @@ describe("TriggerRegionHelper", () => { platform: "gcfv1", eventTrigger: { eventType: "google.storage.object.create", - eventFilters: { - resource: "projects/_/buckets/myBucket", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/_/buckets/my-bucket", + }, + ], retry: false, }, ...SPEC, @@ -69,9 +75,12 @@ describe("TriggerRegionHelper", () => { expect(v1EventFn.eventTrigger).to.deep.eq({ eventType: "google.storage.object.create", - eventFilters: { - resource: "projects/_/buckets/myBucket", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/_/buckets/my-bucket", + }, + ], retry: false, }); expect(v2CallableFn.httpsTrigger).to.deep.eq({}); @@ -85,9 +94,12 @@ describe("TriggerRegionHelper", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, }, ...SPEC, @@ -97,9 +109,12 @@ describe("TriggerRegionHelper", () => { expect(wantFn.eventTrigger).to.deep.eq({ eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, region: "us", }); @@ -113,9 +128,12 @@ describe("TriggerRegionHelper", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { - bucket: "my-bucket", - }, + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], retry: false, }, ...SPEC, diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index 34285fccce5..d0311b5c5b6 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -68,9 +68,12 @@ describe("cloudfunctions", () => { ...ENDPOINT, eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: { - resource: "projects/p/topics/t", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/p/topics/t", + }, + ], retry: false, }, }; @@ -208,9 +211,12 @@ describe("cloudfunctions", () => { ...ENDPOINT, eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: { - resource: "projects/p/topics/t", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/p/topics/t", + }, + ], retry: true, }, }); @@ -228,9 +234,12 @@ describe("cloudfunctions", () => { ...ENDPOINT, eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: { - resource: "projects/p/topics/t", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/p/topics/t", + }, + ], retry: false, }, }); diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 1725b2238b8..7bd184470e0 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -2,6 +2,7 @@ import { expect } from "chai"; import * as cloudfunctionsv2 from "../../gcp/cloudfunctionsv2"; import * as backend from "../../deploy/functions/backend"; +import * as v2events from "../../functions/events/v2"; describe("cloudfunctionsv2", () => { const FUNCTION_NAME: backend.TargetIds = { @@ -92,10 +93,16 @@ describe("cloudfunctionsv2", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.audit.log.v1.written", - eventFilters: { - resource: "projects/p/regions/r/instances/i", - serviceName: "compute.googleapis.com", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/p/regions/r/instances/i", + }, + { + attribute: "serviceName", + value: "compute.googleapis.com", + }, + ], retry: false, }, }; @@ -192,10 +199,17 @@ describe("cloudfunctionsv2", () => { ...ENDPOINT, platform: "gcfv2", eventTrigger: { - eventType: cloudfunctionsv2.PUBSUB_PUBLISH_EVENT, - eventFilters: { - resource: "projects/p/topics/t", - }, + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: [ + { + attribute: "topic", + value: "projects/p/topics/t", + }, + { + attribute: "serviceName", + value: "pubsub.googleapis.com", + }, + ], retry: false, }, maxInstances: 42, @@ -210,8 +224,14 @@ describe("cloudfunctionsv2", () => { > = { ...CLOUD_FUNCTION_V2, eventTrigger: { - eventType: cloudfunctionsv2.PUBSUB_PUBLISH_EVENT, + eventType: v2events.PUBSUB_PUBLISH_EVENT, pubsubTopic: "projects/p/topics/t", + eventFilters: [ + { + attribute: "serviceName", + value: "pubsub.googleapis.com", + }, + ], }, serviceConfig: { ...CLOUD_FUNCTION_V2.serviceConfig, @@ -244,7 +264,7 @@ describe("cloudfunctionsv2", () => { cloudfunctionsv2.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION_V2, eventTrigger: { - eventType: cloudfunctionsv2.PUBSUB_PUBLISH_EVENT, + eventType: v2events.PUBSUB_PUBLISH_EVENT, pubsubTopic: "projects/p/topics/t", }, }) @@ -253,10 +273,13 @@ describe("cloudfunctionsv2", () => { platform: "gcfv2", uri: RUN_URI, eventTrigger: { - eventType: cloudfunctionsv2.PUBSUB_PUBLISH_EVENT, - eventFilters: { - resource: "projects/p/topics/t", - }, + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: [ + { + attribute: "topic", + value: "projects/p/topics/t", + }, + ], retry: false, }, }); @@ -285,10 +308,16 @@ describe("cloudfunctionsv2", () => { uri: RUN_URI, eventTrigger: { eventType: "google.cloud.audit.log.v1.written", - eventFilters: { - resource: "projects/p/regions/r/instances/i", - serviceName: "compute.googleapis.com", - }, + eventFilters: [ + { + attribute: "resource", + value: "projects/p/regions/r/instances/i", + }, + { + attribute: "serviceName", + value: "compute.googleapis.com", + }, + ], retry: false, }, }); From 5d3d6b955d0ae115532f4ab5f0315df76316553a Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Mon, 7 Mar 2022 13:09:09 -0500 Subject: [PATCH 0128/1699] Move upload logic to UploadService (#4250) * multipart and firebase.ts integration * update gcloud.ts * refactor persistence out into StorageEmulator * fix lint and method references * fix tests * upload.ts * update callers of UploadService and tests * more tests * more tests * md -> metadata * address pr comments * lint * fix tests * fix tests --- src/emulator/storage/apis/firebase.ts | 289 ++++++++---------- src/emulator/storage/apis/gcloud.ts | 100 +++--- src/emulator/storage/files.ts | 127 +++----- src/emulator/storage/index.ts | 10 +- src/emulator/storage/persistence.ts | 3 +- src/emulator/storage/upload.ts | 182 +++++++++++ src/test/emulators/storage/files.spec.ts | 140 +++++---- .../emulators/storage/persistence.spec.ts | 33 ++ src/test/emulators/storage/upload.spec.ts | 27 ++ 9 files changed, 549 insertions(+), 362 deletions(-) create mode 100644 src/emulator/storage/upload.ts create mode 100644 src/test/emulators/storage/persistence.spec.ts create mode 100644 src/test/emulators/storage/upload.spec.ts diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index fc5df887bdb..210102ba02d 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -10,6 +10,7 @@ import { RulesetOperationMethod } from "../rules/types"; import { parseObjectUploadMultipartRequest } from "../multipart"; import { NotFoundError, ForbiddenError } from "../errors"; import { isPermitted } from "../rules/utils"; +import { NotCancellableError, Upload, UploadNotActiveError } from "../upload"; /** * @param emulator @@ -17,7 +18,7 @@ import { isPermitted } from "../rules/utils"; export function createFirebaseEndpoints(emulator: StorageEmulator): Router { // eslint-disable-next-line new-cap const firebaseStorageAPI = Router(); - const { storageLayer } = emulator; + const { storageLayer, uploadService } = emulator; if (process.env.STORAGE_EMULATOR_DEBUG) { firebaseStorageAPI.use((req, res, next) => { @@ -229,11 +230,11 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }; const handleUpload = async (req: Request, res: Response) => { + const bucketId = req.params.bucketId; if (req.query.create_token || req.query.delete_token) { const decodedObjectId = decodeURIComponent(req.params.objectId); - const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/"); - - const mdBefore = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); + const operationPath = ["b", bucketId, "o", decodedObjectId].join("/"); + const metadataBefore = storageLayer.getMetadata(bucketId, req.params.objectId); if ( !(await isPermitted({ @@ -242,7 +243,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { path: operationPath, authorization: req.header("authorization"), file: { - before: mdBefore?.asRulesResource(), + before: metadataBefore?.asRulesResource(), // TODO: before and after w/ metadata change }, })) @@ -255,7 +256,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }); } - if (!mdBefore) { + if (!metadataBefore) { return res.status(404).json({ error: { code: 404, @@ -266,29 +267,29 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { const createTokenParam = req.query["create_token"]; const deleteTokenParam = req.query["delete_token"]; - let md: StoredFileMetadata | undefined; + let metadata: StoredFileMetadata | undefined; if (createTokenParam) { if (createTokenParam !== "true") { res.sendStatus(400); return; } - md = storageLayer.addDownloadToken(req.params.bucketId, req.params.objectId); + metadata = storageLayer.addDownloadToken(req.params.bucketId, req.params.objectId); } else if (deleteTokenParam) { - md = storageLayer.deleteDownloadToken( + metadata = storageLayer.deleteDownloadToken( req.params.bucketId, req.params.objectId, deleteTokenParam.toString() ); } - if (!md) { + if (!metadata) { res.sendStatus(404); return; } - setObjectHeaders(res, md); - return res.json(new OutgoingFirebaseMetadata(md)); + setObjectHeaders(res, metadata); + return res.json(new OutgoingFirebaseMetadata(metadata)); } if (!req.query.name) { @@ -296,7 +297,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return; } - const name = req.query.name.toString(); + const objectId = req.query.name.toString(); const uploadType = req.header("x-goog-upload-protocol"); if (uploadType === "multipart") { @@ -323,159 +324,130 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } throw err; } - const metadata = JSON.parse(metadataRaw)!; - const md = storageLayer.oneShotUpload( - req.params.bucketId, - name, - metadata.contentType!, - metadata, - dataRaw - ); - - if (!md) { - res.sendStatus(400); - return; - } - - const operationPath = ["b", req.params.bucketId, "o", name].join("/"); - - if ( - !(await isPermitted({ - ruleset: emulator.rules, - // TODO: This will be either create or update - method: RulesetOperationMethod.CREATE, - path: operationPath, - authorization: req.header("authorization"), - file: { - after: md?.asRulesResource(), - }, - })) - ) { - storageLayer.deleteFile(md?.bucket, md?.name); - return res.status(403).json({ - error: { - code: 403, - message: `Permission denied. No WRITE permission.`, - }, - }); - } - - if (md.downloadTokens.length === 0) { - md.addDownloadToken(); + const upload = uploadService.multipartUpload({ + bucketId, + objectId, + metadataRaw, + dataRaw: dataRaw, + authorization: req.header("authorization"), + }); + let metadata: StoredFileMetadata; + try { + metadata = await storageLayer.handleUploadObject(upload); + } catch (err) { + if (err instanceof ForbiddenError) { + return res.status(403).json({ + error: { + code: 403, + message: "Permission denied. No WRITE permission.", + }, + }); + } + throw err; } + metadata.addDownloadToken(); + return res.status(200).json(new OutgoingFirebaseMetadata(metadata)); + } - res.json(new OutgoingFirebaseMetadata(md)); + // Resumable upload + const uploadCommand = req.header("x-goog-upload-command"); + if (!uploadCommand) { + res.sendStatus(400); return; - } else { - const operationPath = ["b", req.params.bucketId, "o", name].join("/"); - const uploadCommand = req.header("x-goog-upload-command"); - if (!uploadCommand) { - res.sendStatus(400); - return; - } - - if (uploadCommand === "start") { - let objectContentType = - req.header("x-goog-upload-header-content-type") || - req.header("x-goog-upload-content-type"); - if (!objectContentType) { - const mimeTypeFromName = mime.getType(name); - if (!mimeTypeFromName) { - objectContentType = "application/octet-stream"; - } else { - objectContentType = mimeTypeFromName; - } - } - - const upload = storageLayer.startUpload( - req.params.bucketId, - name, - objectContentType, - req.body, - // Store auth header for use in the finalize request - req.header("authorization") - ); - - storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0)); + } - const emulatorInfo = EmulatorRegistry.getInfo(Emulators.STORAGE); + if (uploadCommand === "start") { + const upload = uploadService.startResumableUpload({ + bucketId, + objectId, + metadataRaw: JSON.stringify(req.body), + // Store auth header for use in the finalize request + authorization: req.header("authorization"), + }); - res.header("x-goog-upload-chunk-granularity", "10000"); - res.header("x-goog-upload-control-url", ""); - res.header("x-goog-upload-status", "active"); - res.header( - "x-goog-upload-url", - `http://${req.hostname}:${emulatorInfo?.port}/v0/b/${req.params.bucketId}/o?name=${req.query.name}&upload_id=${upload.uploadId}&upload_protocol=resumable` - ); - res.header("x-gupload-uploadid", upload.uploadId); + res.header("x-goog-upload-chunk-granularity", "10000"); + res.header("x-goog-upload-control-url", ""); + res.header("x-goog-upload-status", "active"); + const emulatorInfo = EmulatorRegistry.getInfo(Emulators.STORAGE); + res.header( + "x-goog-upload-url", + `http://${req.hostname}:${emulatorInfo?.port}/v0/b/${bucketId}/o?name=${objectId}&upload_id=${upload.id}&upload_protocol=resumable` + ); + res.header("x-gupload-uploadid", upload.id); - res.status(200).send(); - return; - } + return res.sendStatus(200); + } - if (!req.query.upload_id) { - res.sendStatus(400); - return; - } + if (!req.query.upload_id) { + return res.sendStatus(400); + } - const uploadId = req.query.upload_id.toString(); - if (uploadCommand === "query") { - const upload = storageLayer.queryUpload(uploadId); - if (!upload) { - res.sendStatus(400); - return; + const uploadId = req.query.upload_id.toString(); + if (uploadCommand === "query") { + let upload: Upload; + try { + upload = uploadService.getResumableUpload(uploadId); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); } - - res.header("X-Goog-Upload-Size-Received", upload.currentBytesUploaded.toString()); - res.sendStatus(200); - return; + throw err; } + res.header("X-Goog-Upload-Size-Received", upload.size.toString()); + return res.sendStatus(200); + } - if (uploadCommand === "cancel") { - const upload = storageLayer.queryUpload(uploadId); - if (upload) { - const cancelled = storageLayer.cancelUpload(upload); - res.sendStatus(cancelled ? 200 : 400); - } else { - res.sendStatus(404); + if (uploadCommand === "cancel") { + try { + uploadService.cancelResumableUpload(uploadId); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } else if (err instanceof NotCancellableError) { + return res.sendStatus(400); } - return; + throw err; } + return res.sendStatus(200); + } - let upload; - if (uploadCommand.includes("upload")) { - upload = storageLayer.uploadBytes(uploadId, await reqBodyToBuffer(req)); - - if (!upload) { - res.sendStatus(400); - return; + if (uploadCommand.includes("upload")) { + let upload: Upload; + try { + upload = uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req)); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } else if (err instanceof UploadNotActiveError) { + return res.sendStatus(400); } - + throw err; + } + if (!uploadCommand.includes("finalize")) { res.header("x-goog-upload-status", "active"); - res.header("x-gupload-uploadid", upload.uploadId); + res.header("x-gupload-uploadid", upload.id); + return res.sendStatus(200); } + // Intentional fall through to handle "upload, finalize" case. + } - if (uploadCommand.includes("finalize")) { - upload = storageLayer.queryUpload(uploadId); - if (!upload) { - res.sendStatus(400); - return; + if (uploadCommand.includes("finalize")) { + let upload: Upload; + try { + upload = uploadService.finalizeResumableUpload(uploadId); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } else if (err instanceof UploadNotActiveError) { + return res.sendStatus(400); } - - // For resumable uploads, we check auth on finalization in case of byte-dependant rules - if ( - !(await isPermitted({ - ruleset: emulator.rules, - // TODO This will be either create or update - method: RulesetOperationMethod.CREATE, - path: operationPath, - authorization: upload.authorization, - file: { - after: storageLayer.createMetadata(upload).asRulesResource(), - }, - })) - ) { - storageLayer.deleteFile(upload.bucketId, name); + throw err; + } + let metadata: StoredFileMetadata; + try { + metadata = await storageLayer.handleUploadObject(upload); + } catch (err) { + if (err instanceof ForbiddenError) { return res.status(403).json({ error: { code: 403, @@ -483,26 +455,17 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }, }); } - - res.header("x-goog-upload-status", "final"); - const uploadedFile = storageLayer.finalizeUpload(upload); - - const md = uploadedFile.metadata; - if (md.downloadTokens.length === 0) { - md.addDownloadToken(); - } - - res.json(new OutgoingFirebaseMetadata(uploadedFile.metadata)); - } else if (!upload) { - res.sendStatus(400); - return; - } else { - res.sendStatus(200); + throw err; } + metadata.addDownloadToken(); + return res.status(200).json(new OutgoingFirebaseMetadata(metadata)); } + + // Unsupported upload command. + return res.sendStatus(400); }; - // update metata handler + // update metadata handler firebaseStorageAPI.patch("/b/:bucketId/o/:objectId", handleMetadataUpdate); firebaseStorageAPI.put("/b/:bucketId/o/:objectId?", async (req, res) => { switch (req.header("x-http-method-override")?.toLowerCase()) { diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index 1a54152616f..a429859fdf0 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -12,6 +12,8 @@ import { EmulatorLogger } from "../../emulatorLogger"; import { StorageLayer } from "../files"; import type { Request, Response } from "express"; import { parseObjectUploadMultipartRequest } from "../multipart"; +import { Upload, UploadNotActiveError } from "../upload"; +import { ForbiddenError, NotFoundError } from "../errors"; /** * @param emulator @@ -20,7 +22,7 @@ import { parseObjectUploadMultipartRequest } from "../multipart"; export function createCloudEndpoints(emulator: StorageEmulator): Router { // eslint-disable-next-line new-cap const gcloudStorageAPI = Router(); - const { storageLayer } = emulator; + const { storageLayer, uploadService } = emulator; // Automatically create a bucket for any route which uses a bucket gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => { @@ -127,15 +129,29 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { } const uploadId = req.query.upload_id.toString(); - - const upload = storageLayer.uploadBytes(uploadId, await reqBodyToBuffer(req)); - if (!upload) { - res.sendStatus(400); - return; + let upload: Upload; + try { + uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req)); + upload = uploadService.finalizeResumableUpload(uploadId); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } else if (err instanceof UploadNotActiveError) { + return res.sendStatus(400); + } + throw err; } - const uploadedFile = storageLayer.finalizeUpload(upload); - res.status(200).json(new CloudStorageObjectMetadata(uploadedFile.metadata)).send(); + let metadata: StoredFileMetadata; + try { + metadata = await storageLayer.handleUploadObject(upload, /* skipAuth = */ true); + } catch (err) { + if (err instanceof ForbiddenError) { + throw new Error("Request failed unexpectedly due to Firebase Rules."); + } + throw err; + } + return res.json(new CloudStorageObjectMetadata(metadata)); }); gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", (req, res) => { @@ -185,28 +201,23 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type"); if (!contentTypeHeader) { - res.sendStatus(400); - return; + return res.sendStatus(400); } - if (req.query.uploadType === "resumable") { - const upload = storageLayer.startUpload( - req.params.bucketId, - name, - contentTypeHeader, - req.body - ); const emulatorInfo = EmulatorRegistry.getInfo(Emulators.STORAGE); - if (emulatorInfo === undefined) { - res.sendStatus(500); - return; + return res.sendStatus(500); } + const upload = uploadService.startResumableUpload({ + bucketId: req.params.bucketId, + objectId: name, + metadataRaw: JSON.stringify(req.body), + authorization: req.header("authorization"), + }); const { host, port } = emulatorInfo; - const uploadUrl = `http://${host}:${port}/upload/storage/v1/b/${upload.bucketId}/o?name=${upload.fileLocation}&uploadType=resumable&upload_id=${upload.uploadId}`; - res.header("location", uploadUrl).status(200).send(); - return; + const uploadUrl = `http://${host}:${port}/upload/storage/v1/b/${req.params.bucketId}/o?name=${name}&uploadType=resumable&upload_id=${upload.id}`; + return res.header("location", uploadUrl).sendStatus(200); } // Multipart upload @@ -218,31 +229,32 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { await reqBodyToBuffer(req) )); } catch (err) { - if (err instanceof Error) { - return res.status(400).json({ - error: { - code: 400, - message: err.toString(), - }, - }); + return res.status(400).json({ + error: { + code: 400, + message: err, + }, + }); + } + + const upload = uploadService.multipartUpload({ + bucketId: req.params.bucketId, + objectId: name, + metadataRaw: metadataRaw, + dataRaw: dataRaw, + authorization: req.header("authorization"), + }); + let metadata: StoredFileMetadata; + try { + metadata = await storageLayer.handleUploadObject(upload, /* skipAuth = */ true); + } catch (err) { + if (err instanceof ForbiddenError) { + throw new Error("Request failed unexpectedly due to Firebase Rules."); } throw err; } - const incomingMetadata = JSON.parse(metadataRaw)!; - const storedMetadata = storageLayer.oneShotUpload( - req.params.bucketId, - name, - incomingMetadata.contentType!, - incomingMetadata, - dataRaw - ); - if (!storedMetadata) { - res.sendStatus(400); - return; - } - res.status(200).json(new CloudStorageObjectMetadata(storedMetadata)).send(); - return; + return res.status(200).json(new CloudStorageObjectMetadata(metadata)); }); gcloudStorageAPI.get("/:bucketId/:objectId(**)", (req, res) => { diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index f72276212c0..1105b72315b 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -1,4 +1,3 @@ -import { v4 } from "uuid"; import { ListItem, ListResponse } from "./list"; import { CloudStorageBucketMetadata, @@ -19,6 +18,7 @@ import { import { RulesetOperationMethod } from "./rules/types"; import { RulesValidator } from "./rules/utils"; import { Persistence } from "./persistence"; +import { Upload } from "./upload"; interface BucketsList { buckets: { @@ -137,7 +137,6 @@ export type GetObjectResponse = { export class StorageLayer { private _files!: Map; - private _uploads!: Map; private _buckets!: Map; private _cloudFunctions: StorageCloudFunctions; @@ -152,7 +151,6 @@ export class StorageLayer { public reset(): void { this._files = new Map(); - this._uploads = new Map(); this._buckets = new Map(); } @@ -254,55 +252,6 @@ export class StorageLayer { this._files = value; } - public startUpload( - bucket: string, - object: string, - contentType: string, - metadata: IncomingMetadata, - authorization?: string - ): ResumableUpload { - const uploadId = v4(); - const upload = new ResumableUpload( - bucket, - object, - uploadId, - contentType, - metadata, - authorization - ); - this._uploads.set(uploadId, upload); - return upload; - } - - public queryUpload(uploadId: string): ResumableUpload | undefined { - return this._uploads.get(uploadId); - } - - /** - * Deletes partially uploaded file from persistence layer and updates its status. Cancelling - * an upload is idempotent. - * @param upload The upload to be cancelled. - * @returns Whether the upload was cancelled (i.e. its initial status was ACTIVE or CANCELLED). - */ - public cancelUpload(upload: ResumableUpload): boolean { - if (upload.status === UploadStatus.ACTIVE) { - this._persistence.deleteFile(upload.fileLocation); - upload.status = UploadStatus.CANCELLED; - } - return upload.status === UploadStatus.CANCELLED; - } - - public uploadBytes(uploadId: string, bytes: Buffer): ResumableUpload | undefined { - const upload = this._uploads.get(uploadId); - - if (!upload) { - return undefined; - } - this._persistence.appendBytes(upload.fileLocation, bytes); - upload.currentBytesUploaded += bytes.byteLength; - return upload; - } - public deleteFile(bucketId: string, objectId: string): boolean { const isFolder = objectId.toLowerCase().endsWith("%2f"); @@ -334,55 +283,47 @@ export class StorageLayer { } /** - * Stores the uploaded file with generated metadata and triggers Object Finalize Cloud Functions. - * @param upload The upload to finalize. - * @returns The stored file. + * Last step in uploading a file. Validates the request and persists the staging + * object to its permanent location on disk. + * TODO(tonyjhuang): Inject a Rules evaluator into StorageLayer to avoid needing skipAuth param + * @throws {ForbiddenError} if the request fails security rules auth. */ - public finalizeUpload(upload: ResumableUpload): StoredFile { - upload.status = UploadStatus.FINISHED; + public async handleUploadObject(upload: Upload, skipAuth = false): Promise { + if (upload.status !== UploadStatus.FINISHED) { + throw new Error(`Unexpected upload status encountered: ${upload.status}.`); + } - const metadata = this.createMetadata(upload); const filePath = this.path(upload.bucketId, upload.objectId); - const file = new StoredFile(metadata, filePath); - - this._files.set(filePath, file); - - this._persistence.deleteFile(filePath, /* failSilently = */ true); - this._persistence.renameFile(upload.fileLocation, filePath); - - this._cloudFunctions.dispatch("finalize", new CloudStorageObjectMetadata(file.metadata)); - - return file; - } - - public oneShotUpload( - bucket: string, - object: string, - contentType: string, - incomingMetadata: IncomingMetadata, - bytes: Buffer - ) { - const filePath = this.path(bucket, object); - - this._persistence.deleteFile(filePath, true); - - this._persistence.appendBytes(filePath, bytes); - const md = new StoredFileMetadata( + const metadata = new StoredFileMetadata( { - name: object, - bucket: bucket, - contentType: incomingMetadata.contentType || "application/octet-stream", - contentEncoding: incomingMetadata.contentEncoding, - customMetadata: incomingMetadata.metadata, + name: upload.objectId, + bucket: upload.bucketId, + contentType: upload.metadata.contentType || "application/octet-stream", + contentEncoding: upload.metadata.contentEncoding, + customMetadata: upload.metadata.metadata, }, this._cloudFunctions, - bytes + this._persistence.readBytes(upload.path, upload.size) ); - const file = new StoredFile(md, this._persistence.getDiskPath(filePath)); - this._files.set(filePath, file); + const authorized = + skipAuth || + (await this._validator.validate( + ["b", upload.bucketId, "o", upload.objectId].join("/"), + RulesetOperationMethod.CREATE, + { before: metadata?.asRulesResource() }, + upload.authorization + )); + if (!authorized) { + this._persistence.deleteFile(upload.path); + throw new ForbiddenError(); + } - this._cloudFunctions.dispatch("finalize", new CloudStorageObjectMetadata(file.metadata)); - return file.metadata; + // Persist to permanent location on disk. + this._persistence.deleteFile(filePath, /* failSilently = */ true); + this._persistence.renameFile(upload.path, filePath); + this._files.set(filePath, new StoredFile(metadata, this._persistence.getDiskPath(filePath))); + this._cloudFunctions.dispatch("finalize", new CloudStorageObjectMetadata(metadata)); + return metadata; } public listItemsAndPrefixes( diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index 3d4039d2bb1..8b528af02af 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -10,10 +10,10 @@ import * as fs from "fs"; import { StorageRulesetInstance, StorageRulesRuntime, StorageRulesIssues } from "./rules/runtime"; import { Source } from "./rules/types"; import { FirebaseError } from "../../error"; -import { getDownloadDetails } from "../downloadableEmulators"; import express = require("express"); import { getRulesValidator } from "./rules/utils"; import { Persistence } from "./persistence"; +import { UploadService } from "./upload"; export interface StorageEmulatorArgs { projectId: string; @@ -34,9 +34,9 @@ export class StorageEmulator implements EmulatorInstance { private _rulesRuntime: StorageRulesRuntime; private _persistence: Persistence; private _storageLayer: StorageLayer; + private _uploadService: UploadService; constructor(private args: StorageEmulatorArgs) { - const downloadDetails = getDownloadDetails(Emulators.STORAGE); this._rulesRuntime = new StorageRulesRuntime(); this._persistence = new Persistence(this.getPersistenceTmpDir()); this._storageLayer = new StorageLayer( @@ -44,12 +44,17 @@ export class StorageEmulator implements EmulatorInstance { getRulesValidator(() => this.rules), this._persistence ); + this._uploadService = new UploadService(this._persistence); } get storageLayer(): StorageLayer { return this._storageLayer; } + get uploadService(): UploadService { + return this._uploadService; + } + get rules(): StorageRulesetInstance | undefined { return this._rules; } @@ -61,6 +66,7 @@ export class StorageEmulator implements EmulatorInstance { reset(): void { this._storageLayer.reset(); this._persistence.reset(this.getPersistenceTmpDir()); + this._uploadService.reset(); } async start(): Promise { diff --git a/src/emulator/storage/persistence.ts b/src/emulator/storage/persistence.ts index 9651490beb3..47a61a2052e 100644 --- a/src/emulator/storage/persistence.ts +++ b/src/emulator/storage/persistence.ts @@ -48,10 +48,9 @@ export class Persistence { } readBytes(fileName: string, size: number, fileOffset?: number): Buffer { - const path = this.getDiskPath(fileName); let fd; try { - fd = openSync(path, "r"); + fd = openSync(this.getDiskPath(fileName), "r"); const buf = Buffer.alloc(size); const offset = fileOffset && fileOffset > 0 ? fileOffset : 0; readSync(fd, buf, 0, size, offset); diff --git a/src/emulator/storage/upload.ts b/src/emulator/storage/upload.ts new file mode 100644 index 00000000000..c6482e486bf --- /dev/null +++ b/src/emulator/storage/upload.ts @@ -0,0 +1,182 @@ +import { Persistence } from "./persistence"; +import { IncomingMetadata } from "./metadata"; +import { v4 as uuidV4 } from "uuid"; +import { NotFoundError } from "./errors"; + +/** A file upload. */ +export type Upload = { + id: string; + bucketId: string; + objectId: string; + type: UploadType; + // Path to where the file is stored on disk. May contain incomplete data if + // status !== FINISHED. + path: string; + status: UploadStatus; + metadata: IncomingMetadata; + size: number; + authorization?: string; +}; + +export enum UploadType { + MULTIPART, + RESUMABLE, +} + +/** The status of an upload. Multipart uploads can only ever be FINISHED. */ +export enum UploadStatus { + ACTIVE, + CANCELLED, + FINISHED, +} + +/** Request object for {@link UploadService#multipartUpload}. */ +export type MultipartUploadRequest = { + bucketId: string; + objectId: string; + metadataRaw: string; + dataRaw: Buffer; + authorization?: string; +}; + +/** Request object for {@link UploadService#startResumableUpload}. */ +export type StartResumableUploadRequest = { + bucketId: string; + objectId: string; + metadataRaw: string; + authorization?: string; +}; + +/** Error that signals a resumable upload that's expected to be active is not. */ +export class UploadNotActiveError extends Error {} + +/** Error that signals a resumable upload is not cancellable. */ +export class NotCancellableError extends Error {} + +/** + * Service that handles byte transfer and maintains state for file uploads. + * + * New file uploads will be persisted to a temp staging directory which will not + * survive across emulator restarts. Clients are expected to move staged files + * to a more permanent location. + */ +export class UploadService { + private _uploads!: Map; + constructor(private _persistence: Persistence) { + this.reset(); + } + + /** Resets the state of the UploadService. */ + public reset(): void { + this._uploads = new Map(); + } + + /** + * Handles a multipart file upload which is expected to have the entirety of + * the file's contents in a single request. + */ + public multipartUpload(request: MultipartUploadRequest): Upload { + const upload = this.startMultipartUpload(request, request.dataRaw.byteLength); + this._persistence.deleteFile(upload.path, /* failSilently = */ true); + this._persistence.appendBytes(upload.path, request.dataRaw); + return upload; + } + + private startMultipartUpload(request: MultipartUploadRequest, sizeInBytes: number): Upload { + const id = uuidV4(); + const upload: Upload = { + id: uuidV4(), + bucketId: request.bucketId, + objectId: request.objectId, + type: UploadType.MULTIPART, + path: this.getStagingFileName(id, request.bucketId, request.objectId), + status: UploadStatus.FINISHED, + metadata: JSON.parse(request.metadataRaw), + size: sizeInBytes, + authorization: request.authorization, + }; + this._uploads.set(upload.id, upload); + + return upload; + } + + /** + * Initializes a new ResumableUpload. + */ + public startResumableUpload(request: StartResumableUploadRequest): Upload { + const id = uuidV4(); + const upload: Upload = { + id: id, + bucketId: request.bucketId, + objectId: request.objectId, + type: UploadType.RESUMABLE, + path: this.getStagingFileName(id, request.bucketId, request.objectId), + status: UploadStatus.ACTIVE, + metadata: JSON.parse(request.metadataRaw), + size: 0, + authorization: request.authorization, + }; + this._uploads.set(upload.id, upload); + this._persistence.deleteFile(upload.path, /* failSilently = */ true); + return upload; + } + + /** + * Appends bytes to an existing resumable upload. + * @throws {NotFoundError} if the resumable upload does not exist. + * @throws {NotActiveUploadError} if the resumable upload is not in the ACTIVE state. + */ + public continueResumableUpload(uploadId: string, dataRaw: Buffer): Upload { + const upload = this.getResumableUpload(uploadId); + if (upload.status !== UploadStatus.ACTIVE) { + throw new UploadNotActiveError(); + } + this._persistence.appendBytes(upload.path, dataRaw); + upload.size += dataRaw.byteLength; + return upload; + } + + /** + * Queries for an existing resumable upload. + * @throws {NotFoundError} if the resumable upload does not exist. + */ + public getResumableUpload(uploadId: string): Upload { + const upload = this._uploads.get(uploadId); + if (!upload || upload.type !== UploadType.RESUMABLE) { + throw new NotFoundError(); + } + return upload; + } + + /** + * Cancels a resumable upload. + * @throws {NotFoundError} if the resumable upload does not exist. + * @throws {NotCancellableError} if the resumable upload can not be cancelled. + */ + public cancelResumableUpload(uploadId: string): Upload { + const upload = this.getResumableUpload(uploadId); + if (upload.status === UploadStatus.FINISHED) { + throw new NotCancellableError(); + } + upload.status = UploadStatus.CANCELLED; + return upload; + } + + /** + * Marks a ResumableUpload as finalized. + * @throws {NotFoundError} if the resumable upload does not exist. + * @throws {NotActiveUploadError} if the resumable upload is not ACTIVE. + */ + public finalizeResumableUpload(uploadId: string): Upload { + const upload = this.getResumableUpload(uploadId); + if (upload.status === UploadStatus.CANCELLED) { + throw new UploadNotActiveError(); + } + upload.status = UploadStatus.FINISHED; + return upload; + } + + private getStagingFileName(uploadId: string, bucketId: string, objectId: string): string { + return encodeURIComponent(`${uploadId}_b_${bucketId}_o_${objectId}`); + } +} diff --git a/src/test/emulators/storage/files.spec.ts b/src/test/emulators/storage/files.spec.ts index 0aaa88e85df..a34d1c4d2f7 100644 --- a/src/test/emulators/storage/files.spec.ts +++ b/src/test/emulators/storage/files.spec.ts @@ -7,6 +7,7 @@ import { StorageLayer } from "../../../emulator/storage/files"; import { ForbiddenError, NotFoundError } from "../../../emulator/storage/errors"; import { Persistence } from "../../../emulator/storage/persistence"; import { RulesValidator } from "../../../emulator/storage/rules/utils"; +import { Upload, UploadService, UploadStatus, UploadType } from "../../../emulator/storage/upload"; const ALWAYS_TRUE_RULES_VALIDATOR = { validate: () => Promise.resolve(true), @@ -38,78 +39,101 @@ describe("files", () => { expect(deserialized).to.deep.equal(metadata); }); - it("should store file in memory when upload is finalized", () => { - const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); - const bytesToWrite = "Hello, World!"; - - const upload = storageLayer.startUpload("bucket", "object", "mime/type", { - contentType: "mime/type", + describe("StorageLayer", () => { + let _persistence: Persistence; + let _uploadService: UploadService; + + const UPLOAD: Upload = { + id: "uploadId", + bucketId: "bucket", + objectId: "dir%2Fobject", + path: "", + type: UploadType.RESUMABLE, + status: UploadStatus.FINISHED, + metadata: {}, + size: 10, + }; + + beforeEach(() => { + _persistence = new Persistence(getPersistenceTmpDir()); + _uploadService = new UploadService(_persistence); }); - storageLayer.uploadBytes(upload.uploadId, Buffer.from(bytesToWrite)); - storageLayer.finalizeUpload(upload); - expect(storageLayer.getBytes("bucket", "object")?.includes(bytesToWrite)); - expect(storageLayer.getMetadata("bucket", "object")?.size).equals(bytesToWrite.length); - }); + describe("#handleUploadObject()", () => { + it("should throw if upload is not finished", () => { + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); + const upload = _uploadService.startResumableUpload({ + bucketId: "bucket", + objectId: "dir%2Fobject", + metadataRaw: "{}", + }); - it("should delete file from persistence layer when upload is cancelled", () => { - const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); + expect(storageLayer.handleUploadObject(upload)).to.be.rejectedWith( + "Unexpected upload status" + ); + }); - const upload = storageLayer.startUpload("bucket", "object", "mime/type", { - contentType: "mime/type", + it("should throw if upload is not authorized", () => { + const storageLayer = getStorageLayer(ALWAYS_FALSE_RULES_VALIDATOR); + const uploadId = _uploadService.startResumableUpload({ + bucketId: "bucket", + objectId: "dir%2Fobject", + metadataRaw: "{}", + }).id; + const data = Buffer.from("hello world"); + _uploadService.continueResumableUpload(uploadId, Buffer.from("hello world")); + const upload = _uploadService.finalizeResumableUpload(uploadId); + + expect(storageLayer.handleUploadObject(upload)).to.be.rejectedWith(ForbiddenError); + }); }); - storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0)); - storageLayer.cancelUpload(upload); - expect(storageLayer.getMetadata("bucket", "object")).to.equal(undefined); - }); + describe("#handleGetObject()", () => { + it("should return data and metadata", async () => { + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); + const upload = _uploadService.multipartUpload({ + bucketId: "bucket", + objectId: "dir%2Fobject", + metadataRaw: `{"contentType": "mime/type"}`, + dataRaw: Buffer.from("Hello, World!"), + }); + await storageLayer.handleUploadObject(upload); - describe("#handleGetObject()", () => { - it("should return data and metadata", async () => { - const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); - storageLayer.oneShotUpload( - "bucket", - "dir%2Fobject", - "mime/type", - { - contentType: "mime/type", - }, - Buffer.from("Hello, World!") - ); + const { metadata, data } = await storageLayer.handleGetObject({ + bucketId: "bucket", + decodedObjectId: "dir%2Fobject", + }); - const { metadata, data } = await storageLayer.handleGetObject({ - bucketId: "bucket", - decodedObjectId: "dir%2Fobject", + expect(metadata.contentType).to.equal("mime/type"); + expect(data.toString()).to.equal("Hello, World!"); }); - expect(metadata.contentType).to.equal("mime/type"); - expect(data.toString()).to.equal("Hello, World!"); - }); + it("should throw an error if request is not authorized", () => { + const storageLayer = getStorageLayer(ALWAYS_FALSE_RULES_VALIDATOR); - it("should throw an error if request is not authorized", () => { - const storageLayer = getStorageLayer(ALWAYS_FALSE_RULES_VALIDATOR); + expect( + storageLayer.handleGetObject({ + bucketId: "bucket", + decodedObjectId: "dir%2Fobject", + }) + ).to.be.rejectedWith(ForbiddenError); + }); - expect( - storageLayer.handleGetObject({ - bucketId: "bucket", - decodedObjectId: "dir%2Fobject", - }) - ).to.be.rejectedWith(ForbiddenError); + it("should throw an error if the object does not exist", () => { + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); + + expect( + storageLayer.handleGetObject({ + bucketId: "bucket", + decodedObjectId: "dir%2Fobject", + }) + ).to.be.rejectedWith(NotFoundError); + }); }); - it("should throw an error if the object does not exist", () => { - const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); + const getStorageLayer = (rulesValidator: RulesValidator) => + new StorageLayer("project", rulesValidator, _persistence); - expect( - storageLayer.handleGetObject({ - bucketId: "bucket", - decodedObjectId: "dir%2Fobject", - }) - ).to.be.rejectedWith(NotFoundError); - }); + const getPersistenceTmpDir = () => `${tmpdir()}/firebase/storage/blobs`; }); - - const getStorageLayer = (rulesValidator: RulesValidator) => - new StorageLayer("project", rulesValidator, new Persistence(getPersistenceTmpDir())); - const getPersistenceTmpDir = () => `${tmpdir()}/firebase/storage/blobs`; }); diff --git a/src/test/emulators/storage/persistence.spec.ts b/src/test/emulators/storage/persistence.spec.ts new file mode 100644 index 00000000000..dded2ef5779 --- /dev/null +++ b/src/test/emulators/storage/persistence.spec.ts @@ -0,0 +1,33 @@ +import { expect } from "chai"; +import { tmpdir } from "os"; + +import { v4 as uuidV4 } from "uuid"; +import { Persistence } from "../../../emulator/storage/persistence"; + +describe("Persistence", () => { + const testDir = `${tmpdir()}/${uuidV4()}`; + const _persistence = new Persistence(testDir); + after(async () => { + await _persistence.deleteAll(); + }); + + describe("#deleteFile()", () => { + it("should delete files", () => { + const filename = `${uuidV4()}%2F${uuidV4()}`; + _persistence.appendBytes(filename, Buffer.from("hello world")); + + _persistence.deleteFile(filename); + expect(() => _persistence.readBytes(filename, 10)).to.throw; + }); + }); + + describe("#readBytes()", () => { + it("should read existing files", () => { + const filename = `${uuidV4()}%2F${uuidV4()}`; + const data = Buffer.from("hello world"); + + _persistence.appendBytes(filename, data); + expect(_persistence.readBytes(filename, data.byteLength).toString()).to.equal("hello world"); + }); + }); +}); diff --git a/src/test/emulators/storage/upload.spec.ts b/src/test/emulators/storage/upload.spec.ts new file mode 100644 index 00000000000..75810fe9bb1 --- /dev/null +++ b/src/test/emulators/storage/upload.spec.ts @@ -0,0 +1,27 @@ +/* + it("should store file in memory when upload is finalized", () => { + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); + const bytesToWrite = "Hello, World!"; + + + const upload = storageLayer.startUpload("bucket", "object", "mime/type", { + contentType: "mime/type", + }); + storageLayer.uploadBytes(upload.uploadId, Buffer.from(bytesToWrite)); + storageLayer.finalizeUpload(upload); + + expect(storageLayer.getBytes("bucket", "object")?.includes(bytesToWrite)); + expect(storageLayer.getMetadata("bucket", "object")?.size).equals(bytesToWrite.length); + }); + + it("should delete file from persistence layer when upload is cancelled", () => { + const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); + + const upload = storageLayer.startUpload("bucket", "object", "mime/type", { + contentType: "mime/type", + }); + storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0)); + storageLayer.cancelUpload(upload); + + expect(storageLayer.getMetadata("bucket", "object")).to.equal(undefined); + });*/ From ad03551b6f8c87efda36ff86ca8aacb6533e4684 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Mon, 7 Mar 2022 13:39:23 -0500 Subject: [PATCH 0129/1699] Handle race condition in extensions emulator with downloading sources (#4249) * Update extensionsEmulator.ts * Update extensionsEmulator.ts * Update extensionsEmulator.ts * Update extensionsEmulator.ts Co-authored-by: joehan --- src/emulator/extensionsEmulator.ts | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 4c997561311..249be501a29 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -33,6 +33,9 @@ export class ExtensionsEmulator { private args: ExtensionEmulatorArgs; private logger = EmulatorLogger.forEmulator(Emulators.EXTENSIONS); + // Keeps track of all the extension sources that are being downloaded. + private pendingDownloads = new Map>(); + constructor(args: ExtensionEmulatorArgs) { this.args = args; } @@ -67,14 +70,32 @@ export class ExtensionsEmulator { path.join(os.homedir(), ".cache", "firebase", "extensions"); const sourceCodePath = path.join(cacheDir, ref); + // Wait for previous download promise to resolve before we check source validity. + // This avoids racing to download the same source multiple times. + // Note: The below will not work because it throws the thread to the back of the message queue. + // await (this.pendingDownloads.get(ref) ?? Promise.resolve()); + if (this.pendingDownloads.get(ref)) { + await this.pendingDownloads.get(ref); + } + if (!this.hasValidSource({ path: sourceCodePath, extRef: ref })) { - const extensionVersion = await planner.getExtensionVersion(instance); - await downloadExtensionVersion(ref, extensionVersion.sourceDownloadUri, sourceCodePath); - this.installAndBuildSourceCode(sourceCodePath); + const promise = this.downloadSource(instance, ref, sourceCodePath); + this.pendingDownloads.set(ref, promise); + await promise; } return sourceCodePath; } + private async downloadSource( + instance: planner.InstanceSpec, + ref: string, + sourceCodePath: string + ): Promise { + const extensionVersion = await planner.getExtensionVersion(instance); + await downloadExtensionVersion(ref, extensionVersion.sourceDownloadUri, sourceCodePath); + this.installAndBuildSourceCode(sourceCodePath); + } + /** * Returns if the source code at given path is valid. * From 524f98dddff1b4a7dcf142612fc77592f8e31929 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Mon, 7 Mar 2022 14:20:04 -0500 Subject: [PATCH 0130/1699] Add StorageRulesManager class for emulator (#4245) * Add StorageRulesManager class for emulator * Expose setSourceFile; add unit test * Address PR feedback * Address PR feedback --- src/emulator/storage/index.ts | 111 ++--------------- src/emulator/storage/rules/manager.ts | 112 ++++++++++++++++++ src/emulator/storage/server.ts | 2 +- .../emulators/storage/rules/manager.spec.ts | 92 ++++++++++++++ .../rules/runtime.spec.ts} | 24 ++-- 5 files changed, 232 insertions(+), 109 deletions(-) create mode 100644 src/emulator/storage/rules/manager.ts create mode 100644 src/test/emulators/storage/rules/manager.spec.ts rename src/test/emulators/{storage.rules.spec.ts => storage/rules/runtime.spec.ts} (93%) diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index 8b528af02af..4514794dc81 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -4,12 +4,10 @@ import { Constants } from "../constants"; import { EmulatorInfo, EmulatorInstance, Emulators } from "../types"; import { createApp } from "./server"; import { StorageLayer } from "./files"; -import * as chokidar from "chokidar"; import { EmulatorLogger } from "../emulatorLogger"; -import * as fs from "fs"; +import { StorageRulesManager } from "./rules/manager"; import { StorageRulesetInstance, StorageRulesRuntime, StorageRulesIssues } from "./rules/runtime"; -import { Source } from "./rules/types"; -import { FirebaseError } from "../../error"; +import { SourceFile } from "./rules/types"; import express = require("express"); import { getRulesValidator } from "./rules/utils"; import { Persistence } from "./persistence"; @@ -19,25 +17,24 @@ export interface StorageEmulatorArgs { projectId: string; port?: number; host?: string; - rules: Source | string; + rules: SourceFile | string; auto_download?: boolean; } export class StorageEmulator implements EmulatorInstance { private destroyServer?: () => Promise; private _app?: express.Express; - private _rulesWatcher?: chokidar.FSWatcher; - private _rules?: StorageRulesetInstance; - private _rulesetSource?: Source; private _logger = EmulatorLogger.forEmulator(Emulators.STORAGE); private _rulesRuntime: StorageRulesRuntime; + private _rulesManager: StorageRulesManager; private _persistence: Persistence; private _storageLayer: StorageLayer; private _uploadService: UploadService; constructor(private args: StorageEmulatorArgs) { this._rulesRuntime = new StorageRulesRuntime(); + this._rulesManager = new StorageRulesManager(this._rulesRuntime); this._persistence = new Persistence(this.getPersistenceTmpDir()); this._storageLayer = new StorageLayer( args.projectId, @@ -56,7 +53,7 @@ export class StorageEmulator implements EmulatorInstance { } get rules(): StorageRulesetInstance | undefined { - return this._rules; + return this._rulesManager.ruleset; } get logger(): EmulatorLogger { @@ -72,107 +69,23 @@ export class StorageEmulator implements EmulatorInstance { async start(): Promise { const { host, port } = this.getInfo(); await this._rulesRuntime.start(this.args.auto_download); + await this._rulesManager.setSourceFile(this.args.rules); this._app = await createApp(this.args.projectId, this); - - if (typeof this.args.rules === "string") { - const rulesFile = this.args.rules; - this.updateRulesSource(rulesFile); - } else { - this._rulesetSource = this.args.rules; - } - - if (!this._rulesetSource || this._rulesetSource.files.length === 0) { - throw new FirebaseError("Can not initialize Storage emulator without a rules source / file."); - } else if (this._rulesetSource.files.length > 1) { - throw new FirebaseError( - "Can not initialize Storage emulator with more than one rules source / file." - ); - } - - await this.loadRuleset(); - - const rulesPath = this._rulesetSource.files[0].name; - this._rulesWatcher = chokidar.watch(rulesPath, { persistent: true, ignoreInitial: true }); - this._rulesWatcher.on("change", async () => { - // There have been some race conditions reported (on Windows) where reading the - // file too quickly after the watcher fires results in an empty file being read. - // Adding a small delay prevents that at very little cost. - await new Promise((res) => setTimeout(res, 5)); - - this._logger.logLabeled( - "BULLET", - "storage", - `Change detected, updating rules for Cloud Storage...` - ); - this.updateRulesSource(rulesPath); - await this.loadRuleset(); - }); - const server = this._app.listen(port, host); this.destroyServer = utils.createDestroyer(server); } - private updateRulesSource(rulesFile: string): void { - this._rulesetSource = { - files: [ - { - name: rulesFile, - content: fs.readFileSync(rulesFile).toString(), - }, - ], - }; - } - - public async loadRuleset(source?: Source): Promise { - if (source) { - this._rulesetSource = source; - } - - if (!this._rulesetSource) { - const msg = "Attempting to update ruleset without a source."; - this._logger.log("WARN", msg); - - const error = JSON.stringify({ error: msg }); - return new StorageRulesIssues([error], []); - } - - const { ruleset, issues } = await this._rulesRuntime.loadRuleset(this._rulesetSource); - - if (!ruleset) { - issues.all.forEach((issue) => { - let parsedIssue; - try { - parsedIssue = JSON.parse(issue); - } catch { - // Parse manually - } - - if (parsedIssue) { - this._logger.log( - "WARN", - `${parsedIssue.description_.replace(/\.$/, "")} in ${ - parsedIssue.sourcePosition_.fileName_ - }:${parsedIssue.sourcePosition_.line_}` - ); - } else { - this._logger.log("WARN", issue); - } - }); - - delete this._rules; - } else { - this._rules = ruleset; - } - - return issues; - } - async connect(): Promise { // No-op } + async setRules(rules: SourceFile): Promise { + return this._rulesManager.setSourceFile(rules); + } + async stop(): Promise { await this.storageLayer.deleteAll(); + await this._rulesManager.close(); return this.destroyServer ? this.destroyServer() : Promise.resolve(); } diff --git a/src/emulator/storage/rules/manager.ts b/src/emulator/storage/rules/manager.ts new file mode 100644 index 00000000000..1f599cf8a45 --- /dev/null +++ b/src/emulator/storage/rules/manager.ts @@ -0,0 +1,112 @@ +import * as chokidar from "chokidar"; +import * as fs from "fs"; +import { EmulatorLogger } from "../../emulatorLogger"; +import { Emulators } from "../../types"; +import { FirebaseError } from "../../../error"; +import { SourceFile } from "./types"; +import { StorageRulesIssues, StorageRulesRuntime, StorageRulesetInstance } from "./runtime"; + +/** + * Loads and maintains a {@link StorageRulesetInstance} for a given source file. Listens for + * changes to the file and updates the ruleset accordingly. + */ +export class StorageRulesManager { + private _sourceFile?: SourceFile; + private _ruleset?: StorageRulesetInstance; + private _watcher = new chokidar.FSWatcher(); + private _logger = EmulatorLogger.forEmulator(Emulators.STORAGE); + + constructor(private _runtime: StorageRulesRuntime) {} + + get ruleset(): StorageRulesetInstance | undefined { + return this._ruleset; + } + + /** + * Updates the source file and, correspondingly, the file watcher and ruleset. + * @throws {FirebaseError} if file path is invalid. + */ + public async setSourceFile(rules: SourceFile | string): Promise { + const prevRulesFile = this._sourceFile?.name; + let rulesFile: string; + if (typeof rules === "string") { + this._sourceFile = { name: rules, content: readSourceFile(rules) }; + rulesFile = rules; + } else { + // Allow invalid file path here for testing + this._sourceFile = rules; + rulesFile = rules.name; + } + + const issues = await this.loadRuleset(); + this.updateWatcher(rulesFile, prevRulesFile); + return issues; + } + + /** + * Deletes source file, ruleset, and removes listeners from all files. + */ + public async close(): Promise { + delete this._sourceFile; + delete this._ruleset; + await this._watcher.close(); + } + + private updateWatcher(rulesFile: string, prevRulesFile?: string): void { + if (prevRulesFile) { + this._watcher.unwatch(prevRulesFile); + } + + this._watcher = chokidar + .watch(rulesFile, { persistent: true, ignoreInitial: true }) + .on("change", async () => { + // There have been some race conditions reported (on Windows) where reading the + // file too quickly after the watcher fires results in an empty file being read. + // Adding a small delay prevents that at very little cost. + await new Promise((res) => setTimeout(res, 5)); + + this._logger.logLabeled( + "BULLET", + "storage", + "Change detected, updating rules for Cloud Storage..." + ); + await this.loadRuleset(); + }); + } + + private async loadRuleset(): Promise { + const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this._sourceFile!] }); + + if (ruleset) { + this._ruleset = ruleset; + return issues; + } + + delete this._ruleset; + issues.all.forEach((issue: string) => { + try { + const parsedIssue = JSON.parse(issue); + this._logger.log( + "WARN", + `${parsedIssue.description_.replace(/\.$/, "")} in ${ + parsedIssue.sourcePosition_.fileName_ + }:${parsedIssue.sourcePosition_.line_}` + ); + } catch { + this._logger.log("WARN", issue); + } + }); + return issues; + } +} + +function readSourceFile(fileName: string): string { + try { + return fs.readFileSync(fileName).toString(); + } catch (error: any) { + if (error.code === "ENOENT") { + throw new FirebaseError(`File not found: ${fileName}`); + } + throw error; + } +} diff --git a/src/emulator/storage/server.ts b/src/emulator/storage/server.ts index 5ba0af0889b..49af5e0c5e0 100644 --- a/src/emulator/storage/server.ts +++ b/src/emulator/storage/server.ts @@ -92,7 +92,7 @@ export function createApp( const name = file.name; const content = file.content; - const issues = await emulator.loadRuleset({ files: [{ name, content }] }); + const issues = await emulator.setRules({ name, content }); if (issues.errors.length > 0) { res.status(400).json({ diff --git a/src/test/emulators/storage/rules/manager.spec.ts b/src/test/emulators/storage/rules/manager.spec.ts new file mode 100644 index 00000000000..18d670ce6db --- /dev/null +++ b/src/test/emulators/storage/rules/manager.spec.ts @@ -0,0 +1,92 @@ +import { expect } from "chai"; +import { tmpdir } from "os"; +import { v4 as uuidv4 } from "uuid"; + +import { FirebaseError } from "../../../../error"; +import { StorageRulesFiles, TIMEOUT_MED } from "../../fixtures"; +import { StorageRulesManager } from "../../../../emulator/storage/rules/manager"; +import { StorageRulesRuntime } from "../../../../emulator/storage/rules/runtime"; +import { Persistence } from "../../../../emulator/storage/persistence"; +import { RulesetOperationMethod } from "../../../../emulator/storage/rules/types"; + +describe("Storage Rules Manager", function () { + const rulesRuntime = new StorageRulesRuntime(); + const rulesManager = new StorageRulesManager(rulesRuntime); + + // eslint-disable-next-line @typescript-eslint/no-invalid-this + this.timeout(TIMEOUT_MED); + + before(async () => { + await rulesRuntime.start(); + }); + + after(async () => { + rulesRuntime.stop(); + await rulesManager.close(); + }); + + it("should load ruleset from SourceFile object", async () => { + await rulesManager.setSourceFile(StorageRulesFiles.readWriteIfTrue); + expect(rulesManager.ruleset).not.to.be.undefined; + }); + + it("should load ruleset from file path", async () => { + // Write rules to file + const fileName = "storage.rules"; + const testDir = `${tmpdir()}/${uuidv4()}`; + const persistence = new Persistence(testDir); + persistence.appendBytes(fileName, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); + + await rulesManager.setSourceFile(`${testDir}/${fileName}`); + + expect(rulesManager.ruleset).not.to.be.undefined; + }); + + it("should set source file", async () => { + await rulesManager.setSourceFile(StorageRulesFiles.readWriteIfTrue); + const opts = { method: RulesetOperationMethod.GET, file: {}, path: "/b/bucket/o/" }; + expect((await rulesManager.ruleset!.verify(opts)).permitted).to.be.true; + + const issues = await rulesManager.setSourceFile(StorageRulesFiles.readWriteIfAuth); + + expect(issues.errors.length).to.equal(0); + expect(issues.warnings.length).to.equal(0); + expect((await rulesManager.ruleset!.verify(opts)).permitted).to.be.false; + }); + + it("should reload ruleset on changes to source file", async () => { + const opts = { method: RulesetOperationMethod.GET, file: {}, path: "/b/bucket/o/" }; + + // Write rules to file + const fileName = "storage.rules"; + const testDir = `${tmpdir()}/${uuidv4()}`; + const persistence = new Persistence(testDir); + persistence.appendBytes(fileName, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); + + await rulesManager.setSourceFile(`${testDir}/${fileName}`); + expect((await rulesManager.ruleset!.verify(opts)).permitted).to.be.true; + + // Write new rules to file + persistence.deleteFile(fileName); + persistence.appendBytes(fileName, Buffer.from(StorageRulesFiles.readWriteIfAuth.content)); + + await rulesManager.setSourceFile(`${testDir}/${fileName}`); + expect((await rulesManager.ruleset!.verify(opts)).permitted).to.be.false; + }); + + it("should throw FirebaseError when attempting to set invalid source file", async () => { + const invalidFileName = "foo"; + await expect(rulesManager.setSourceFile(invalidFileName)).to.be.rejectedWith( + FirebaseError, + `File not found: ${invalidFileName}` + ); + }); + + it("should delete ruleset when storage manager is closed", async () => { + await rulesManager.setSourceFile(StorageRulesFiles.readWriteIfTrue); + expect(rulesManager.ruleset).not.to.be.undefined; + + await rulesManager.close(); + expect(rulesManager.ruleset).to.be.undefined; + }); +}); diff --git a/src/test/emulators/storage.rules.spec.ts b/src/test/emulators/storage/rules/runtime.spec.ts similarity index 93% rename from src/test/emulators/storage.rules.spec.ts rename to src/test/emulators/storage/rules/runtime.spec.ts index 125211d8962..424ffc9ffbe 100644 --- a/src/test/emulators/storage.rules.spec.ts +++ b/src/test/emulators/storage/rules/runtime.spec.ts @@ -1,13 +1,19 @@ -import { RulesetVerificationOpts, StorageRulesRuntime } from "../../emulator/storage/rules/runtime"; +import { + RulesetVerificationOpts, + StorageRulesRuntime, +} from "../../../../emulator/storage/rules/runtime"; import { expect } from "chai"; -import { StorageRulesFiles } from "./fixtures"; +import { StorageRulesFiles } from "../../fixtures"; import * as jwt from "jsonwebtoken"; -import { EmulatorLogger } from "../../emulator/emulatorLogger"; -import { ExpressionValue } from "../../emulator/storage/rules/expressionValue"; -import { RulesetOperationMethod } from "../../emulator/storage/rules/types"; -import { downloadIfNecessary, getDownloadDetails } from "../../emulator/downloadableEmulators"; -import { Emulators } from "../../emulator/types"; -import { RulesResourceMetadata } from "../../emulator/storage/metadata"; +import { EmulatorLogger } from "../../../../emulator/emulatorLogger"; +import { ExpressionValue } from "../../../../emulator/storage/rules/expressionValue"; +import { RulesetOperationMethod } from "../../../../emulator/storage/rules/types"; +import { + downloadIfNecessary, + getDownloadDetails, +} from "../../../../emulator/downloadableEmulators"; +import { Emulators } from "../../../../emulator/types"; +import { RulesResourceMetadata } from "../../../../emulator/storage/metadata"; const TOKENS = { signedInUser: jwt.sign( @@ -40,7 +46,7 @@ function createFakeResourceMetadata(params: { }; } -describe.skip("Storage Rules", function () { +describe.skip("Storage Rules Runtime", function () { let runtime: StorageRulesRuntime; // eslint-disable-next-line @typescript-eslint/no-invalid-this From 94b3156f033c7cab75001409b305edb29a26d688 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 7 Mar 2022 11:48:56 -0800 Subject: [PATCH 0131/1699] Split function deployments by region AND memory (#4253) Today, Function deployments are made in batches of functions grouped by region. Combined w/ source token to re-used built containers for deployment, we get meaningful decrease in deploy time and in Cloud Build resource usage. Unfortunately, this setup has a peculiar bug; Google Cloud Functions bakes in the flag to expand heap size for appropriate for the memory configuration of a function (`--max_old_space_size`) on the container itself. That means that if you batch two functions with differing memory configuration (e.g. 256MB vs 4 GB), it's guaranteed that one function will have wrongly configured `--max_old_space_size` flag value (Follow https://github.com/firebase/firebase-tools/issues/3716#issuecomment-1012573174 for how we discovered this issue). This PR proposes batching function by region AND `availalbeMemoryMB` to fix this bug. We do this by refactoring `calculateRegionalChanges` to generate a collection of `Changeset` (previously known as `RegionalChanges`) that sub-divides functions in a region by their memory. In fact, we generalize the approach by allowing arbitrary subdivision by `keyFn` (e.g. `keyFn: (endpoint) => endpoint.availableMemoryMb`) as I anticipate that we will revisit this section of the code once I start working on "codebases". Fixes https://github.com/firebase/firebase-tools/issues/3716 --- CHANGELOG.md | 1 + src/deploy/functions/release/fabricator.ts | 10 +-- src/deploy/functions/release/planner.ts | 66 +++++++++----- .../functions/release/fabricator.spec.ts | 16 ++-- .../deploy/functions/release/planner.spec.ts | 86 +++++++++++++++---- src/test/utils.spec.ts | 33 +++++++ src/utils.ts | 18 ++++ 7 files changed, 179 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..fa5c4a34424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes bug where functions' memory configurations weren't preserved in batched function deploys (#4253). diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 7f9bc832bf6..42f9e583efa 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -79,12 +79,12 @@ export class Fabricator { totalTime: 0, results: [], }; - const deployRegions = Object.values(plan).map(async (changes): Promise => { - const results = await this.applyRegionalChanges(changes); + const deployChangesets = Object.values(plan).map(async (changes): Promise => { + const results = await this.applyChangeset(changes); summary.results.push(...results); return; }); - const promiseResults = await utils.allSettled(deployRegions); + const promiseResults = await utils.allSettled(deployChangesets); const errs = promiseResults .filter((r) => r.status === "rejected") @@ -100,9 +100,7 @@ export class Fabricator { return summary; } - async applyRegionalChanges( - changes: planner.RegionalChanges - ): Promise> { + async applyChangeset(changes: planner.Changeset): Promise> { const deployResults: reporter.DeployResult[] = []; const handle = async ( op: reporter.OperationType, diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index 39029af9734..f6dc7b96176 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -10,13 +10,13 @@ export interface EndpointUpdate { deleteAndRecreate?: backend.Endpoint; } -export interface RegionalChanges { +export interface Changeset { endpointsToCreate: backend.Endpoint[]; endpointsToUpdate: EndpointUpdate[]; endpointsToDelete: backend.Endpoint[]; } -export type DeploymentPlan = Record; +export type DeploymentPlan = Record; export interface Options { filters?: string[][]; @@ -24,25 +24,49 @@ export interface Options { deleteAll?: boolean; } -/** Calculate the changes needed for a given region. */ -export function calculateRegionalChanges( +/** Calculate the changesets of given endpoints by grouping endpoints with keyFn. */ +export function calculateChangesets( want: Record, have: Record, + keyFn: (e: backend.Endpoint) => string, options: Options -): RegionalChanges { - const endpointsToCreate = Object.keys(want) - .filter((id) => !have[id]) - .map((id) => want[id]); - - const endpointsToDelete = Object.keys(have) - .filter((id) => !want[id]) - .filter((id) => options.deleteAll || isFirebaseManaged(have[id].labels || {})) - .map((id) => have[id]); - - const endpointsToUpdate = Object.keys(want) - .filter((id) => have[id]) - .map((id) => calculateUpdate(want[id], have[id])); - return { endpointsToCreate, endpointsToUpdate, endpointsToDelete }; +): Record { + const toCreate = utils.groupBy( + Object.keys(want) + .filter((id) => !have[id]) + .map((id) => want[id]), + keyFn + ); + + const toDelete = utils.groupBy( + Object.keys(have) + .filter((id) => !want[id]) + .filter((id) => options.deleteAll || isFirebaseManaged(have[id].labels || {})) + .map((id) => have[id]), + keyFn + ); + + const toUpdate = utils.groupBy( + Object.keys(want) + .filter((id) => have[id]) + .map((id) => calculateUpdate(want[id], have[id])), + (eu: EndpointUpdate) => keyFn(eu.endpoint) + ); + + const result: Record = {}; + const keys = new Set([ + ...Object.keys(toCreate), + ...Object.keys(toDelete), + ...Object.keys(toUpdate), + ]); + for (const key of keys) { + result[key] = { + endpointsToCreate: toCreate[key] || [], + endpointsToUpdate: toUpdate[key] || [], + endpointsToDelete: toDelete[key] || [], + }; + } + return result; } /** @@ -78,7 +102,7 @@ export function createDeploymentPlan( have: backend.Backend, options: Options = {} ): DeploymentPlan { - const deployment: DeploymentPlan = {}; + let deployment: DeploymentPlan = {}; want = backend.matchingBackend(want, (endpoint) => { return functionMatchesAnyGroup(endpoint, options.filters || []); }); @@ -88,11 +112,13 @@ export function createDeploymentPlan( const regions = new Set([...Object.keys(want.endpoints), ...Object.keys(have.endpoints)]); for (const region of regions) { - deployment[region] = calculateRegionalChanges( + const changesets = calculateChangesets( want.endpoints[region] || {}, have.endpoints[region] || {}, + (e) => `${e.region}-${e.availableMemoryMb || "default"}`, options ); + deployment = { ...deployment, ...changesets }; } if (upgradedToGCFv2WithoutSettingConcurrency(want, have)) { diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index c670b8caf55..502f4618013 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -988,7 +988,7 @@ describe("Fabricator", () => { const ep1 = endpoint({ httpsTrigger: {} }, { id: "A" }); const ep2 = endpoint({ httpsTrigger: {} }, { id: "B" }); const ep3 = endpoint({ httpsTrigger: {} }, { id: "C" }); - const changes: planner.RegionalChanges = { + const changes: planner.Changeset = { endpointsToCreate: [ep1, ep2], endpointsToUpdate: [{ endpoint: ep3 }], endpointsToDelete: [], @@ -1014,19 +1014,19 @@ describe("Fabricator", () => { const updateEndpoint = sinon.stub(fab, "updateEndpoint"); updateEndpoint.callsFake(fakeUpsert); - await fab.applyRegionalChanges(changes); + await fab.applyChangeset(changes); }); it("handles errors and wraps them in results", async () => { // when it hits a real API it will fail. const ep = endpoint(); - const changes: planner.RegionalChanges = { + const changes: planner.Changeset = { endpointsToCreate: [ep], endpointsToUpdate: [], endpointsToDelete: [], }; - const results = await fab.applyRegionalChanges(changes); + const results = await fab.applyChangeset(changes); expect(results[0].error).to.be.instanceOf(reporter.DeploymentError); expect(results[0].error?.message).to.match(/create function/); }); @@ -1036,13 +1036,13 @@ describe("Fabricator", () => { // when it hits a real API it will fail. const createEP = endpoint({ httpsTrigger: {} }, { id: "A" }); const deleteEP = endpoint({ httpsTrigger: {} }, { id: "B" }); - const changes: planner.RegionalChanges = { + const changes: planner.Changeset = { endpointsToCreate: [createEP], endpointsToUpdate: [], endpointsToDelete: [deleteEP], }; - const results = await fab.applyRegionalChanges(changes); + const results = await fab.applyChangeset(changes); const result = results.find((r) => r.endpoint.id === deleteEP.id); expect(result?.error).to.be.instanceOf(reporter.AbortedDeploymentError); expect(result?.durationMs).to.equal(0); @@ -1053,7 +1053,7 @@ describe("Fabricator", () => { const updateEP = endpoint({ httpsTrigger: {} }, { id: "B" }); const deleteEP = endpoint({ httpsTrigger: {} }, { id: "C" }); const update: planner.EndpointUpdate = { endpoint: updateEP }; - const changes: planner.RegionalChanges = { + const changes: planner.Changeset = { endpointsToCreate: [createEP], endpointsToUpdate: [update], endpointsToDelete: [deleteEP], @@ -1066,7 +1066,7 @@ describe("Fabricator", () => { const deleteEndpoint = sinon.stub(fab, "deleteEndpoint"); deleteEndpoint.resolves(); - const results = await fab.applyRegionalChanges(changes); + const results = await fab.applyChangeset(changes); expect(createEndpoint).to.have.been.calledWithMatch(createEP); expect(updateEndpoint).to.have.been.calledWithMatch(update); expect(deleteEndpoint).to.have.been.calledWith(deleteEP); diff --git a/src/test/deploy/functions/release/planner.spec.ts b/src/test/deploy/functions/release/planner.spec.ts index 9bab37ab298..38fae238ebb 100644 --- a/src/test/deploy/functions/release/planner.spec.ts +++ b/src/test/deploy/functions/release/planner.spec.ts @@ -150,14 +150,16 @@ describe("planner", () => { const have = { updated, deleted, pantheon }; // note: pantheon is not updated in any way - expect(planner.calculateRegionalChanges(want, have, {})).to.deep.equal({ - endpointsToCreate: [created], - endpointsToUpdate: [ - { - endpoint: updated, - }, - ], - endpointsToDelete: [deleted], + expect(planner.calculateChangesets(want, have, (e) => e.region, {})).to.deep.equal({ + region: { + endpointsToCreate: [created], + endpointsToUpdate: [ + { + endpoint: updated, + }, + ], + endpointsToDelete: [deleted], + }, }); }); @@ -172,19 +174,69 @@ describe("planner", () => { const have = { updated, deleted, pantheon }; // note: pantheon is deleted because we have deleteAll: true - expect(planner.calculateRegionalChanges(want, have, { deleteAll: true })).to.deep.equal({ - endpointsToCreate: [created], - endpointsToUpdate: [ - { - endpoint: updated, - }, - ], - endpointsToDelete: [deleted, pantheon], + expect( + planner.calculateChangesets(want, have, (e) => e.region, { deleteAll: true }) + ).to.deep.equal({ + region: { + endpointsToCreate: [created], + endpointsToUpdate: [ + { + endpoint: updated, + }, + ], + endpointsToDelete: [deleted, pantheon], + }, }); }); }); describe("createDeploymentPlan", () => { + it("groups deployment by region and memory", () => { + const region1mem1Created: backend.Endpoint = func("id1", "region1"); + const region1mem1Updated: backend.Endpoint = func("id2", "region1"); + + const region2mem1Created: backend.Endpoint = func("id3", "region2"); + const region2mem2Updated: backend.Endpoint = func("id4", "region2"); + region2mem2Updated.availableMemoryMb = 512; + const region2mem2Deleted: backend.Endpoint = func("id5", "region2"); + region2mem2Deleted.availableMemoryMb = 512; + region2mem2Deleted.labels = deploymentTool.labels(); + + const have = backend.of(region1mem1Updated, region2mem2Updated, region2mem2Deleted); + const want = backend.of( + region1mem1Created, + region1mem1Updated, + region2mem1Created, + region2mem2Updated + ); + + expect(planner.createDeploymentPlan(want, have, {})).to.deep.equal({ + "region1-default": { + endpointsToCreate: [region1mem1Created], + endpointsToUpdate: [ + { + endpoint: region1mem1Updated, + }, + ], + endpointsToDelete: [], + }, + "region2-default": { + endpointsToCreate: [region2mem1Created], + endpointsToUpdate: [], + endpointsToDelete: [], + }, + "region2-512": { + endpointsToCreate: [], + endpointsToUpdate: [ + { + endpoint: region2mem2Updated, + }, + ], + endpointsToDelete: [region2mem2Deleted], + }, + }); + }); + it("applies filters", () => { const group1Created = func("g1-created", "region"); const group1Updated = func("g1-updated", "region"); @@ -201,7 +253,7 @@ describe("planner", () => { const have = backend.of(group1Updated, group1Deleted, group2Updated, group2Deleted); expect(planner.createDeploymentPlan(want, have, { filters: [["g1"]] })).to.deep.equal({ - region: { + "region-default": { endpointsToCreate: [group1Created], endpointsToUpdate: [ { diff --git a/src/test/utils.spec.ts b/src/test/utils.spec.ts index e2bb07dfe82..a661354f4de 100644 --- a/src/test/utils.spec.ts +++ b/src/test/utils.spec.ts @@ -297,4 +297,37 @@ describe("utils", () => { ]); }); }); + + describe("groupBy", () => { + it("should transform simple array by fn", () => { + const arr = [3.4, 1.2, 7.7, 2, 3.9]; + expect(utils.groupBy(arr, Math.floor)).to.deep.equal({ + 1: [1.2], + 2: [2], + 3: [3.4, 3.9], + 7: [7.7], + }); + }); + + it("should transform array of objects by fn", () => { + const arr = [ + { id: 1, location: "us" }, + { id: 2, location: "us" }, + { id: 3, location: "asia" }, + { id: 4, location: "europe" }, + { id: 5, location: "asia" }, + ]; + expect(utils.groupBy(arr, (obj) => obj.location)).to.deep.equal({ + us: [ + { id: 1, location: "us" }, + { id: 2, location: "us" }, + ], + asia: [ + { id: 3, location: "asia" }, + { id: 5, location: "asia" }, + ], + europe: [{ id: 4, location: "europe" }], + }); + }); + }); }); diff --git a/src/utils.ts b/src/utils.ts index d6d1fc96eab..64838c46ea7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -620,3 +620,21 @@ export function assertIsStringOrUndefined( }); } } + +/** + * Polyfill for groupBy. + */ +export function groupBy( + arr: T[], + f: (item: T) => K +): Record { + return arr.reduce((result, item) => { + const key = f(item); + if (result[key]) { + result[key].push(item); + } else { + result[key] = [item]; + } + return result; + }, {} as Record); +} From a53828189dcdfe3dcdc2240b3a0b4c348b043a5b Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Mon, 7 Mar 2022 16:13:41 -0500 Subject: [PATCH 0132/1699] Merge ext:* --local launch branch into master (#4257) * Add a --local flag to ext:install (#4204) * mvp for install --local * format * resolve merge conflict * format * check for duplicates from the manifest * address comments * Add unit tests * Add tests * void promises * Update ext-install.ts * simplify * Update manifest.ts Co-authored-by: joehan * Extensions - Move manifest related util into manifest.ts (#4217) * move util to manifest.ts * Update paramHelper.ts * Add --local flag to ext:configure (#4219) * Allow configure --local * format * write new values to manifest * Update ext-configure.ts * Cleanup * Add tests * fix tests * fix comments * Add --local flag to ext:uninstall command. (#4223) * uninstall --local * Add tests * Update manifest.spec.ts * Fix tests with factory * Update manifest.spec.ts * PR fixes * Add --local flag to ext:update (#4234) * Update ext-update.ts * write to manifest * Handle missing manifest params during update * Better logging * Update ext-update.ts * Fix tests * Add deprecation warning to ext:* commands in favor of --local flag (#4243) * add deprecation warning * fixes * Add preview warning when running with --local * Revert "Add preview warning when running with --local" This reverts commit 8b3e8c1f6fa5d68ed09d8ee909b278c4828b1bdc. * Revert "Revert "Add preview warning when running with --local"" This reverts commit 3c2e6f791b8f6a39b64f8247280d64790a531e71. * pr fixes Co-authored-by: joehan --- src/commands/ext-configure.ts | 104 +++++++- src/commands/ext-export.ts | 24 +- src/commands/ext-install.ts | 95 ++++++- src/commands/ext-uninstall.ts | 18 +- src/commands/ext-update.ts | 96 ++++++- src/config.ts | 4 + src/deploy/extensions/params.ts | 58 ----- src/deploy/extensions/planner.ts | 4 +- src/extensions/extensionsApi.ts | 2 +- src/extensions/manifest.ts | 168 ++++++++++-- src/extensions/paramHelper.ts | 22 +- src/test/deploy/extensions/params.spec.ts | 104 -------- src/test/extensions/manifest.spec.ts | 301 ++++++++++++++++++++++ src/test/extensions/paramHelper.spec.ts | 26 +- 14 files changed, 806 insertions(+), 220 deletions(-) delete mode 100644 src/deploy/extensions/params.ts delete mode 100644 src/test/deploy/extensions/params.spec.ts create mode 100644 src/test/extensions/manifest.spec.ts diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 304a112ace0..a86c8c7a9df 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -15,6 +15,10 @@ import * as paramHelper from "../extensions/paramHelper"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; import { logger } from "../logger"; +import * as refs from "../extensions/refs"; +import * as manifest from "../extensions/manifest"; +import { Options } from "../options"; +import { partition } from "../functional"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -27,18 +31,80 @@ export default new Command("ext:configure ") .description("configure an existing extension instance") .withForce() .option("--params ", "path of params file with .env format.") + .option("--local", "save to firebase.json rather than directly install to a Firebase project") .before(requirePermissions, [ "firebaseextensions.instances.update", "firebaseextensions.instances.get", ]) .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) - .action(async (instanceId: string, options: any) => { + .action(async (instanceId: string, options: Options) => { + const projectId = needProjectId(options); + + if (options.local) { + if (options.nonInteractive) { + throw new FirebaseError( + `Command not supported in non-interactive mode, edit ./extensions/${instanceId}.env directly instead` + ); + } + + const config = manifest.loadConfig(options); + const targetRef = manifest.getInstanceRef(instanceId, config); + const extensionVersion = await extensionsApi.getExtensionVersion( + refs.toExtensionVersionRef(targetRef) + ); + + const oldParamValues = manifest.readInstanceParam({ + instanceId, + projectDir: config.projectDir, + }); + + const [immutableParams, tbdParams] = partition( + extensionVersion.spec.params, + (param) => param.immutable ?? false + ); + infoImmutableParams(immutableParams, oldParamValues); + + // Ask for mutable param values from user. + paramHelper.setNewDefaults(tbdParams, oldParamValues); + const mutableParamsValues = await paramHelper.getParams({ + projectId, + paramSpecs: tbdParams, + nonInteractive: false, + paramsEnvPath: (options.params ?? "") as string, + instanceId, + reconfiguring: true, + }); + + // Merge with old immutable params. + const newParamValues = { + ...oldParamValues, + ...mutableParamsValues, + }; + + await manifest.writeToManifest( + [ + { + instanceId, + ref: targetRef, + params: newParamValues, + }, + ], + config, + { + nonInteractive: false, + force: true, // Skip asking for permission again + } + ); + manifest.showPreviewWarning(); + return; + } + + // TODO(b/220900194): Remove everything below and make --local the default behavior. const spinner = ora( `Configuring ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...` ); try { - const projectId = needProjectId(options); let existingInstance: extensionsApi.ExtensionInstance; try { existingInstance = await extensionsApi.getInstance(projectId, instanceId); @@ -55,16 +121,13 @@ export default new Command("ext:configure ") } const paramSpecWithNewDefaults = paramHelper.getParamsWithCurrentValuesAsDefaults(existingInstance); - const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => { - return param.immutable || param.param === "LOCATION"; - // TODO: Stop special casing "LOCATION" once all official extensions make it immutable - }); + const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => param.immutable); const params = await paramHelper.getParams({ projectId, paramSpecs: paramSpecWithNewDefaults, nonInteractive: options.nonInteractive, - paramsEnvPath: options.params, + paramsEnvPath: options.params as string, instanceId, reconfiguring: true, }); @@ -97,6 +160,7 @@ export default new Command("ext:configure ") )}` ) ); + manifest.showDeprecationWarning(); return res; } catch (err: any) { if (spinner.isSpinning) { @@ -110,3 +174,29 @@ export default new Command("ext:configure ") throw err; } }); + +function infoImmutableParams( + immutableParams: extensionsApi.Param[], + paramValues: { [key: string]: string } +) { + if (!immutableParams.length) { + return; + } + + const plural = immutableParams.length > 1; + utils.logLabeledWarning( + logPrefix, + marked(`The following param${plural ? "s are" : " is"} immutable and won't be changed:`) + ); + + for (const { param } of immutableParams) { + logger.info(`param: ${param}, value: ${paramValues[param]}`); + } + + logger.info( + (plural + ? "To set different values for these params" + : "To set a different value for this param") + + ", uninstall the extension, then install a new instance of this extension." + ); +} diff --git a/src/commands/ext-export.ts b/src/commands/ext-export.ts index ca0aeb2103d..1b58b05f115 100644 --- a/src/commands/ext-export.ts +++ b/src/commands/ext-export.ts @@ -1,15 +1,13 @@ import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; -import { Config } from "../config"; import * as planner from "../deploy/extensions/planner"; -import { FirebaseError } from "../error"; import { displayExportInfo, parameterizeProject, setSecretParamsToLatest, } from "../extensions/export"; import { ensureExtensionsApiEnabled } from "../extensions/extensionsHelper"; -import { writeToManifest } from "../extensions/manifest"; +import * as manifest from "../extensions/manifest"; import { partition } from "../functional"; import { getProjectNumber } from "../getProjectNumber"; import { logger } from "../logger"; @@ -66,14 +64,14 @@ module.exports = new Command("ext:export") return; } - const existingConfig = Config.load(options, true); - if (!existingConfig) { - throw new FirebaseError( - "Not currently in a Firebase directory. Please run `firebase init` to create a Firebase directory." - ); - } - await writeToManifest(withRef, existingConfig, { - nonInteractive: options.nonInteractive, - force: options.force, - }); + const existingConfig = manifest.loadConfig(options); + await manifest.writeToManifest( + withRef, + existingConfig, + { + nonInteractive: options.nonInteractive, + force: options.force, + }, + true /** allowOverwrite */ + ); }); diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 8411ac77e76..b6264e6b75f 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -39,6 +39,8 @@ import * as utils from "../utils"; import { track } from "../track"; import { logger } from "../logger"; import { previews } from "../previews"; +import { Options } from "../options"; +import * as manifest from "../extensions/manifest"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -56,14 +58,16 @@ export default new Command("ext:install [extensionName]") "or run with `-i` to see all available extensions." ) .withForce() + // TODO(b/221037520): Deprecate the params flag then remove it in the next breaking version. .option("--params ", "name of params variables file with .env format.") + .option("--local", "save to firebase.json rather than directly install to a Firebase project") .before(requirePermissions, ["firebaseextensions.instances.create"]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) - .action(async (extensionName: string, options: any) => { + .action(async (extensionName: string, options: Options) => { const projectId = needProjectId(options); - const paramsEnvPath = options.params; + const paramsEnvPath = (options.params ?? "") as string; let learnMore = false; if (!extensionName) { if (options.interactive) { @@ -93,6 +97,11 @@ export default new Command("ext:install [extensionName]") // Otherwise, treat the input as an extension reference and proceed with reference-based installation. if (isLocalOrURLPath(extensionName)) { void track("Extension Install", "Install by Source", options.interactive ? 1 : 0); + if (options.local) { + throw new FirebaseError( + "Installing a local source locally is not supported yet, please use ext:dev:emulator commands" + ); + } source = await infoInstallBySource(projectId, extensionName); } else { void track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); @@ -128,6 +137,32 @@ export default new Command("ext:install [extensionName]") `View details: https://firebase.google.com/products/extensions/${spec.name}\n` ); } + + if (options.local) { + try { + return installToManifest({ + paramsEnvPath, + projectId, + extensionName, + source, + extVersion, + nonInteractive: options.nonInteractive, + force: options.force, + }); + } catch (err: any) { + if (!(err instanceof FirebaseError)) { + throw new FirebaseError( + `Error occurred saving the extension to manifest: ${err.message}`, + { + original: err, + } + ); + } + throw err; + } + } + + // TODO(b/220900194): Remove this and make --local the default behavior. try { return installExtension({ paramsEnvPath, @@ -200,6 +235,61 @@ interface InstallExtensionOptions { force?: boolean; } +/** + * Saves the extension instance config values to the manifest. + * + * Requires running `firebase deploy` to install it to the Firebase project. + * @param options + */ +async function installToManifest(options: InstallExtensionOptions): Promise { + const { projectId, extensionName, extVersion, paramsEnvPath, nonInteractive, force } = options; + const spec = extVersion?.spec; + if (!spec) { + throw new FirebaseError( + `Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.` + ); + } + + const config = manifest.loadConfig(options); + + let instanceId = spec.name; + while (manifest.instanceExists(instanceId, config)) { + instanceId = await promptForValidInstanceId(`${spec.name}-${getRandomString(4)}`); + } + + const params = await paramHelper.getParams({ + projectId, + paramSpecs: spec.params, + nonInteractive, + paramsEnvPath, + instanceId, + }); + + const ref = refs.parse(extVersion.ref); + await manifest.writeToManifest( + [ + { + instanceId, + ref, + params, + }, + ], + config, + { nonInteractive, force: force ?? false } + ); + manifest.showPreviewWarning(); +} + +/** + * Installs the extension in user's project. + * + * 1. Checks products are provisioned. + * 2. Checks billings are enabled if needed. + * 3. Asks for permission to grant sa roles. + * 4. Asks for extension params + * 5. Install + * @param options + */ async function installExtension(options: InstallExtensionOptions): Promise { const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } = options; @@ -350,6 +440,7 @@ async function installExtension(options: InstallExtensionOptions): Promise "including those to update, reconfigure, or delete your installed extension." ) ); + manifest.showDeprecationWarning(); } catch (err: any) { if (spinner.isSpinning) { spinner.fail(); diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index ac656bff3d6..8ef8ea9324f 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -21,6 +21,8 @@ import { promptOnce } from "../prompt"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; import { logger } from "../logger"; +import * as manifest from "../extensions/manifest"; +import { Options } from "../options"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -29,14 +31,25 @@ marked.setOptions({ export default new Command("ext:uninstall ") .description("uninstall an extension that is installed in your Firebase project by instance ID") .withForce() + .option( + "--local", + "remove from firebase.json rather than directly uninstall from a Firebase project" + ) .before(requirePermissions, ["firebaseextensions.instances.delete"]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) - .action(async (instanceId: string, options: any) => { + .action(async (instanceId: string, options: Options) => { + if (options.local) { + const config = manifest.loadConfig(options); + manifest.removeFromManifest(instanceId, config); + manifest.showPreviewWarning(); + return; + } + + // TODO(b/220900194): Remove everything below and make --local the default behavior. const projectId = needProjectId(options); let instance; - try { instance = await extensionsApi.getInstance(projectId, instanceId); } catch (err: any) { @@ -121,6 +134,7 @@ export default new Command("ext:uninstall ") return utils.reject(`Error occurred uninstalling extension ${instanceId}`, { original: err }); } utils.logLabeledSuccess(logPrefix, `uninstalled ${clc.bold(instanceId)}`); + manifest.showDeprecationWarning(); }); /** diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index fbfe6540a15..7ec9c432cbf 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -39,6 +39,9 @@ import { needProjectId } from "../projectUtils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; import { previews } from "../previews"; +import * as manifest from "../extensions/manifest"; +import { Options } from "../options"; +import { logger } from ".."; marked.setOptions({ renderer: new TerminalRenderer(), @@ -62,10 +65,96 @@ export default new Command("ext:update [updateSource]") .before(diagnoseAndFixProject) .withForce() .option("--params ", "name of params variables file with .env format.") - .action(async (instanceId: string, updateSource: string, options: any) => { + .option( + "--local", + "save the update to firebase.json rather than directly update an existing Extension instance on a Firebase project" + ) + .action(async (instanceId: string, updateSource: string, options: Options) => { + const projectId = needProjectId(options); + + if (options.local) { + const config = manifest.loadConfig(options); + const oldRef = manifest.getInstanceRef(instanceId, config); + const oldExtensionVersion = await extensionsApi.getExtensionVersion( + refs.toExtensionVersionRef(oldRef) + ); + updateSource = inferUpdateSource(updateSource, refs.toExtensionRef(oldRef)); + + // TODO(b/213335255): Allow local sources after manifest supports that. + const newSourceOrigin = getSourceOrigin(updateSource); + if ( + ![SourceOrigin.PUBLISHED_EXTENSION, SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes( + newSourceOrigin + ) + ) { + throw new FirebaseError(`Only updating to a published extension version is allowed`); + } + + const newExtensionVersion = await extensionsApi.getExtensionVersion(updateSource); + + if (oldExtensionVersion.ref === newExtensionVersion.ref) { + utils.logLabeledBullet( + logPrefix, + `${clc.bold(instanceId)} is already up to date. Its version is ${clc.bold( + newExtensionVersion.ref + )}.` + ); + return; + } + + utils.logLabeledBullet( + logPrefix, + `Updating ${clc.bold(instanceId)} from version ${clc.bold( + oldExtensionVersion.ref + )} to version ${clc.bold(newExtensionVersion.ref)}.` + ); + + if ( + !(await confirm({ + nonInteractive: options.nonInteractive, + force: options.force, + default: false, + })) + ) { + utils.logLabeledBullet(logPrefix, "Update aborted."); + return; + } + + const oldParamValues = manifest.readInstanceParam({ + instanceId, + projectDir: config.projectDir, + }); + + const newParams = await paramHelper.getParamsForUpdate({ + spec: oldExtensionVersion.spec, + newSpec: newExtensionVersion.spec, + currentParams: oldParamValues, + projectId, + paramsEnvPath: (options.params ?? "") as string, + nonInteractive: options.nonInteractive, + instanceId, + }); + + await manifest.writeToManifest( + [ + { + instanceId, + ref: refs.parse(newExtensionVersion.ref), + params: newParams, + }, + ], + config, + { + nonInteractive: options.nonInteractive, + force: true, // Skip asking for permission again + } + ); + manifest.showPreviewWarning(); + return; + } + const spinner = ora(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`); try { - const projectId = needProjectId(options); let existingInstance: extensionsApi.ExtensionInstance; try { existingInstance = await extensionsApi.getInstance(projectId, instanceId); @@ -238,7 +327,7 @@ export default new Command("ext:update [updateSource]") newSpec, currentParams: existingParams, projectId, - paramsEnvPath: options.params, + paramsEnvPath: (options.params ?? "") as string, nonInteractive: options.nonInteractive, instanceId, }); @@ -267,6 +356,7 @@ export default new Command("ext:update [updateSource]") )}` ) ); + manifest.showDeprecationWarning(); } catch (err: any) { if (spinner.isSpinning) { spinner.fail(); diff --git a/src/config.ts b/src/config.ts index edfe7fff985..7b00e4d4172 100644 --- a/src/config.ts +++ b/src/config.ts @@ -203,6 +203,10 @@ export class Config { fs.writeFileSync(this.path(p), content, "utf8"); } + deleteProjectFile(p: string) { + fs.removeSync(this.path(p)); + } + askWriteProjectFile(p: string, content: any, force?: boolean) { const writeTo = this.path(p); let next; diff --git a/src/deploy/extensions/params.ts b/src/deploy/extensions/params.ts deleted file mode 100644 index 152303cc8f4..00000000000 --- a/src/deploy/extensions/params.ts +++ /dev/null @@ -1,58 +0,0 @@ -import * as path from "path"; -import { logger } from "../../logger"; - -import { readEnvFile } from "../../extensions/paramHelper"; -import { FirebaseError } from "../../error"; - -const ENV_DIRECTORY = "extensions"; - -/** - * readParams gets the params for an extension instance from the `extensions` folder, - * checking for project specific env files, then falling back to generic env files. - * This checks the following locations & if a param is defined in multiple places, it prefers - * whichever is higher on this list: - * - extensions/{instanceId}.env.{projectID} - * - extensions/{instanceId}.env.{projectNumber} - * - extensions/{instanceId}.env.{projectAlias} - * - extensions/{instanceId}.env - */ -export function readParams(args: { - projectDir: string; - projectId: string; - projectNumber: string; - aliases: string[]; - instanceId: string; - checkLocal?: boolean; -}): Record { - const filesToCheck = [ - `${args.instanceId}.env`, - ...args.aliases.map((alias) => `${args.instanceId}.env.${alias}`), - `${args.instanceId}.env.${args.projectNumber}`, - `${args.instanceId}.env.${args.projectId}`, - ]; - if (args.checkLocal) { - filesToCheck.push(`${args.instanceId}.env.local`); - } - let noFilesFound = true; - const combinedParams = {}; - for (const fileToCheck of filesToCheck) { - try { - const params = readParamsFile(args.projectDir, fileToCheck); - logger.debug(`Successfully read params from ${fileToCheck}`); - noFilesFound = false; - Object.assign(combinedParams, params); - } catch (err: any) { - logger.debug(`${err}`); - } - } - if (noFilesFound) { - throw new FirebaseError(`No params file found for ${args.instanceId}`); - } - return combinedParams; -} - -function readParamsFile(projectDir: string, fileName: string): Record { - const paramPath = path.join(projectDir, ENV_DIRECTORY, fileName); - const params = readEnvFile(paramPath); - return params as Record; -} diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 29fcde84e60..1d2c2310b57 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -5,8 +5,8 @@ import { FirebaseError } from "../../error"; import * as extensionsApi from "../../extensions/extensionsApi"; import { getFirebaseProjectParams, substituteParams } from "../../extensions/extensionsHelper"; import * as refs from "../../extensions/refs"; -import { readParams } from "./params"; import { logger } from "../../logger"; +import { readInstanceParam } from "../../extensions/manifest"; export interface InstanceSpec { instanceId: string; @@ -90,7 +90,7 @@ export async function want(args: { const ref = refs.parse(e[1]); ref.version = await resolveVersion(ref); - const params = readParams({ + const params = readInstanceParam({ projectDir: args.projectDir, instanceId, projectId: args.projectId, diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index 4ffc431a6d6..e7e2083caff 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -145,7 +145,7 @@ export interface Author { } export interface Param { - param: string; + param: string; // The key of the {param:value} pair. label: string; description?: string; default?: string; diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index 6b5f070eb56..19fa54e6ce8 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -1,10 +1,16 @@ import * as clc from "cli-color"; - +import * as path from "path"; import * as refs from "./refs"; import { Config } from "../config"; import { InstanceSpec } from "../deploy/extensions/planner"; import { logger } from "../logger"; import { promptOnce } from "../prompt"; +import { readEnvFile } from "./paramHelper"; +import { FirebaseError } from "../error"; +import * as utils from "../utils"; +import { logPrefix } from "./extensionsHelper"; + +const ENV_DIRECTORY = "extensions"; /** * Write a list of instanceSpecs to extensions manifest. @@ -16,11 +22,13 @@ import { promptOnce } from "../prompt"; * @param config existing config in firebase.json * @param options.nonInteractive will try to do the job without asking for user input. * @param options.force only when this flag is true this will overwrite existing .env files + * @param allowOverwrite allows overwriting the entire manifest with the new specs */ export async function writeToManifest( specs: InstanceSpec[], config: Config, - options: { nonInteractive: boolean; force: boolean } + options: { nonInteractive: boolean; force: boolean }, + allowOverwrite: boolean = false ): Promise { if ( config.has("extensions") && @@ -31,16 +39,18 @@ export async function writeToManifest( const currentExtensions = Object.entries(config.get("extensions")) .map((i) => `${i[0]}: ${i[1]}`) .join("\n\t"); - const overwrite = await promptOnce({ - type: "list", - message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`, - choices: [ - { name: "Overwrite", value: true }, - { name: "Merge", value: false }, - ], - }); - if (overwrite) { - config.set("extensions", {}); + if (allowOverwrite) { + const overwrite = await promptOnce({ + type: "list", + message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`, + choices: [ + { name: "Overwrite", value: true }, + { name: "Merge", value: false }, + ], + }); + if (overwrite) { + config.set("extensions", {}); + } } } @@ -48,18 +58,62 @@ export async function writeToManifest( await writeEnvFiles(specs, config, options.force); } -// TODO(lihes): Add some tests. +/** + * Remove an instance from extensions manifest. + */ +export function removeFromManifest(instanceId: string, config: Config) { + if (!instanceExists(instanceId, config)) { + throw new FirebaseError(`Extension instance ${instanceId} not found in firebase.json.`); + } + + const extensions = config.get("extensions", {}); + extensions[instanceId] = undefined; + config.set("extensions", extensions); + config.writeProjectFile("firebase.json", config.src); + logger.info(`Removed extension instance ${instanceId} from firebase.json`); + + config.deleteProjectFile(`extensions/${instanceId}.env`); + logger.info(`Removed extension instance environment config extensions/${instanceId}.env`); + config.deleteProjectFile(`extensions/${instanceId}.env.local`); + logger.info(`Removed extension instance environment config extensions/${instanceId}.env.local`); + // TODO(lihes): Remove all project specific env files. +} + +export function loadConfig(options: any): Config { + const existingConfig = Config.load(options, true); + if (!existingConfig) { + throw new FirebaseError( + "Not currently in a Firebase directory. Run `firebase init` to create a Firebase directory." + ); + } + return existingConfig; +} + +/** + * Checks if an instance name already exists in the manifest. + */ +export function instanceExists(instanceId: string, config: Config): boolean { + return !!config.get("extensions", {})[instanceId]; +} + +export function getInstanceRef(instanceId: string, config: Config): refs.Ref { + if (!instanceExists(instanceId, config)) { + throw new FirebaseError(`Could not find extension instance ${instanceId} in firebase.json`); + } + const ref = config.get("extensions", {})[instanceId]; + return refs.parse(ref); +} + function writeExtensionsToFirebaseJson(specs: InstanceSpec[], config: Config): void { const extensions = config.get("extensions", {}); for (const s of specs) { extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref!); } config.set("extensions", extensions); - logger.info("Adding Extensions to " + clc.bold("firebase.json") + "..."); config.writeProjectFile("firebase.json", config.src); + utils.logSuccess("Wrote extensions to " + clc.bold("firebase.json") + "..."); } -// TODO(lihes): Add some tests. async function writeEnvFiles( specs: InstanceSpec[], config: Config, @@ -72,3 +126,87 @@ async function writeEnvFiles( await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force); } } + +/** + * readParams gets the params for an extension instance from the `extensions` folder, + * checking for project specific env files, then falling back to generic env files. + * This checks the following locations & if a param is defined in multiple places, it prefers + * whichever is higher on this list: + * - extensions/{instanceId}.env.local (only if checkLocal is true) + * - extensions/{instanceId}.env.{projectID} + * - extensions/{instanceId}.env.{projectNumber} + * - extensions/{instanceId}.env.{projectAlias} + * - extensions/{instanceId}.env + */ +export function readInstanceParam(args: { + instanceId: string; + projectDir: string; + projectId?: string; + projectNumber?: string; + aliases?: string[]; + checkLocal?: boolean; +}): Record { + const aliases = args.aliases ?? []; + const filesToCheck = [ + `${args.instanceId}.env`, + ...aliases.map((alias) => `${args.instanceId}.env.${alias}`), + ...(args.projectNumber ? [`${args.instanceId}.env.${args.projectNumber}`] : []), + ...(args.projectId ? [`${args.instanceId}.env.${args.projectId}`] : []), + ]; + if (args.checkLocal) { + filesToCheck.push(`${args.instanceId}.env.local`); + } + let noFilesFound = true; + const combinedParams = {}; + for (const fileToCheck of filesToCheck) { + try { + const params = readParamsFile(args.projectDir, fileToCheck); + logger.debug(`Successfully read params from ${fileToCheck}`); + noFilesFound = false; + Object.assign(combinedParams, params); + } catch (err: any) { + logger.debug(`${err}`); + } + } + if (noFilesFound) { + throw new FirebaseError(`No params file found for ${args.instanceId}`); + } + return combinedParams; +} + +function readParamsFile(projectDir: string, fileName: string): Record { + const paramPath = path.join(projectDir, ENV_DIRECTORY, fileName); + const params = readEnvFile(paramPath); + return params as Record; +} + +// TODO(lihes): Add a docs link once exists. +/** + * Show deprecation warning about --local flag taking over current default bahaviors. + */ +export function showDeprecationWarning() { + utils.logLabeledWarning( + logPrefix, + "The behavior of ext:install, ext:update, ext:configure, and ext:uninstall will change in firebase-tools@11.0.0. " + + "Instead of deploying extensions directly, " + + "changes to extension instances will be written to firebase.json and ./extensions/*.env. " + + `Then ${clc.bold( + "firebase deploy (--only extensions)" + )} will deploy the changes to your Firebase project. ` + + `To access this behavior now, pass the ${clc.bold("--local")} flag.` + ); +} + +// TODO(lihes): Add a docs link once exists. +/** + * Show preview warning about --local flag needing deploy to take effect in firebase project. + */ +export function showPreviewWarning() { + utils.logLabeledWarning( + logPrefix, + "These changes will be reflected in your Firebase Emulator after restart. " + + `Run ${clc.bold( + "firebase deploy (--only extensions)" + )} to deploy the changes to your Firebase project. ` + ); +} diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 50717fab2a5..6ca61f9a259 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -23,7 +23,7 @@ import * as env from "../functions/env"; * @param params A list of params * @param newDefaults a map of { PARAM_NAME: default_value } */ -function setNewDefaults( +export function setNewDefaults( params: extensionsApi.Param[], newDefaults: { [key: string]: string } ): extensionsApi.Param[] { @@ -58,10 +58,10 @@ export function getParamsWithCurrentValuesAsDefaults( */ export async function getParams(args: { projectId: string; + instanceId: string; paramSpecs: extensionsApi.Param[]; nonInteractive?: boolean; paramsEnvPath?: string; - instanceId: string; reconfiguring?: boolean; }): Promise<{ [key: string]: string }> { let params: any; @@ -157,21 +157,19 @@ export async function promptForNewParams(args: { const comparer = (param1: extensionsApi.Param, param2: extensionsApi.Param) => { return param1.type === param2.type && param1.param === param2.param; }; - let paramsDiffDeletions = _.differenceWith( - args.spec.params, - _.get(args.newSpec, "params", []), - comparer + + // Some params are in the spec but not in currentParams, remove so we can prompt for them. + const oldParams = args.spec.params.filter((p) => + Object.keys(args.currentParams).includes(p.param) ); + + let paramsDiffDeletions = _.differenceWith(oldParams, args.newSpec.params, comparer); paramsDiffDeletions = substituteParams( paramsDiffDeletions, firebaseProjectParams ); - let paramsDiffAdditions = _.differenceWith( - args.newSpec.params, - _.get(args.spec, "params", []), - comparer - ); + let paramsDiffAdditions = _.differenceWith(args.newSpec.params, oldParams, comparer); paramsDiffAdditions = substituteParams( paramsDiffAdditions, firebaseProjectParams @@ -199,7 +197,7 @@ export async function promptForNewParams(args: { return args.currentParams; } -export function getParamsFromFile(args: { +function getParamsFromFile(args: { projectId: string; paramSpecs: extensionsApi.Param[]; paramsEnvPath: string; diff --git a/src/test/deploy/extensions/params.spec.ts b/src/test/deploy/extensions/params.spec.ts deleted file mode 100644 index 02f1f7869d7..00000000000 --- a/src/test/deploy/extensions/params.spec.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { expect } from "chai"; -import * as sinon from "sinon"; - -import * as params from "../../../deploy/extensions/params"; -import * as paramHelper from "../../../extensions/paramHelper"; - -describe("readParams", () => { - let readEnvFileStub: sinon.SinonStub; - const testProjectDir = "test"; - const testProjectId = "my-project"; - const testProjectNumber = "123456"; - const testInstanceId = "extensionId"; - - beforeEach(() => { - readEnvFileStub = sinon.stub(paramHelper, "readEnvFile").returns({}); - }); - - afterEach(() => { - readEnvFileStub.restore(); - }); - - it("should read from generic .env file", () => { - readEnvFileStub - .withArgs("test/extensions/extensionId.env") - .returns({ param: "otherValue", param2: "value2" }); - - expect( - params.readParams({ - projectDir: testProjectDir, - instanceId: testInstanceId, - projectId: testProjectId, - projectNumber: testProjectNumber, - aliases: [], - }) - ).to.deep.equal({ param: "otherValue", param2: "value2" }); - }); - - it("should read from project id .env file", () => { - readEnvFileStub - .withArgs("test/extensions/extensionId.env.my-project") - .returns({ param: "otherValue", param2: "value2" }); - - expect( - params.readParams({ - projectDir: testProjectDir, - instanceId: testInstanceId, - projectId: testProjectId, - projectNumber: testProjectNumber, - aliases: [], - }) - ).to.deep.equal({ param: "otherValue", param2: "value2" }); - }); - - it("should read from project number .env file", () => { - readEnvFileStub - .withArgs("test/extensions/extensionId.env.123456") - .returns({ param: "otherValue", param2: "value2" }); - - expect( - params.readParams({ - projectDir: testProjectDir, - instanceId: testInstanceId, - projectId: testProjectId, - projectNumber: testProjectNumber, - aliases: [], - }) - ).to.deep.equal({ param: "otherValue", param2: "value2" }); - }); - - it("should read from an alias .env file", () => { - readEnvFileStub - .withArgs("test/extensions/extensionId.env.prod") - .returns({ param: "otherValue", param2: "value2" }); - - expect( - params.readParams({ - projectDir: testProjectDir, - instanceId: testInstanceId, - projectId: testProjectId, - projectNumber: testProjectNumber, - aliases: ["prod"], - }) - ).to.deep.equal({ param: "otherValue", param2: "value2" }); - }); - - it("should prefer values from project specific env files", () => { - readEnvFileStub - .withArgs("test/extensions/extensionId.env.my-project") - .returns({ param: "value" }); - readEnvFileStub - .withArgs("test/extensions/extensionId.env") - .returns({ param: "otherValue", param2: "value2" }); - - expect( - params.readParams({ - projectDir: testProjectDir, - instanceId: testInstanceId, - projectId: testProjectId, - projectNumber: testProjectNumber, - aliases: [], - }) - ).to.deep.equal({ param: "value", param2: "value2" }); - }); -}); diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts new file mode 100644 index 00000000000..1b261bd28e1 --- /dev/null +++ b/src/test/extensions/manifest.spec.ts @@ -0,0 +1,301 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; + +import * as manifest from "../../extensions/manifest"; +import * as paramHelper from "../../extensions/paramHelper"; +import * as refs from "../../extensions/refs"; + +import { Config } from "../../config"; +import * as prompt from "../../prompt"; +import { FirebaseError } from "../../error"; + +/** + * Returns a base Config with some extensions data. + * + * The inner content cannot be a constant because Config edits in-place and mutates + * the state between tests. + */ +function generateBaseConfig(): Config { + return new Config( + { + extensions: { + "delete-user-data": "firebase/delete-user-data@0.1.12", + "delete-user-data-gm2h": "firebase/delete-user-data@0.1.12", + }, + }, + {} + ); +} + +describe("manifest", () => { + const sandbox: sinon.SinonSandbox = sinon.createSandbox(); + + describe(`${manifest.instanceExists.name}`, () => { + it("should return true for an existing instance", () => { + const result = manifest.instanceExists("delete-user-data", generateBaseConfig()); + + expect(result).to.be.true; + }); + + it("should return false for a non-existing instance", () => { + const result = manifest.instanceExists("does-not-exist", generateBaseConfig()); + + expect(result).to.be.false; + }); + }); + + describe(`${manifest.getInstanceRef.name}`, () => { + it("should return the correct ref for an existing instance", () => { + const result = manifest.getInstanceRef("delete-user-data", generateBaseConfig()); + + expect(refs.toExtensionVersionRef(result)).to.equal( + refs.toExtensionVersionRef({ + publisherId: "firebase", + extensionId: "delete-user-data", + version: "0.1.12", + }) + ); + }); + + it("should throw when looking for a non-existing instance", () => { + expect(() => manifest.getInstanceRef("does-not-exist", generateBaseConfig())).to.throw( + FirebaseError + ); + }); + }); + + describe(`${manifest.removeFromManifest.name}`, () => { + let deleteProjectFileStub: sinon.SinonStub; + let writeProjectFileStub: sinon.SinonStub; + beforeEach(() => { + deleteProjectFileStub = sandbox.stub(Config.prototype, "deleteProjectFile"); + writeProjectFileStub = sandbox.stub(Config.prototype, "writeProjectFile"); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should remove from firebase.json and remove .env file", () => { + const result = manifest.removeFromManifest("delete-user-data", generateBaseConfig()); + + expect(writeProjectFileStub).calledWithExactly("firebase.json", { + extensions: { + "delete-user-data": undefined, + "delete-user-data-gm2h": "firebase/delete-user-data@0.1.12", + }, + }); + + expect(deleteProjectFileStub).calledWithExactly("extensions/delete-user-data.env"); + }); + }); + + describe(`${manifest.writeToManifest.name}`, () => { + let askWriteProjectFileStub: sinon.SinonStub; + let writeProjectFileStub: sinon.SinonStub; + beforeEach(() => { + askWriteProjectFileStub = sandbox.stub(Config.prototype, "askWriteProjectFile"); + writeProjectFileStub = sandbox.stub(Config.prototype, "writeProjectFile"); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should write to both firebase.json and env files", async () => { + await manifest.writeToManifest( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { a: "pikachu", b: "bulbasaur" }, + }, + { + instanceId: "instance-2", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "2.0.0", + }, + params: { a: "eevee", b: "squirtle" }, + }, + ], + generateBaseConfig(), + { nonInteractive: false, force: false } + ); + expect(writeProjectFileStub).calledWithExactly("firebase.json", { + extensions: { + "delete-user-data": "firebase/delete-user-data@0.1.12", + "delete-user-data-gm2h": "firebase/delete-user-data@0.1.12", + "instance-1": "firebase/bigquery-export@1.0.0", + "instance-2": "firebase/bigquery-export@2.0.0", + }, + }); + + expect(askWriteProjectFileStub).to.have.been.calledTwice; + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-1.env", + `a=pikachu\nb=bulbasaur`, + false + ); + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-2.env", + `a=eevee\nb=squirtle`, + false + ); + }); + + it("should overwrite when user chooses to", async () => { + // Chooses to overwrite instead of merge. + sandbox.stub(prompt, "promptOnce").resolves(true); + + await manifest.writeToManifest( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { a: "pikachu", b: "bulbasaur" }, + }, + { + instanceId: "instance-2", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "2.0.0", + }, + params: { a: "eevee", b: "squirtle" }, + }, + ], + generateBaseConfig(), + { nonInteractive: false, force: false }, + true /** allowOverwrite */ + ); + expect(writeProjectFileStub).calledWithExactly("firebase.json", { + extensions: { + // Original list deleted here. + "instance-1": "firebase/bigquery-export@1.0.0", + "instance-2": "firebase/bigquery-export@2.0.0", + }, + }); + + expect(askWriteProjectFileStub).to.have.been.calledTwice; + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-1.env", + `a=pikachu\nb=bulbasaur`, + false + ); + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-2.env", + `a=eevee\nb=squirtle`, + false + ); + }); + }); + + describe("readParams", () => { + let readEnvFileStub: sinon.SinonStub; + const testProjectDir = "test"; + const testProjectId = "my-project"; + const testProjectNumber = "123456"; + const testInstanceId = "extensionId"; + + beforeEach(() => { + readEnvFileStub = sinon.stub(paramHelper, "readEnvFile").returns({}); + }); + + afterEach(() => { + readEnvFileStub.restore(); + }); + + it("should read from generic .env file", () => { + readEnvFileStub + .withArgs("test/extensions/extensionId.env") + .returns({ param: "otherValue", param2: "value2" }); + + expect( + manifest.readInstanceParam({ + projectDir: testProjectDir, + instanceId: testInstanceId, + projectId: testProjectId, + projectNumber: testProjectNumber, + aliases: [], + }) + ).to.deep.equal({ param: "otherValue", param2: "value2" }); + }); + + it("should read from project id .env file", () => { + readEnvFileStub + .withArgs("test/extensions/extensionId.env.my-project") + .returns({ param: "otherValue", param2: "value2" }); + + expect( + manifest.readInstanceParam({ + projectDir: testProjectDir, + instanceId: testInstanceId, + projectId: testProjectId, + projectNumber: testProjectNumber, + aliases: [], + }) + ).to.deep.equal({ param: "otherValue", param2: "value2" }); + }); + + it("should read from project number .env file", () => { + readEnvFileStub + .withArgs("test/extensions/extensionId.env.123456") + .returns({ param: "otherValue", param2: "value2" }); + + expect( + manifest.readInstanceParam({ + projectDir: testProjectDir, + instanceId: testInstanceId, + projectId: testProjectId, + projectNumber: testProjectNumber, + aliases: [], + }) + ).to.deep.equal({ param: "otherValue", param2: "value2" }); + }); + + it("should read from an alias .env file", () => { + readEnvFileStub + .withArgs("test/extensions/extensionId.env.prod") + .returns({ param: "otherValue", param2: "value2" }); + + expect( + manifest.readInstanceParam({ + projectDir: testProjectDir, + instanceId: testInstanceId, + projectId: testProjectId, + projectNumber: testProjectNumber, + aliases: ["prod"], + }) + ).to.deep.equal({ param: "otherValue", param2: "value2" }); + }); + + it("should prefer values from project specific env files", () => { + readEnvFileStub + .withArgs("test/extensions/extensionId.env.my-project") + .returns({ param: "value" }); + readEnvFileStub + .withArgs("test/extensions/extensionId.env") + .returns({ param: "otherValue", param2: "value2" }); + + expect( + manifest.readInstanceParam({ + projectDir: testProjectDir, + instanceId: testInstanceId, + projectId: testProjectId, + projectNumber: testProjectNumber, + aliases: [], + }) + ).to.deep.equal({ param: "value", param2: "value2" }); + }); + }); +}); diff --git a/src/test/extensions/paramHelper.spec.ts b/src/test/extensions/paramHelper.spec.ts index b6ecb183b84..e65ea69d615 100644 --- a/src/test/extensions/paramHelper.spec.ts +++ b/src/test/extensions/paramHelper.spec.ts @@ -267,7 +267,7 @@ describe("paramHelper", () => { version: "0.1.0", roles: [], resources: [], - params: TEST_PARAMS, + params: [...TEST_PARAMS], sourceUrl: "", }, }, @@ -391,6 +391,30 @@ describe("paramHelper", () => { ]); }); + it("should prompt for params that are not currently populated", async () => { + promptStub.resolves("user input"); + const newSpec = _.cloneDeep(SPEC); + newSpec.params = TEST_PARAMS_2; + + const newParams = await paramHelper.promptForNewParams({ + spec: SPEC, + newSpec, + currentParams: { + A_PARAMETER: "value", + // ANOTHER_PARAMETER is not populated + }, + projectId: PROJECT_ID, + instanceId: INSTANCE_ID, + }); + + const expected = { + ANOTHER_PARAMETER: "user input", + NEW_PARAMETER: "user input", + THIRD_PARAMETER: "user input", + }; + expect(newParams).to.eql(expected); + }); + it("should not prompt the user for params that did not change type or param", async () => { promptStub.resolves("Fail"); const newSpec = _.cloneDeep(SPEC); From 49c6273aee229789c957a7454c5127fcfea170ae Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Mon, 7 Mar 2022 19:15:57 -0500 Subject: [PATCH 0133/1699] Fix upload rules resource eval logic (#4260) * multipart and firebase.ts integration * update gcloud.ts * refactor persistence out into StorageEmulator * fix lint and method references * fix tests * upload.ts * update callers of UploadService and tests * more tests * more tests * md -> metadata * address pr comments * lint * fix tests * fix tests * more integration tests, fix upload bug * more fixing * fix remaining * lint * address pr comments * clean * remove only * fix test --- .../storage.rules | 30 +- scripts/storage-emulator-integration/tests.ts | 307 ++++++++---------- src/emulator/storage/files.ts | 2 +- 3 files changed, 172 insertions(+), 167 deletions(-) diff --git a/scripts/storage-emulator-integration/storage.rules b/scripts/storage-emulator-integration/storage.rules index 9c494fbdc1a..3c1a8c64c06 100644 --- a/scripts/storage-emulator-integration/storage.rules +++ b/scripts/storage-emulator-integration/storage.rules @@ -1,9 +1,33 @@ rules_version = '2'; service firebase.storage { match /b/{bucket}/o { - match /{allPaths=**} { - allow read, create, update: if request.auth != null; - allow delete: if request.auth != null && resource.contentType != 'text/plain'; + match /topLevel { + allow list; + } + + match /testing/{allPaths=**} { + allow read, create, update, delete: if request.auth != null; + } + + match /list/{allPaths=**} { + allow list; + allow create; + } + + match /delete { + match /disallowIfContentTypeText { + allow create; + allow delete: if resource.contentType != 'text/plain'; + } + } + + match /upload { + match /allowIfContentTypeImage.png { + allow create: if request.resource.contentType == 'image/blah'; + } + match /replace.txt { + allow read, create; + } } match /public/{allPaths=**} { diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index b367ae00a58..210af482138 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -129,6 +129,35 @@ async function resetStorageEmulator(emulatorHost: string) { }); } +async function uploadText( + page: puppeteer.Page, + filename: string, + text: string, + format?: string, + metadata?: firebase.storage.UploadMetadata +): Promise { + return page.evaluate( + async (filename, text, format, metadata) => { + try { + const task = await firebase + .storage() + .ref(filename) + .putString(text, format, JSON.parse(metadata)); + return task.state; + } catch (err) { + if (err instanceof Error) { + throw err.message; + } + throw err; + } + }, + filename, + text, + format ?? "raw", + JSON.stringify(metadata ?? {}) + )!; +} + describe("Storage emulator", () => { let test: TriggerEndToEndTest; let browser: puppeteer.Browser; @@ -637,125 +666,6 @@ describe("Storage emulator", () => { } }); - it("should upload a file", async function (this) { - this.timeout(TEST_SETUP_TIMEOUT); - - const uploadState = await page.evaluate((IMAGE_FILE_BASE64) => { - const auth = (window as any).auth as firebase.auth.Auth; - - return auth - .signInAnonymously() - .then(() => { - return firebase - .storage() - .ref("testing/image.png") - .putString(IMAGE_FILE_BASE64, "base64"); - }) - .then((task) => { - return task.state; - }) - .catch((err) => { - throw err.message; - }); - }, IMAGE_FILE_BASE64); - - expect(uploadState).to.equal("success"); - }); - - it("should upload replace existing file", async function (this) { - this.timeout(TEST_SETUP_TIMEOUT); - - const uploadText = (text: string) => - page.evaluate((TEXT_FILE) => { - const auth = (window as any).auth as firebase.auth.Auth; - - return auth - .signInAnonymously() - .then(() => { - return firebase.storage().ref("replace.txt").putString(TEXT_FILE); - }) - .then((task) => { - return task.state; - }) - .catch((err) => { - throw err.message; - }); - }, text); - - await uploadText("some-content"); - await uploadText("some-other-content"); - - const downloadUrl = await page.evaluate((filename) => { - return firebase.storage().ref("replace.txt").getDownloadURL(); - }, filename); - - const requestClient = TEST_CONFIG.useProductionServers ? https : http; - await new Promise((resolve, reject) => { - requestClient.get( - downloadUrl, - { - headers: { - // This is considered an authorized request in the emulator - Authorization: "Bearer owner", - }, - }, - (response) => { - const data: any = []; - response - .on("data", (chunk) => data.push(chunk)) - .on("end", () => { - expect(Buffer.concat(data).toString()).to.equal("some-other-content"); - }) - .on("close", resolve) - .on("error", reject); - } - ); - }); - }); - - it("should upload a file into a directory", async () => { - const uploadState = await page.evaluate((IMAGE_FILE_BASE64) => { - const auth = (window as any).auth as firebase.auth.Auth; - - return auth - .signInAnonymously() - .then(() => { - return firebase - .storage() - .ref("testing/storage_ref/big/path/image.png") - .putString(IMAGE_FILE_BASE64, "base64"); - }) - .then((task) => { - return task.state; - }) - .catch((err) => { - throw err.message; - }); - }, IMAGE_FILE_BASE64); - - expect(uploadState).to.equal("success"); - }); - - it("should upload a file using put", async () => { - const uploadState = await page.evaluate((IMAGE_FILE_BASE64) => { - const auth = (window as any).auth as firebase.auth.Auth; - const _file = new File([IMAGE_FILE_BASE64], "toUpload.txt"); - return auth - .signInAnonymously() - .then(() => { - return firebase.storage().ref("image_put.png").put(_file); - }) - .then((task) => { - return task.state; - }) - .catch((err) => { - throw err.message; - }); - }, IMAGE_FILE_BASE64); - - expect(uploadState).to.equal("success"); - }); - describe(".ref()", () => { beforeEach(async function (this) { this.timeout(TEST_SETUP_TIMEOUT); @@ -787,6 +697,97 @@ describe("Storage emulator", () => { ); }); + describe("#put()", () => { + it("should upload a file", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + const uploadState = await uploadText( + page, + "testing/image.png", + IMAGE_FILE_BASE64, + "base64" + ); + + expect(uploadState).to.equal("success"); + }); + + it("should upload replace existing file", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + await uploadText(page, "upload/replace.txt", "some-content"); + await uploadText(page, "upload/replace.txt", "some-other-content"); + + const downloadUrl = await page.evaluate((filename) => { + return firebase.storage().ref("upload/replace.txt").getDownloadURL(); + }, filename); + + const requestClient = TEST_CONFIG.useProductionServers ? https : http; + await new Promise((resolve, reject) => { + requestClient.get( + downloadUrl, + { + headers: { + // This is considered an authorized request in the emulator + Authorization: "Bearer owner", + }, + }, + (response) => { + const data: any = []; + response + .on("data", (chunk) => data.push(chunk)) + .on("end", () => { + expect(Buffer.concat(data).toString()).to.equal("some-other-content"); + }) + .on("close", resolve) + .on("error", reject); + } + ); + }); + }); + + it("should upload a file using put", async () => { + const uploadState = await page.evaluate(async (IMAGE_FILE_BASE64) => { + const task = await firebase + .storage() + .ref("testing/image_put.png") + .put(new File([IMAGE_FILE_BASE64], "toUpload.txt")); + return task.state; + }, IMAGE_FILE_BASE64); + + expect(uploadState).to.equal("success"); + }); + + it("should upload a file with custom metadata", async () => { + const uploadState = await page.evaluate(async (IMAGE_FILE_BASE64) => { + const task = await firebase + .storage() + .ref("upload/allowIfContentTypeImage.png") + .put(new File([IMAGE_FILE_BASE64], "toUpload.txt"), { contentType: "image/blah" }); + return task.state; + }, IMAGE_FILE_BASE64); + + expect(uploadState).to.equal("success"); + }); + + it("should return a 403 on rules deny", async () => { + const uploadState = await page.evaluate(async (IMAGE_FILE_BASE64) => { + const _file = new File([IMAGE_FILE_BASE64], "toUpload.txt"); + try { + const task = await firebase + .storage() + .ref("upload/allowIfContentTypeImage.png") + .put(_file, { contentType: "text/plain" }); + return task.state; + } catch (err: any) { + if (err instanceof Error) { + return err.message; + } + throw err; + } + }, IMAGE_FILE_BASE64); + expect(uploadState!).to.include("User does not have permission"); + }); + }); + describe("#listAll()", () => { beforeEach(async function (this) { this.timeout(TEST_SETUP_TIMEOUT); @@ -899,39 +900,19 @@ describe("Storage emulator", () => { }); it("should list at /", async () => { - await page.evaluate( - async (IMAGE_FILE_BASE64, filename) => { - const auth = (window as any).auth as firebase.auth.Auth; - try { - await auth.signInAnonymously(); - const task = await firebase - .storage() - .ref(filename) - .putString(IMAGE_FILE_BASE64, "base64"); - return task.state; - } catch (err: any) { - throw err.message; - } - }, - IMAGE_FILE_BASE64, - `file.jpg` - ); - - const listResult = await page.evaluate(() => { - return firebase - .storage() - .ref() - .listAll() - .then((list) => { - return { - prefixes: list.prefixes.map((prefix) => prefix.name), - items: list.items.map((item) => item.name), - }; - }); + await uploadText(page, "list/file.jpg", "hello"); + await uploadText(page, "list/subdir/file.jpg", "world"); + + const listResult = await page.evaluate(async () => { + const list = await firebase.storage().ref("/list").listAll(); + return { + prefixes: list.prefixes.map((prefix) => prefix.name), + items: list.items.map((item) => item.name), + }; }); expect(listResult).to.deep.equal({ - prefixes: ["testing"], + prefixes: ["subdir"], items: ["file.jpg"], }); }); @@ -1209,21 +1190,21 @@ describe("Storage emulator", () => { }); it("should not delete file when security rule on resource object disallows it", async () => { - await page.evaluate((filename) => { - return firebase.storage().ref(filename).updateMetadata({ contentType: "text/plain" }); - }, filename); + await uploadText(page, "delete/disallowIfContentTypeText", "some-content", undefined, { + contentType: "text/plain", + }); - const error = await page.evaluate((filename) => { - return new Promise((resolve) => { - firebase - .storage() - .ref(filename) - .delete() - .catch((err) => { - resolve(err.message); - }); - }); - }, filename); + const error: string = await page.evaluate(async (filename) => { + try { + await firebase.storage().ref(filename).delete(); + return "success"; + } catch (err) { + if (err instanceof Error) { + return err.message; + } + throw err; + } + }, "delete/disallowIfContentTypeText"); expect(error).to.contain("does not have permission to access"); }); diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 1105b72315b..f0cd97fbcca 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -310,7 +310,7 @@ export class StorageLayer { (await this._validator.validate( ["b", upload.bucketId, "o", upload.objectId].join("/"), RulesetOperationMethod.CREATE, - { before: metadata?.asRulesResource() }, + { after: metadata?.asRulesResource() }, upload.authorization )); if (!authorized) { From 21f93c50bda8c3eadaab7de612816eba5d372e50 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 8 Mar 2022 09:25:48 -0800 Subject: [PATCH 0134/1699] ext:export now uses stable ordering for params (#4267) --- CHANGELOG.md | 1 + src/extensions/manifest.ts | 3 ++ src/test/extensions/manifest.spec.ts | 47 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5c4a34424..dd7496e4312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Fixes bug where functions' memory configurations weren't preserved in batched function deploys (#4253). +- `ext:export` now uses stable ordering for params in .env files (#4256). diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index 19fa54e6ce8..aba21ce5fb5 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -121,6 +121,9 @@ async function writeEnvFiles( ): Promise { for (const spec of specs) { const content = Object.entries(spec.params) + .sort((a, b) => { + return a[0].localeCompare(b[0]); + }) .map((r) => `${r[0]}=${r[1]}`) .join("\n"); await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force); diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts index 1b261bd28e1..6f23b82474a 100644 --- a/src/test/extensions/manifest.spec.ts +++ b/src/test/extensions/manifest.spec.ts @@ -149,6 +149,53 @@ describe("manifest", () => { ); }); + it("should write to env files in stable, alphabetical by key order", async () => { + await manifest.writeToManifest( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { b: "bulbasaur", a: "absol" }, + }, + { + instanceId: "instance-2", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "2.0.0", + }, + params: { e: "eevee", s: "squirtle" }, + }, + ], + generateBaseConfig(), + { nonInteractive: false, force: false } + ); + expect(writeProjectFileStub).calledWithExactly("firebase.json", { + extensions: { + "delete-user-data": "firebase/delete-user-data@0.1.12", + "delete-user-data-gm2h": "firebase/delete-user-data@0.1.12", + "instance-1": "firebase/bigquery-export@1.0.0", + "instance-2": "firebase/bigquery-export@2.0.0", + }, + }); + + expect(askWriteProjectFileStub).to.have.been.calledTwice; + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-1.env", + `a=absol\nb=bulbasaur`, + false + ); + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-2.env", + `e=eevee\ns=squirtle`, + false + ); + }); + it("should overwrite when user chooses to", async () => { // Chooses to overwrite instead of merge. sandbox.stub(prompt, "promptOnce").resolves(true); From 37ee4b4bb31abbe9e4f6bcd131aebb5397dbd40c Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Tue, 8 Mar 2022 12:56:46 -0500 Subject: [PATCH 0135/1699] Add method to parse multiple targets for Storage Emulator rules (#4263) --- src/emulator/controller.ts | 10 +- src/emulator/storage/index.ts | 11 ++- src/emulator/storage/rules/config.ts | 43 +++++++++ .../emulators/storage/rules/config.spec.ts | 91 +++++++++++++++++++ .../emulators/storage/rules/storage.rules | 7 ++ 5 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 src/emulator/storage/rules/config.ts create mode 100644 src/test/emulators/storage/rules/config.spec.ts create mode 100644 src/test/emulators/storage/rules/storage.rules diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 1cdcc6e90f2..3eb2dd919b8 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -38,6 +38,7 @@ import { promptOnce } from "../prompt"; import { FLAG_EXPORT_ON_EXIT_NAME } from "./commandUtils"; import { fileExistsSync } from "../fsutils"; import { StorageEmulator } from "./storage"; +import { getStorageRulesConfig } from "./storage/rules/config"; import { getDefaultDatabaseInstance } from "../getDefaultDatabaseInstance"; import { getProjectDefaultAccount } from "../auth"; import { Options } from "../options"; @@ -692,19 +693,12 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) if (shouldStart(options, Emulators.STORAGE)) { const storageAddr = await getAndCheckAddress(Emulators.STORAGE, options); - const storageConfig = options.config.data.storage; - - if (!storageConfig?.rules) { - throw new FirebaseError( - "Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration" - ); - } const storageEmulator = new StorageEmulator({ host: storageAddr.host, port: storageAddr.port, projectId: projectId, - rules: options.config.path(storageConfig.rules), + rules: getStorageRulesConfig(projectId, options), }); await startEmulator(storageEmulator); diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index 4514794dc81..e6816bda0a4 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -13,11 +13,16 @@ import { getRulesValidator } from "./rules/utils"; import { Persistence } from "./persistence"; import { UploadService } from "./upload"; +export type RulesConfig = { + resource: string; + rules: string; +}; + export interface StorageEmulatorArgs { projectId: string; port?: number; host?: string; - rules: SourceFile | string; + rules: RulesConfig[]; auto_download?: boolean; } @@ -69,7 +74,9 @@ export class StorageEmulator implements EmulatorInstance { async start(): Promise { const { host, port } = this.getInfo(); await this._rulesRuntime.start(this.args.auto_download); - await this._rulesManager.setSourceFile(this.args.rules); + + // TODO(hsinpei): set source file for multiple resources + await this._rulesManager.setSourceFile(this.args.rules[0].rules); this._app = await createApp(this.args.projectId, this); const server = this._app.listen(port, host); this.destroyServer = utils.createDestroyer(server); diff --git a/src/emulator/storage/rules/config.ts b/src/emulator/storage/rules/config.ts new file mode 100644 index 00000000000..8fc95eeda27 --- /dev/null +++ b/src/emulator/storage/rules/config.ts @@ -0,0 +1,43 @@ +import { RulesConfig } from ".."; +import { FirebaseError } from "../../../error"; +import { Options } from "../../../options"; + +function getAbsoluteRulesPath(rules: string, options: Options): string { + return options.config.path(rules); +} + +export function getStorageRulesConfig(projectId: string, options: Options): RulesConfig[] { + const storageConfig = options.config.data.storage; + if (!storageConfig) { + throw new FirebaseError( + "Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration" + ); + } + + // Single resource + if (!Array.isArray(storageConfig)) { + if (!storageConfig.rules) { + throw new FirebaseError( + "Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration" + ); + } + + // TODO(hsinpei): set default resource + const resource = "default"; + return [{ resource, rules: getAbsoluteRulesPath(storageConfig.rules, options) }]; + } + + // Multiple resources + const results: RulesConfig[] = []; + const { rc } = options; + for (const targetConfig of storageConfig) { + if (!targetConfig.target) { + throw new FirebaseError("Must supply 'target' in Storage configuration"); + } + rc.requireTarget(projectId, "storage", targetConfig.target); + rc.target(projectId, "storage", targetConfig.target).forEach((resource: string) => { + results.push({ resource, rules: getAbsoluteRulesPath(targetConfig.rules, options) }); + }); + } + return results; +} diff --git a/src/test/emulators/storage/rules/config.spec.ts b/src/test/emulators/storage/rules/config.spec.ts new file mode 100644 index 00000000000..09f1d66bc82 --- /dev/null +++ b/src/test/emulators/storage/rules/config.spec.ts @@ -0,0 +1,91 @@ +import * as path from "path"; +import { expect } from "chai"; +import { readFileSync } from "fs-extra"; +import { tmpdir } from "os"; +import { v4 as uuidv4 } from "uuid"; + +import { Config } from "../../../../config"; +import { Options } from "../../../../options"; +import { RC } from "../../../../rc"; +import { getStorageRulesConfig } from "../../../../emulator/storage/rules/config"; +import { StorageRulesFiles } from "../../fixtures"; +import { Persistence } from "../../../../emulator/storage/persistence"; +import { FirebaseError } from "../../../../error"; + +const PROJECT_ID = "test-project"; + +describe("Storage Rules Config", () => { + const tmpDir = `${tmpdir()}/${uuidv4()}`; + const persistence = new Persistence(tmpDir); + const resolvePath = (fileName: string) => path.resolve(tmpDir, fileName); + + it("should parse rules config for single target", () => { + const rulesFile = "storage.rules"; + persistence.appendBytes(rulesFile, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); + + const config = getOptions({ + data: { storage: { rules: rulesFile } }, + path: resolvePath, + }); + const result = getStorageRulesConfig(PROJECT_ID, config); + + expect(result.length).to.equal(1); + expect(result[0].rules).to.equal(`${tmpDir}/storage.rules`); + }); + + it("should parse rules file for multiple targets", () => { + const config = getOptions({ + data: { + storage: [ + { target: "main", rules: "storage_main.rules" }, + { target: "other", rules: "storage_other.rules" }, + ], + }, + path: resolvePath, + }); + config.rc.applyTarget(PROJECT_ID, "storage", "main", ["bucket_1", "bucket_2"]); + config.rc.applyTarget(PROJECT_ID, "storage", "other", ["bucket_3"]); + + const result = getStorageRulesConfig(PROJECT_ID, config); + + expect(result.length).to.equal(3); + expect(result[0]).to.eql({ resource: "bucket_1", rules: `${tmpDir}/storage_main.rules` }); + expect(result[1]).to.eql({ resource: "bucket_2", rules: `${tmpDir}/storage_main.rules` }); + expect(result[2]).to.eql({ resource: "bucket_3", rules: `${tmpDir}/storage_other.rules` }); + }); + + it("should throw FirebaseError when storage config is missing", () => { + const config = getOptions({ data: {}, path: resolvePath }); + expect(() => getStorageRulesConfig(PROJECT_ID, config)).to.throw( + FirebaseError, + "Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration" + ); + }); + + it("should throw FirebaseError when rules file is missing", () => { + const config = getOptions({ data: { storage: {} }, path: resolvePath }); + expect(() => getStorageRulesConfig(PROJECT_ID, config)).to.throw( + FirebaseError, + "Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration" + ); + }); +}); + +function getOptions(config: any): Options { + return { + cwd: "/", + configPath: "/", + /* eslint-disable-next-line */ + config, + only: "", + except: "", + nonInteractive: false, + json: false, + interactive: false, + debug: false, + force: false, + filteredTargets: [], + rc: new RC(), + project: PROJECT_ID, + }; +} diff --git a/src/test/emulators/storage/rules/storage.rules b/src/test/emulators/storage/rules/storage.rules new file mode 100644 index 00000000000..1392ea1d571 --- /dev/null +++ b/src/test/emulators/storage/rules/storage.rules @@ -0,0 +1,7 @@ +service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write: if request.auth != null; + } + } +} From e9ce0870c6264c57cda0ba621f90b857e0c33393 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Tue, 8 Mar 2022 15:11:21 -0500 Subject: [PATCH 0136/1699] Adds new alerting event provider (#4258) * adding notes * fixing api of firealerts service object * cleaning up and adding tests * naming * changelog entry * we now pass in projectId and projectNumber from prepare to requireProjectBindings * updating error message * addressing pr comments * removing unused import --- CHANGELOG.md | 1 + src/deploy/functions/checkIam.ts | 8 +- src/deploy/functions/prepare.ts | 5 +- .../functions/services/firebaseAlerts.ts | 49 ++++++++ src/deploy/functions/services/index.ts | 15 ++- src/deploy/functions/services/storage.ts | 4 +- src/functions/events/v2.ts | 7 +- src/gcp/resourceManager.ts | 14 ++- src/test/deploy/functions/checkIam.spec.ts | 19 +-- .../functions/services/firebaseAlerts.spec.ts | 111 ++++++++++++++++++ .../deploy/functions/services/storage.spec.ts | 8 +- 11 files changed, 213 insertions(+), 28 deletions(-) create mode 100644 src/deploy/functions/services/firebaseAlerts.ts create mode 100644 src/test/deploy/functions/services/firebaseAlerts.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7496e4312..e89be84ba17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Fixes bug where functions' memory configurations weren't preserved in batched function deploys (#4253). - `ext:export` now uses stable ordering for params in .env files (#4256). +- Adds alerting event provider (#4258). diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index d4f2bcd892c..c423a999477 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -148,7 +148,7 @@ export function mergeBindings(policy: iam.Policy, allRequiredBindings: iam.Bindi * @param have backend that we have currently deployed */ export async function ensureServiceAgentRoles( - projectId: string, + projectNumber: string, want: backend.Backend, have: backend.Backend ): Promise { @@ -164,7 +164,7 @@ export async function ensureServiceAgentRoles( // get the full project iam policy let policy: iam.Policy; try { - policy = await getIamPolicy(projectId); + policy = await getIamPolicy(projectNumber); } catch (err: any) { utils.logLabeledBullet( "functions", @@ -178,13 +178,13 @@ export async function ensureServiceAgentRoles( // run in parallel all the missingProjectBindings jobs const findRequiredBindings: Array>> = []; newServices.forEach((service) => - findRequiredBindings.push(service.requiredProjectBindings!(projectId, policy)) + findRequiredBindings.push(service.requiredProjectBindings!(projectNumber, policy)) ); const allRequiredBindings = await Promise.all(findRequiredBindings); mergeBindings(policy, allRequiredBindings); // set the updated policy try { - await setIamPolicy(projectId, policy, "bindings"); + await setIamPolicy(projectNumber, policy, "bindings"); } catch (err: any) { throw new FirebaseError( "We failed to modify the IAM policy for the project. The functions " + diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 4bcf36ef784..4e7de48c1c2 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -14,7 +14,7 @@ import { logBullet } from "../../utils"; import { getFunctionsConfig, prepareFunctionsUpload } from "./prepareFunctionsUpload"; import { promptForFailurePolicies, promptForMinInstances } from "./prompts"; import { previews } from "../../previews"; -import { needProjectId } from "../../projectUtils"; +import { needProjectId, needProjectNumber } from "../../projectUtils"; import { track } from "../../track"; import { logger } from "../../logger"; import { ensureTriggerRegions } from "./triggerRegionHelper"; @@ -37,6 +37,7 @@ export async function prepare( payload: args.Payload ): Promise { const projectId = needProjectId(options); + const projectNumber = await needProjectNumber(options); const sourceDirName = options.config.get("functions.source") as string; if (!sourceDirName) { @@ -160,7 +161,7 @@ export async function prepare( }); const haveBackend = await backend.existingBackend(context); - await ensureServiceAgentRoles(projectId, wantBackend, haveBackend); + await ensureServiceAgentRoles(projectNumber, wantBackend, haveBackend); inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv); await ensureTriggerRegions(wantBackend); diff --git a/src/deploy/functions/services/firebaseAlerts.ts b/src/deploy/functions/services/firebaseAlerts.ts new file mode 100644 index 00000000000..ed6c2f53370 --- /dev/null +++ b/src/deploy/functions/services/firebaseAlerts.ts @@ -0,0 +1,49 @@ +import * as backend from "../backend"; +import * as iam from "../../../gcp/iam"; +import { getProjectNumber } from "../../../getProjectNumber"; +import { FirebaseError } from "../../../error"; + +export const SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator"; + +/** + * Finds the required project level IAM bindings for the Pub/Sub service agent + * If the user enabled Pub/Sub on or before April 8, 2021, then we must enable the token creator role + * @param projectId project identifier + * @param existingPolicy the project level IAM policy + */ +export function obtainFirebaseAlertsBindings( + projectNumber: string, + existingPolicy: iam.Policy +): Promise> { + const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`; + let pubsubBinding = existingPolicy.bindings.find( + (b) => b.role === SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE + ); + if (!pubsubBinding) { + pubsubBinding = { + role: SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [], + }; + } + if (!pubsubBinding.members.find((m) => m === pubsubServiceAgent)) { + pubsubBinding.members.push(pubsubServiceAgent); + } + return Promise.resolve([pubsubBinding]); +} + +/** + * Sets a Firebase Alerts event trigger's region to 'global' since the service is global + * @param endpoint the storage endpoint + * @param eventTrigger the endpoints event trigger + */ +export function ensureFirebaseAlertsTriggerRegion( + endpoint: backend.Endpoint & backend.EventTriggered +): Promise { + if (!endpoint.eventTrigger.region) { + endpoint.eventTrigger.region = "global"; + } + if (endpoint.eventTrigger.region !== "global") { + throw new FirebaseError("A firebase alerts trigger must specify 'global' trigger location"); + } + return Promise.resolve(); +} diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 1e6abb6eb9f..70bf9773090 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -2,6 +2,7 @@ import * as backend from "../backend"; import * as iam from "../../../gcp/iam"; import * as v2events from "../../../functions/events/v2"; import { obtainStorageBindings, ensureStorageTriggerRegion } from "./storage"; +import { obtainFirebaseAlertsBindings, ensureFirebaseAlertsTriggerRegion } from "./firebaseAlerts"; const noop = (): Promise => Promise.resolve(); @@ -11,7 +12,9 @@ export interface Service { readonly api: string; // dispatch functions - requiredProjectBindings: ((pId: any, p: any) => Promise>) | undefined; + requiredProjectBindings: + | ((projectNumber: string, policy: iam.Policy) => Promise>) + | undefined; ensureTriggerRegion: (ep: backend.Endpoint & backend.EventTriggered) => Promise; } @@ -30,12 +33,19 @@ export const PubSubService: Service = { ensureTriggerRegion: noop, }; /** A storage service object */ -export const StorageService = { +export const StorageService: Service = { name: "storage", api: "storage.googleapis.com", requiredProjectBindings: obtainStorageBindings, ensureTriggerRegion: ensureStorageTriggerRegion, }; +/** A firebase alerts service object */ +export const FirebaseAlertsService: Service = { + name: "firebasealerts", + api: "logging.googleapis.com", + requiredProjectBindings: obtainFirebaseAlertsBindings, + ensureTriggerRegion: ensureFirebaseAlertsTriggerRegion, +}; /** Mapping from event type string to service object */ export const EVENT_SERVICE_MAPPING: Record = { @@ -44,6 +54,7 @@ export const EVENT_SERVICE_MAPPING: Record = { "google.cloud.storage.object.v1.archived": StorageService, "google.cloud.storage.object.v1.deleted": StorageService, "google.cloud.storage.object.v1.metadataUpdated": StorageService, + "firebase.firebasealerts.alerts.v1.published": FirebaseAlertsService, }; /** diff --git a/src/deploy/functions/services/storage.ts b/src/deploy/functions/services/storage.ts index 200a3a995d6..a6103d1c97c 100644 --- a/src/deploy/functions/services/storage.ts +++ b/src/deploy/functions/services/storage.ts @@ -13,10 +13,10 @@ const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher"; * @param existingPolicy the project level IAM policy */ export async function obtainStorageBindings( - projectId: string, + projectNumber: string, existingPolicy: iam.Policy ): Promise> { - const storageResponse = await storage.getServiceAccount(projectId); + const storageResponse = await storage.getServiceAccount(projectNumber); const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`; let pubsubBinding = existingPolicy.bindings.find((b) => b.role === PUBSUB_PUBLISHER_ROLE); if (!pubsubBinding) { diff --git a/src/functions/events/v2.ts b/src/functions/events/v2.ts index abfab630e99..851f5030917 100644 --- a/src/functions/events/v2.ts +++ b/src/functions/events/v2.ts @@ -7,4 +7,9 @@ export const STORAGE_EVENTS = [ "google.cloud.storage.object.v1.metadataUpdated", ] as const; -export type Event = typeof PUBSUB_PUBLISH_EVENT | typeof STORAGE_EVENTS[number]; +export const FIREBASE_ALERTS_PUBLISH_EVENT = "firebase.firebasealerts.alerts.v1.published"; + +export type Event = + | typeof PUBSUB_PUBLISH_EVENT + | typeof STORAGE_EVENTS[number] + | typeof FIREBASE_ALERTS_PUBLISH_EVENT; diff --git a/src/gcp/resourceManager.ts b/src/gcp/resourceManager.ts index 89274d23ffe..9b46609d403 100644 --- a/src/gcp/resourceManager.ts +++ b/src/gcp/resourceManager.ts @@ -19,10 +19,12 @@ export const firebaseRoles = { * Fetches the IAM Policy of a project. * https://cloud.google.com/resource-manager/reference/rest/v1/projects/getIamPolicy * - * @param projectId the id of the project whose IAM Policy you want to get + * @param projectIdOrNumber the id of the project whose IAM Policy you want to get */ -export async function getIamPolicy(projectId: string): Promise { - const response = await apiClient.post(`/projects/${projectId}:getIamPolicy`); +export async function getIamPolicy(projectIdOrNumber: string): Promise { + const response = await apiClient.post( + `/projects/${projectIdOrNumber}:getIamPolicy` + ); return response.body; } @@ -30,17 +32,17 @@ export async function getIamPolicy(projectId: string): Promise { * Sets the IAM Policy of a project. * https://cloud.google.com/resource-manager/reference/rest/v1/projects/setIamPolicy * - * @param projectId the id of the project for which you want to set a new IAM Policy + * @param projectIdOrNumber the id of the project for which you want to set a new IAM Policy * @param newPolicy the new IAM policy for the project * @param updateMask A FieldMask specifying which fields of the policy to modify */ export async function setIamPolicy( - projectId: string, + projectIdOrNumber: string, newPolicy: Policy, updateMask = "" ): Promise { const response = await apiClient.post<{ policy: Policy; updateMask: string }, Policy>( - `/projects/${projectId}:setIamPolicy`, + `/projects/${projectIdOrNumber}:setIamPolicy`, { policy: newPolicy, updateMask: updateMask, diff --git a/src/test/deploy/functions/checkIam.spec.ts b/src/test/deploy/functions/checkIam.spec.ts index a0cd0ce32e4..c7f1de3baea 100644 --- a/src/test/deploy/functions/checkIam.spec.ts +++ b/src/test/deploy/functions/checkIam.spec.ts @@ -5,6 +5,8 @@ import * as storage from "../../../gcp/storage"; import * as rm from "../../../gcp/resourceManager"; import * as backend from "../../../deploy/functions/backend"; +const projectNumber = "123456789"; + const STORAGE_RES = { email_address: "service-123@gs-project-accounts.iam.gserviceaccount.com", kind: "storage#serviceAccount", @@ -17,7 +19,7 @@ const BINDING = { const SPEC = { region: "us-west1", - project: "my-project", + project: projectNumber, runtime: "nodejs14", }; @@ -105,10 +107,11 @@ describe("checkIam", () => { ...SPEC, }; - await expect(checkIam.ensureServiceAgentRoles("project", backend.of(wantFn), backend.empty())) - .to.not.be.rejected; + await expect( + checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.empty()) + ).to.not.be.rejected; expect(getIamStub).to.have.been.calledOnce; - expect(getIamStub).to.have.been.calledWith("project"); + expect(getIamStub).to.have.been.calledWith(projectNumber); expect(storageStub).to.not.have.been.called; expect(setIamStub).to.not.have.been.called; }); @@ -155,7 +158,7 @@ describe("checkIam", () => { }; await checkIam.ensureServiceAgentRoles( - "project", + projectNumber, backend.of(wantFn), backend.of(v1EventFn, v2CallableFn, wantFn) ); @@ -199,7 +202,7 @@ describe("checkIam", () => { ...SPEC, }; - await checkIam.ensureServiceAgentRoles("project", backend.of(wantFn), backend.of(haveFn)); + await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.of(haveFn)); expect(storageStub).to.not.have.been.called; expect(getIamStub).to.not.have.been.called; @@ -242,12 +245,12 @@ describe("checkIam", () => { ...SPEC, }; - await checkIam.ensureServiceAgentRoles("project", backend.of(wantFn), backend.empty()); + await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.empty()); expect(storageStub).to.have.been.calledOnce; expect(getIamStub).to.have.been.calledOnce; expect(setIamStub).to.have.been.calledOnce; - expect(setIamStub).to.have.been.calledWith("project", newIamPolicy, "bindings"); + expect(setIamStub).to.have.been.calledWith(projectNumber, newIamPolicy, "bindings"); }); }); }); diff --git a/src/test/deploy/functions/services/firebaseAlerts.spec.ts b/src/test/deploy/functions/services/firebaseAlerts.spec.ts new file mode 100644 index 00000000000..105c889d304 --- /dev/null +++ b/src/test/deploy/functions/services/firebaseAlerts.spec.ts @@ -0,0 +1,111 @@ +import { expect } from "chai"; +import { Endpoint } from "../../../../deploy/functions/backend"; +import * as firebaseAlerts from "../../../../deploy/functions/services/firebaseAlerts"; + +const projectNumber = "123456789"; + +const endpoint: Endpoint = { + id: "endpoint", + region: "us-central1", + project: projectNumber, + eventTrigger: { + retry: false, + eventType: "firebase.firebasealerts.alerts.v1.published", + eventFilters: [], + }, + entryPoint: "endpoint", + platform: "gcfv2", + runtime: "nodejs16", +}; + +describe("obtainFirebaseAlertsBindings", () => { + const iamPolicy = { + etag: "etag", + version: 3, + bindings: [ + { + role: "some/role", + members: ["someuser"], + }, + ], + }; + + it("should add the binding", async () => { + const policy = { ...iamPolicy }; + + const bindings = await firebaseAlerts.obtainFirebaseAlertsBindings(projectNumber, policy); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: ["serviceAccount:service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com"], + }); + }); + + it("should add the service agent as a member", async () => { + const policy = { ...iamPolicy }; + policy.bindings = [ + { + role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: ["someuser"], + }, + ]; + + const bindings = await firebaseAlerts.obtainFirebaseAlertsBindings(projectNumber, policy); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [ + "someuser", + "serviceAccount:service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com", + ], + }); + }); + + it("should do nothing if we have the binding", async () => { + const policy = { ...iamPolicy }; + policy.bindings = [ + { + role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: ["serviceAccount:service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com"], + }, + ]; + + const bindings = await firebaseAlerts.obtainFirebaseAlertsBindings(projectNumber, policy); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: ["serviceAccount:service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com"], + }); + }); +}); + +describe("ensureFirebaseAlertsTriggerRegion", () => { + it("should set the trigger location to global", async () => { + const ep = { ...endpoint }; + + await firebaseAlerts.ensureFirebaseAlertsTriggerRegion(ep); + + expect(endpoint.eventTrigger.region).to.eq("global"); + }); + + it("should not error if the trigger location is global", async () => { + const ep = { ...endpoint }; + ep.eventTrigger.region = "global"; + + await firebaseAlerts.ensureFirebaseAlertsTriggerRegion(endpoint); + + expect(endpoint.eventTrigger.region).to.eq("global"); + }); + + it("should error if the trigger location is not global", () => { + const ep = { ...endpoint }; + ep.eventTrigger.region = "us-west1"; + + expect(() => firebaseAlerts.ensureFirebaseAlertsTriggerRegion(endpoint)).to.throw( + "A firebase alerts trigger must specify 'global' trigger location" + ); + }); +}); diff --git a/src/test/deploy/functions/services/storage.spec.ts b/src/test/deploy/functions/services/storage.spec.ts index f088ae66c2a..38a21453e89 100644 --- a/src/test/deploy/functions/services/storage.spec.ts +++ b/src/test/deploy/functions/services/storage.spec.ts @@ -3,6 +3,8 @@ import * as sinon from "sinon"; import { obtainStorageBindings } from "../../../../deploy/functions/services/storage"; import * as storage from "../../../../gcp/storage"; +const projectNumber = "123456789"; + const STORAGE_RES = { email_address: "service-123@gs-project-accounts.iam.gserviceaccount.com", kind: "storage#serviceAccount", @@ -34,7 +36,7 @@ describe("obtainStorageBindings", () => { bindings: [BINDING], }; - const bindings = await obtainStorageBindings("project", existingPolicy); + const bindings = await obtainStorageBindings(projectNumber, existingPolicy); expect(bindings.length).to.equal(1); expect(bindings[0]).to.deep.equal({ @@ -51,7 +53,7 @@ describe("obtainStorageBindings", () => { bindings: [BINDING, { role: "roles/pubsub.publisher", members: ["someuser"] }], }; - const bindings = await obtainStorageBindings("project", existingPolicy); + const bindings = await obtainStorageBindings(projectNumber, existingPolicy); expect(bindings.length).to.equal(1); expect(bindings[0]).to.deep.equal({ @@ -74,7 +76,7 @@ describe("obtainStorageBindings", () => { ], }; - const bindings = await obtainStorageBindings("project", existingPolicy); + const bindings = await obtainStorageBindings(projectNumber, existingPolicy); expect(bindings.length).to.equal(1); expect(bindings[0]).to.deep.equal({ role: "roles/pubsub.publisher", From 9796812062fa5501e1185e9ca9357167f3f7acf1 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Wed, 9 Mar 2022 11:02:32 -0500 Subject: [PATCH 0137/1699] remove deleteAll (#4276) --- src/emulator/storage/files.ts | 4 ---- src/emulator/storage/index.ts | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index f0cd97fbcca..b2d907426c2 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -278,10 +278,6 @@ export class StorageLayer { } } - public async deleteAll(): Promise { - return this._persistence.deleteAll(); - } - /** * Last step in uploading a file. Validates the request and persists the staging * object to its permanent location on disk. diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index e6816bda0a4..ae7144250ff 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -91,7 +91,7 @@ export class StorageEmulator implements EmulatorInstance { } async stop(): Promise { - await this.storageLayer.deleteAll(); + await this._persistence.deleteAll(); await this._rulesManager.close(); return this.destroyServer ? this.destroyServer() : Promise.resolve(); } From 0aeb7d2dfb750c7c13e0e729547c5f80316287f7 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 9 Mar 2022 10:50:35 -0800 Subject: [PATCH 0138/1699] Update v2 function deployment (#4265) Partially revert back https://github.com/firebase/firebase-tools/pull/4205 and use container contract to parse triggers when (1) preview flag is enabled and (2) minimum functions SDK version is in used. We also sneak in a change to correctly parse `deployment-callable` label to identify callable functions. --- src/deploy/functions/runtimes/node/index.ts | 36 +++++++++++++++++++++ src/gcp/cloudfunctions.ts | 4 +++ src/gcp/cloudfunctionsv2.ts | 4 +++ 3 files changed, 44 insertions(+) diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 4bd1eeb125b..3421fd5abcd 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -1,18 +1,25 @@ import { promisify } from "util"; import * as fs from "fs"; import * as path from "path"; +import * as portfinder from "portfinder"; +import * as semver from "semver"; import * as spawn from "cross-spawn"; import fetch from "node-fetch"; import { FirebaseError } from "../../../../error"; import { getRuntimeChoice } from "./parseRuntimeAndValidateSDK"; import { logger } from "../../../../logger"; +import { previews } from "../../../../previews"; +import { logLabeledWarning } from "../../../../utils"; import * as backend from "../../backend"; +import * as discovery from "../discovery"; import * as runtimes from ".."; import * as validate from "./validate"; import * as versioning from "./versioning"; import * as parseTriggers from "./parseTriggers"; +const MIN_FUNCTIONS_SDK_VERSION = "3.19.0"; + export async function tryCreateDelegate( context: runtimes.DelegateContext ): Promise { @@ -117,6 +124,35 @@ export class Delegate { config: backend.RuntimeConfigValues, env: backend.EnvironmentVariables ): Promise { + if (previews.functionsv2) { + if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) { + logLabeledWarning( + "functions", + `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` + + `Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}` + ); + return parseTriggers.discoverBackend( + this.projectId, + this.sourceDir, + this.runtime, + config, + env + ); + } + let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime); + if (!discovered) { + const getPort = promisify(portfinder.getPort) as () => Promise; + const port = await getPort(); + const kill = await this.serve(port, env); + try { + discovered = await discovery.detectFromPort(port, this.projectId, this.runtime); + } finally { + await kill(); + } + } + discovered.environmentVariables = env; + return discovered; + } return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env); } } diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 8f5260222d3..052b4e8c3d2 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -474,6 +474,10 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { taskQueueTrigger: {}, }; + } else if (gcfFunction.labels?.["deployment-callable"]) { + trigger = { + callableTrigger: {}, + }; } else if (gcfFunction.httpsTrigger) { trigger = { httpsTrigger: {} }; uri = gcfFunction.httpsTrigger.url; diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 83c2db6296d..a34b246ae27 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -496,6 +496,10 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { taskQueueTrigger: {}, }; + } else if (gcfFunction.labels?.["deployment-callable"] === "true") { + trigger = { + callableTrigger: {}, + }; } else if (gcfFunction.eventTrigger) { trigger = { eventTrigger: { From f8b133b635b39e2bbc0621a808131cec2d998623 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Wed, 9 Mar 2022 16:38:12 -0500 Subject: [PATCH 0139/1699] Move security rules evaluation to StorageLayer (#4262) * multipart and firebase.ts integration * update gcloud.ts * refactor persistence out into StorageEmulator * fix lint and method references * fix tests * upload.ts * update callers of UploadService and tests * more tests * more tests * md -> metadata * address pr comments * lint * fix tests * fix tests * stash * more integration tests, fix upload bug * more fixing * fix remaining * lint * metadata * deleteobject * list * get * fix tests * lint * address pr comments * lint * cleanup * fix imports * fix conditional * address pr comments * change to const --- scripts/storage-emulator-integration/tests.ts | 46 ++-- src/emulator/storage/apis/firebase.ts | 167 ++++++------- src/emulator/storage/apis/gcloud.ts | 193 ++++++++------- src/emulator/storage/files.ts | 226 +++++++++--------- 4 files changed, 322 insertions(+), 310 deletions(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index 210af482138..a957533d177 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -317,7 +317,7 @@ describe("Storage emulator", () => { }); describe("#delete()", () => { - it("should properly delete a file from the bucket", async () => { + it("should delete a file from the bucket", async () => { // We use a nested path to ensure that we don't need to decode // the objectId in the gcloud emulator API const bucketFilePath = "file/to/delete"; @@ -1043,14 +1043,9 @@ describe("Storage emulator", () => { describe("#getDownloadURL()", () => { it("returns url pointing to the expected host", async () => { - let downloadUrl; - try { - downloadUrl = await page.evaluate((filename) => { - return firebase.storage().ref(filename).getDownloadURL(); - }, filename); - } catch (err: any) { - expect(err).to.equal(""); - } + const downloadUrl: string = await page.evaluate((filename) => { + return firebase.storage().ref(filename).getDownloadURL(); + }, filename); const expectedHost = TEST_CONFIG.useProductionServers ? "https://firebasestorage.googleapis.com" : STORAGE_EMULATOR_HOST; @@ -1067,27 +1062,18 @@ describe("Storage emulator", () => { const requestClient = TEST_CONFIG.useProductionServers ? https : http; await new Promise((resolve, reject) => { - requestClient.get( - downloadUrl, - { - headers: { - // This is considered an authorized request in the emulator - Authorization: "Bearer owner", - }, - }, - (response) => { - const data: any = []; - response - .on("data", (chunk) => data.push(chunk)) - .on("end", () => { - expect(Buffer.concat(data)).to.deep.equal( - Buffer.from(IMAGE_FILE_BASE64, "base64") - ); - }) - .on("close", resolve) - .on("error", reject); - } - ); + requestClient.get(downloadUrl, (response) => { + const data: any = []; + response + .on("data", (chunk) => data.push(chunk)) + .on("end", () => { + expect(Buffer.concat(data)).to.deep.equal( + Buffer.from(IMAGE_FILE_BASE64, "base64") + ); + }) + .on("close", resolve) + .on("error", reject); + }); }); }); }); diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 210102ba02d..aceb1254c7a 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -1,7 +1,12 @@ import { EmulatorLogger } from "../../emulatorLogger"; import { Emulators } from "../../types"; import { gunzipSync } from "zlib"; -import { OutgoingFirebaseMetadata, RulesResourceMetadata, StoredFileMetadata } from "../metadata"; +import { + IncomingMetadata, + OutgoingFirebaseMetadata, + RulesResourceMetadata, + StoredFileMetadata, +} from "../metadata"; import * as mime from "mime"; import { Request, Response, Router } from "express"; import { StorageEmulator } from "../index"; @@ -11,6 +16,7 @@ import { parseObjectUploadMultipartRequest } from "../multipart"; import { NotFoundError, ForbiddenError } from "../errors"; import { isPermitted } from "../rules/utils"; import { NotCancellableError, Upload, UploadNotActiveError } from "../upload"; +import { ListResponse } from "../list"; /** * @param emulator @@ -104,7 +110,12 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { if (err instanceof NotFoundError) { return res.sendStatus(404); } else if (err instanceof ForbiddenError) { - return res.sendStatus(403); + return res.status(403).json({ + error: { + code: 403, + message: `Permission denied. No READ permission.`, + }, + }); } throw err; } @@ -141,76 +152,57 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }); const handleMetadataUpdate = async (req: Request, res: Response) => { - const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); - - if (!md) { - res.sendStatus(404); - return; - } - - const decodedObjectId = decodeURIComponent(req.params.objectId); - const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/"); - - if ( - !(await isPermitted({ - ruleset: emulator.rules, - method: RulesetOperationMethod.UPDATE, - path: operationPath, + let metadata: StoredFileMetadata; + try { + metadata = await storageLayer.handleUpdateObjectMetadata({ + bucketId: req.params.bucketId, + decodedObjectId: decodeURIComponent(req.params.objectId), + metadata: req.body as IncomingMetadata, authorization: req.header("authorization"), - file: { - before: md.asRulesResource(), - after: md.asRulesResource(req.body), // TODO - }, - })) - ) { - return res.status(403).json({ - error: { - code: 403, - message: `Permission denied. No WRITE permission.`, - }, }); + } catch (err) { + if (err instanceof ForbiddenError) { + return res.status(403).json({ + error: { + code: 403, + message: `Permission denied. No WRITE permission.`, + }, + }); + } + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + throw err; } - - md.update(req.body); - - setObjectHeaders(res, md); - const outgoingMetadata = new OutgoingFirebaseMetadata(md); - res.json(outgoingMetadata); - return; + setObjectHeaders(res, metadata); + return res.json(new OutgoingFirebaseMetadata(metadata)); }; // list object handler firebaseStorageAPI.get("/b/:bucketId/o", async (req, res) => { - let maxRes = undefined; - if (req.query.maxResults) { - maxRes = +req.query.maxResults.toString(); - } - const delimiter = req.query.delimiter ? req.query.delimiter.toString() : "/"; - const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined; - const prefix = req.query.prefix ? req.query.prefix.toString() : ""; - - const operationPath = ["b", req.params.bucketId, "o", prefix].join("/"); - - if ( - !(await isPermitted({ - ruleset: emulator.rules, - method: RulesetOperationMethod.LIST, - path: operationPath, - file: {}, + const maxResults = req.query.maxResults?.toString(); + let response: ListResponse; + try { + response = await storageLayer.handleListObjects({ + bucketId: req.params.bucketId, + prefix: req.query.prefix ? req.query.prefix.toString() : "", + delimiter: req.query.delimiter ? req.query.delimiter.toString() : "/", + pageToken: req.query.pageToken?.toString(), + maxResults: maxResults ? +maxResults : undefined, authorization: req.header("authorization"), - })) - ) { - return res.status(403).json({ - error: { - code: 403, - message: `Permission denied. No LIST permission.`, - }, }); + } catch (err) { + if (err instanceof ForbiddenError) { + return res.status(403).json({ + error: { + code: 403, + message: `Permission denied. No LIST permission.`, + }, + }); + } + throw err; } - - res.json( - storageLayer.listItemsAndPrefixes(req.params.bucketId, prefix, delimiter, pageToken, maxRes) - ); + return res.json(response); }); const reqBodyToBuffer = async (req: Request): Promise => { @@ -236,6 +228,8 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { const operationPath = ["b", bucketId, "o", decodedObjectId].join("/"); const metadataBefore = storageLayer.getMetadata(bucketId, req.params.objectId); + // TODO(tonyjhuang): Replace this Firebase Rules check with an admin-only auth check + // as token management endpoints should only accept admin credentials. if ( !(await isPermitted({ ruleset: emulator.rules, @@ -478,40 +472,27 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleUpload); firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { - const decodedObjectId = decodeURIComponent(req.params.objectId); - const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/"); - const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId); - - const rulesFiles: { before?: RulesResourceMetadata } = {}; - - if (md) { - rulesFiles.before = md.asRulesResource(); - } - - if ( - !(await isPermitted({ - ruleset: emulator.rules, - method: RulesetOperationMethod.DELETE, - path: operationPath, + try { + await storageLayer.handleDeleteObject({ + bucketId: req.params.bucketId, + decodedObjectId: decodeURIComponent(req.params.objectId), authorization: req.header("authorization"), - file: rulesFiles, - })) - ) { - return res.status(403).json({ - error: { - code: 403, - message: `Permission denied. No WRITE permission.`, - }, }); + } catch (err) { + if (err instanceof ForbiddenError) { + return res.status(403).json({ + error: { + code: 403, + message: `Permission denied. No WRITE permission.`, + }, + }); + } + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + throw err; } - - if (!md) { - res.sendStatus(404); - return; - } - - storageLayer.deleteFile(req.params.bucketId, req.params.objectId); - res.sendStatus(200); + res.sendStatus(204); }); firebaseStorageAPI.get("/", (req, res) => { diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index a429859fdf0..e4b93612d3b 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -4,12 +4,13 @@ import { Emulators } from "../../types"; import { CloudStorageObjectAccessControlMetadata, CloudStorageObjectMetadata, + IncomingMetadata, StoredFileMetadata, } from "../metadata"; import { EmulatorRegistry } from "../../registry"; import { StorageEmulator } from "../index"; import { EmulatorLogger } from "../../emulatorLogger"; -import { StorageLayer } from "../files"; +import { GetObjectResponse, StorageLayer } from "../files"; import type { Request, Response } from "express"; import { parseObjectUploadMultipartRequest } from "../multipart"; import { Upload, UploadNotActiveError } from "../upload"; @@ -39,38 +40,54 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { gcloudStorageAPI.get( ["/b/:bucketId/o/:objectId", "/download/storage/v1/b/:bucketId/o/:objectId"], - (req, res) => { - const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); - - if (!md) { - res.sendStatus(404); - return; + async (req, res) => { + let getObjectResponse: GetObjectResponse; + try { + getObjectResponse = await storageLayer.handleGetObject( + { + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }, + /* skipAuth = */ true + ); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + if (err instanceof ForbiddenError) { + throw new Error("Request failed unexpectedly due to Firebase Rules."); + } + throw err; } if (req.query.alt === "media") { - return sendFileBytes(md, storageLayer, req, res); + return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res); } - - const outgoingMd = new CloudStorageObjectMetadata(md); - - res.json(outgoingMd).status(200).send(); - return; + return res.json(new CloudStorageObjectMetadata(getObjectResponse.metadata)); } ); - gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", (req, res) => { - const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); - - if (!md) { - res.sendStatus(404); - return; + gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", async (req, res) => { + let updatedMetadata: StoredFileMetadata; + try { + updatedMetadata = await storageLayer.handleUpdateObjectMetadata( + { + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + metadata: req.body as IncomingMetadata, + }, + /* skipAuth = */ true + ); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + if (err instanceof ForbiddenError) { + throw new Error("Request failed unexpectedly due to Firebase Rules."); + } + throw err; } - - md.update(req.body); - - const outgoingMetadata = new CloudStorageObjectMetadata(md); - res.json(outgoingMetadata).status(200).send(); - return; + return res.json(new CloudStorageObjectMetadata(updatedMetadata)); }); gcloudStorageAPI.get("/b/:bucketId/o", (req, res) => { @@ -90,20 +107,28 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { pageToken, maxRes ); - res.json(listResult); }); - gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", (req, res) => { - const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); - - if (!md) { - res.sendStatus(404); - return; + gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { + try { + await storageLayer.handleDeleteObject( + { + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }, + /* skipAuth = */ true + ); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + if (err instanceof ForbiddenError) { + throw new Error("Request failed unexpectedly due to Firebase Rules."); + } + throw err; } - - storageLayer.deleteFile(req.params.bucketId, req.params.objectId); - res.status(200).send(); + return res.sendStatus(204); }); const reqBodyToBuffer = async (req: Request): Promise => { @@ -154,37 +179,46 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return res.json(new CloudStorageObjectMetadata(metadata)); }); - gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", (req, res) => { + gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", async (req, res) => { // TODO(abehaskins) Link to a doc with more info EmulatorLogger.forEmulator(Emulators.STORAGE).log( "WARN_ONCE", "Cloud Storage ACLs are not supported in the Storage Emulator. All related methods will succeed, but have no effect." ); - const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); - - if (!md) { - res.sendStatus(404); - return; + let getObjectResponse: GetObjectResponse; + try { + getObjectResponse = await storageLayer.handleGetObject( + { + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }, + /* skipAuth = */ true + ); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + if (err instanceof ForbiddenError) { + throw new Error("Request failed unexpectedly due to Firebase Rules."); + } + throw err; } - + const { metadata } = getObjectResponse; // We do an empty update to step metageneration forward; - md.update({}); - - res - .json({ - kind: "storage#objectAccessControl", - object: md.name, - id: `${req.params.bucketId}/${md.name}/${md.generation}/allUsers`, - selfLink: `http://${EmulatorRegistry.getInfo(Emulators.STORAGE)?.host}:${ - EmulatorRegistry.getInfo(Emulators.STORAGE)?.port - }/storage/v1/b/${md.bucket}/o/${encodeURIComponent(md.name)}/acl/allUsers`, - bucket: md.bucket, - entity: req.body.entity, - role: req.body.role, - etag: "someEtag", - generation: md.generation.toString(), - } as CloudStorageObjectAccessControlMetadata) - .status(200); + metadata.update({}); + return res.json({ + kind: "storage#objectAccessControl", + object: metadata.name, + id: `${req.params.bucketId}/${metadata.name}/${metadata.generation}/allUsers`, + selfLink: `http://${EmulatorRegistry.getInfo(Emulators.STORAGE)?.host}:${ + EmulatorRegistry.getInfo(Emulators.STORAGE)?.port + }/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}/acl/allUsers`, + bucket: metadata.bucket, + entity: req.body.entity, + role: req.body.role, + etag: "someEtag", + generation: metadata.generation.toString(), + } as CloudStorageObjectAccessControlMetadata); }); gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => { @@ -257,15 +291,26 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return res.status(200).json(new CloudStorageObjectMetadata(metadata)); }); - gcloudStorageAPI.get("/:bucketId/:objectId(**)", (req, res) => { - const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); - - if (!md) { - res.sendStatus(404); - return; + gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => { + let getObjectResponse: GetObjectResponse; + try { + getObjectResponse = await storageLayer.handleGetObject( + { + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }, + /* skipAuth = */ true + ); + } catch (err) { + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + if (err instanceof ForbiddenError) { + throw new Error("Request failed unexpectedly due to Firebase Rules."); + } + throw err; } - - return sendFileBytes(md, storageLayer, req, res); + return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res); }); gcloudStorageAPI.all("/**", (req, res) => { @@ -281,18 +326,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return gcloudStorageAPI; } -function sendFileBytes( - md: StoredFileMetadata, - storageLayer: StorageLayer, - req: Request, - res: Response -) { - let data = storageLayer.getBytes(req.params.bucketId, req.params.objectId); - if (!data) { - res.sendStatus(404); - return; - } - +function sendFileBytes(md: StoredFileMetadata, data: Buffer, req: Request, res: Response) { const isGZipped = md.contentEncoding === "gzip"; if (isGZipped) { data = gunzipSync(data); @@ -317,5 +351,4 @@ function sendFileBytes( } else { res.end(data); } - return; } diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index b2d907426c2..072a6bbf45f 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -18,7 +18,7 @@ import { import { RulesetOperationMethod } from "./rules/types"; import { RulesValidator } from "./rules/utils"; import { Persistence } from "./persistence"; -import { Upload } from "./upload"; +import { Upload, UploadStatus } from "./upload"; interface BucketsList { buckets: { @@ -48,79 +48,6 @@ export class StoredFile { } } -export class ResumableUpload { - private _uploadId: string; - private _metadata: IncomingMetadata; - private _bucketId: string; - private _objectId: string; - private _contentType: string; - private _authorization: string | undefined; - private _currentBytesUploaded = 0; - private _status: UploadStatus = UploadStatus.ACTIVE; - private _fileLocation: string; - - constructor( - bucketId: string, - objectId: string, - uploadId: string, - contentType: string, - metadata: IncomingMetadata, - authorization?: string - ) { - this._bucketId = bucketId; - this._objectId = objectId; - this._uploadId = uploadId; - this._contentType = contentType; - this._metadata = metadata; - this._authorization = authorization; - this._fileLocation = encodeURIComponent(`${uploadId}_b_${bucketId}_o_${objectId}`); - this._currentBytesUploaded = 0; - } - - public get uploadId(): string { - return this._uploadId; - } - public get metadata(): IncomingMetadata { - return this._metadata; - } - public get bucketId(): string { - return this._bucketId; - } - public get objectId(): string { - return this._objectId; - } - public get contentType(): string { - return this._contentType; - } - public set contentType(contentType: string) { - this._contentType = contentType; - } - public get authorization(): string | undefined { - return this._authorization; - } - public get currentBytesUploaded(): number { - return this._currentBytesUploaded; - } - public set currentBytesUploaded(value: number) { - this._currentBytesUploaded = value; - } - public set status(status: UploadStatus) { - this._status = status; - } - public get status(): UploadStatus { - return this._status; - } - public get fileLocation(): string { - return this._fileLocation; - } -} - -export enum UploadStatus { - ACTIVE, - CANCELLED, - FINISHED, -} - /** Parsed request object for {@link StorageLayer#handleGetObject}. */ export type GetObjectRequest = { bucketId: string; @@ -135,6 +62,30 @@ export type GetObjectResponse = { data: Buffer; }; +/** Parsed request object for {@link StorageLayer#handleUpdateObjectMetadata}. */ +export type UpdateObjectMetadataRequest = { + bucketId: string; + decodedObjectId: string; + metadata: IncomingMetadata; + authorization?: string; +}; + +/** Parsed request object for {@link StorageLayer#handleDeleteObject}. */ +export type DeleteObjectRequest = { + bucketId: string; + decodedObjectId: string; + authorization?: string; +}; + +/** Parsed request object for {@link StorageLayer#handleListObjects}. */ +export type ListObjectsRequest = { + bucketId: string; + prefix: string; + delimiter: string; + pageToken?: string; + maxResults?: number; + authorization?: string; +}; export class StorageLayer { private _files!: Map; private _buckets!: Map; @@ -177,11 +128,17 @@ export class StorageLayer { * @throws {NotFoundError} if object does not exist * @throws {ForbiddenError} if request is unauthorized */ - public async handleGetObject(request: GetObjectRequest): Promise { + public async handleGetObject( + request: GetObjectRequest, + skipAuth = false + ): Promise { const metadata = this.getMetadata(request.bucketId, request.decodedObjectId); // If a valid download token is present, skip Firebase Rules auth. Mainly used by the js sdk. - let authorized = (metadata?.downloadTokens || []).includes(request.downloadToken ?? ""); + const hasValidDownloadToken = (metadata?.downloadTokens || []).includes( + request.downloadToken ?? "" + ); + let authorized = skipAuth || hasValidDownloadToken; if (!authorized) { authorized = await this._validator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), @@ -212,27 +169,6 @@ export class StorageLayer { return; } - /** - * Generates metadata for an uploaded file. Generally, this should only be used for finalized - * uploads, unless needed for security rule checks. - * @param upload The upload corresponding to the file for which to generate metadata. - * @returns Metadata for uploaded file. - */ - public createMetadata(upload: ResumableUpload): StoredFileMetadata { - const bytes = this._persistence.readBytes(upload.fileLocation, upload.currentBytesUploaded); - return new StoredFileMetadata( - { - name: upload.objectId, - bucket: upload.bucketId, - contentType: "", - contentEncoding: upload.metadata.contentEncoding, - customMetadata: upload.metadata.metadata, - }, - this._cloudFunctions, - bytes - ); - } - public getBytes( bucket: string, object: string, @@ -247,12 +183,31 @@ export class StorageLayer { } return undefined; } - - public(value: Map) { - this._files = value; + /** + * Deletes an object. + * @throws {ForbiddenError} if the request is not authorized. + * @throws {NotFoundError} if the object does not exist. + */ + public async handleDeleteObject(request: DeleteObjectRequest, skipAuth = false): Promise { + const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId); + const authorized = + skipAuth || + (await this._validator.validate( + ["b", request.bucketId, "o", request.decodedObjectId].join("/"), + RulesetOperationMethod.DELETE, + { before: storedMetadata?.asRulesResource() }, + request.authorization + )); + if (!authorized) { + throw new ForbiddenError(); + } + if (!storedMetadata) { + throw new NotFoundError(); + } + this.deleteFile(request.bucketId, request.decodedObjectId); } - public deleteFile(bucketId: string, objectId: string): boolean { + private deleteFile(bucketId: string, objectId: string): boolean { const isFolder = objectId.toLowerCase().endsWith("%2f"); if (isFolder) { @@ -278,11 +233,44 @@ export class StorageLayer { } } + /** + * Updates an existing object's metadata. + * @throws {ForbiddenError} if the request is not authorized. + * @throws {NotFoundError} if the object does not exist. + */ + public async handleUpdateObjectMetadata( + request: UpdateObjectMetadataRequest, + skipAuth = false + ): Promise { + const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId); + + const authorized = + skipAuth || + (await this._validator.validate( + ["b", request.bucketId, "o", request.decodedObjectId].join("/"), + RulesetOperationMethod.UPDATE, + { + before: storedMetadata?.asRulesResource(), + after: storedMetadata?.asRulesResource(request.metadata), + }, + request.authorization + )); + if (!authorized) { + throw new ForbiddenError(); + } + if (!storedMetadata) { + throw new NotFoundError(); + } + + storedMetadata.update(request.metadata); + return storedMetadata; + } + /** * Last step in uploading a file. Validates the request and persists the staging * object to its permanent location on disk. * TODO(tonyjhuang): Inject a Rules evaluator into StorageLayer to avoid needing skipAuth param - * @throws {ForbiddenError} if the request fails security rules auth. + * @throws {ForbiddenError} if the request is not authorized. */ public async handleUploadObject(upload: Upload, skipAuth = false): Promise { if (upload.status !== UploadStatus.FINISHED) { @@ -322,7 +310,35 @@ export class StorageLayer { return metadata; } - public listItemsAndPrefixes( + /** + * Lists all files and prefixes (folders) at a path. + * @throws {ForbiddenError} if the request is not authorized. + */ + public async handleListObjects( + request: ListObjectsRequest, + skipAuth = false + ): Promise { + const authorized = + skipAuth || + (await this._validator.validate( + ["b", request.bucketId, "o", request.prefix].join("/"), + RulesetOperationMethod.LIST, + {}, + request.authorization + )); + if (!authorized) { + throw new ForbiddenError(); + } + return this.listItemsAndPrefixes( + request.bucketId, + request.prefix, + request.delimiter, + request.pageToken, + request.maxResults + ); + } + + private listItemsAndPrefixes( bucket: string, prefix: string, delimiter: string, @@ -333,10 +349,6 @@ export class StorageLayer { delimiter = "/"; } - if (!prefix) { - prefix = ""; - } - if (!prefix.endsWith(delimiter)) { prefix += delimiter; } From 181cd3f84b547df925c8b4fbe8b5f427c1c52a9c Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Wed, 9 Mar 2022 17:09:54 -0500 Subject: [PATCH 0140/1699] Refactor Token Management (#4274) * multipart and firebase.ts integration * update gcloud.ts * refactor persistence out into StorageEmulator * fix lint and method references * fix tests * upload.ts * update callers of UploadService and tests * more tests * more tests * md -> metadata * address pr comments * lint * fix tests * fix tests * stash * more integration tests, fix upload bug * more fixing * fix remaining * lint * metadata * deleteobject * list * get * fix tests * lint * address pr comments * lint * restructure * cleanup * fix imports * fix merge * fix async * fix conditional * address pr comments * address pr comments * change to const --- scripts/storage-emulator-integration/tests.ts | 132 ++++++------ src/emulator/storage/apis/firebase.ts | 202 +++++++++--------- src/emulator/storage/files.ts | 77 ++++--- src/emulator/storage/index.ts | 3 +- src/emulator/storage/rules/utils.ts | 22 +- src/test/emulators/storage/files.spec.ts | 11 +- 6 files changed, 249 insertions(+), 198 deletions(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index a957533d177..8dc71f627cd 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -1226,74 +1226,84 @@ describe("Storage emulator", () => { ); }); - it("#addToken", async () => { - await supertest(STORAGE_EMULATOR_HOST) - .post(`/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?create_token=true`) - .set({ Authorization: "Bearer owner" }) - .expect(200) - .then((res) => { - const md = res.body; - expect(md.downloadTokens.split(",").length).to.deep.equal(2); - }); - }); + describe("tokens", () => { + it("should generate new token on create_token", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .post(`/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?create_token=true`) + .set({ Authorization: "Bearer owner" }) + .expect(200) + .then((res) => { + const md = res.body; + expect(md.downloadTokens.split(",").length).to.deep.equal(2); + }); + }); - it("#addTokenWithBadParamIsBadRequest", async () => { - await supertest(STORAGE_EMULATOR_HOST) - .post( - `/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?create_token=someNonTrueParam` - ) - .set({ Authorization: "Bearer owner" }) - .expect(400); - }); + it("should return a 400 if create_token value is invalid", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?create_token=someNonTrueParam` + ) + .set({ Authorization: "Bearer owner" }) + .expect(400); + }); - it("#deleteToken", async () => { - const tokens = await supertest(STORAGE_EMULATOR_HOST) - .post(`/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?create_token=true`) - .set({ Authorization: "Bearer owner" }) - .expect(200) - .then((res) => { - const md = res.body; - const tokens = md.downloadTokens.split(","); - expect(tokens.length).to.equal(2); + it("should return a 403 for create_token if auth header is invalid", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .post(`/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?create_token=true`) + .set({ Authorization: "Bearer somethingElse" }) + .expect(403); + }); - return tokens; - }); - // delete the newly added token - await supertest(STORAGE_EMULATOR_HOST) - .post( - `/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?delete_token=${tokens[0]}` - ) - .set({ Authorization: "Bearer owner" }) - .expect(200) - .then((res) => { - const md = res.body; - expect(md.downloadTokens.split(",")).to.deep.equal([tokens[1]]); - }); - }); + it("should delete a download token", async () => { + const tokens = await supertest(STORAGE_EMULATOR_HOST) + .post(`/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?create_token=true`) + .set({ Authorization: "Bearer owner" }) + .expect(200) + .then((res) => res.body.downloadTokens.split(",")); + // delete the newly added token + await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?delete_token=${tokens[0]}` + ) + .set({ Authorization: "Bearer owner" }) + .expect(200) + .then((res) => { + const md = res.body; + expect(md.downloadTokens.split(",")).to.deep.equal([tokens[1]]); + }); + }); - it("#deleteLastTokenStillLeavesOne", async () => { - const token = await supertest(STORAGE_EMULATOR_HOST) - .get(`/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png`) - .set({ Authorization: "Bearer owner" }) - .expect(200) - .then((res) => { - const md = res.body; - return md.downloadTokens; - }); + it("should regenerate a new token if the last remaining one is deleted", async () => { + const token = await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png`) + .set({ Authorization: "Bearer owner" }) + .expect(200) + .then((res) => res.body.downloadTokens); - // deleting the only token still generates one. - await supertest(STORAGE_EMULATOR_HOST) - .post(`/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?delete_token=${token}`) - .set({ Authorization: "Bearer owner" }) - .expect(200) - .then((res) => { - const md = res.body; - expect(md.downloadTokens.split(",").length).to.deep.equal(1); - expect(md.downloadTokens.split(",")).to.not.deep.equal([token]); - }); + await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?delete_token=${token}` + ) + .set({ Authorization: "Bearer owner" }) + .expect(200) + .then((res) => { + const md = res.body; + expect(md.downloadTokens.split(",").length).to.deep.equal(1); + expect(md.downloadTokens.split(",")).to.not.deep.equal([token]); + }); + }); + + it("should return a 403 for delete_token if auth header is invalid", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/testing%2Fstorage_ref%2Fimage.png?delete_token=someToken` + ) + .set({ Authorization: "Bearer somethingElse" }) + .expect(403); + }); }); - it("#uploadResumableDoesNotRequireMultipleAuthHeaders", async () => { + it("should accept subsequent resumable upload commands without an auth header", async () => { const uploadURL = await supertest(STORAGE_EMULATOR_HOST) .post( `/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg` diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index aceb1254c7a..9dac1dc732b 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -1,20 +1,12 @@ import { EmulatorLogger } from "../../emulatorLogger"; import { Emulators } from "../../types"; import { gunzipSync } from "zlib"; -import { - IncomingMetadata, - OutgoingFirebaseMetadata, - RulesResourceMetadata, - StoredFileMetadata, -} from "../metadata"; -import * as mime from "mime"; +import { IncomingMetadata, OutgoingFirebaseMetadata, StoredFileMetadata } from "../metadata"; import { Request, Response, Router } from "express"; import { StorageEmulator } from "../index"; import { EmulatorRegistry } from "../../registry"; -import { RulesetOperationMethod } from "../rules/types"; import { parseObjectUploadMultipartRequest } from "../multipart"; import { NotFoundError, ForbiddenError } from "../errors"; -import { isPermitted } from "../rules/utils"; import { NotCancellableError, Upload, UploadNotActiveError } from "../upload"; import { ListResponse } from "../list"; @@ -151,33 +143,6 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return res.json(new OutgoingFirebaseMetadata(metadata)); }); - const handleMetadataUpdate = async (req: Request, res: Response) => { - let metadata: StoredFileMetadata; - try { - metadata = await storageLayer.handleUpdateObjectMetadata({ - bucketId: req.params.bucketId, - decodedObjectId: decodeURIComponent(req.params.objectId), - metadata: req.body as IncomingMetadata, - authorization: req.header("authorization"), - }); - } catch (err) { - if (err instanceof ForbiddenError) { - return res.status(403).json({ - error: { - code: 403, - message: `Permission denied. No WRITE permission.`, - }, - }); - } - if (err instanceof NotFoundError) { - return res.sendStatus(404); - } - throw err; - } - setObjectHeaders(res, metadata); - return res.json(new OutgoingFirebaseMetadata(metadata)); - }; - // list object handler firebaseStorageAPI.get("/b/:bucketId/o", async (req, res) => { const maxResults = req.query.maxResults?.toString(); @@ -222,75 +187,12 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }; const handleUpload = async (req: Request, res: Response) => { - const bucketId = req.params.bucketId; - if (req.query.create_token || req.query.delete_token) { - const decodedObjectId = decodeURIComponent(req.params.objectId); - const operationPath = ["b", bucketId, "o", decodedObjectId].join("/"); - const metadataBefore = storageLayer.getMetadata(bucketId, req.params.objectId); - - // TODO(tonyjhuang): Replace this Firebase Rules check with an admin-only auth check - // as token management endpoints should only accept admin credentials. - if ( - !(await isPermitted({ - ruleset: emulator.rules, - method: RulesetOperationMethod.UPDATE, - path: operationPath, - authorization: req.header("authorization"), - file: { - before: metadataBefore?.asRulesResource(), - // TODO: before and after w/ metadata change - }, - })) - ) { - return res.status(403).json({ - error: { - code: 403, - message: `Permission denied. No WRITE permission.`, - }, - }); - } - - if (!metadataBefore) { - return res.status(404).json({ - error: { - code: 404, - message: `Request object can not be found`, - }, - }); - } - - const createTokenParam = req.query["create_token"]; - const deleteTokenParam = req.query["delete_token"]; - let metadata: StoredFileMetadata | undefined; - - if (createTokenParam) { - if (createTokenParam !== "true") { - res.sendStatus(400); - return; - } - metadata = storageLayer.addDownloadToken(req.params.bucketId, req.params.objectId); - } else if (deleteTokenParam) { - metadata = storageLayer.deleteDownloadToken( - req.params.bucketId, - req.params.objectId, - deleteTokenParam.toString() - ); - } - - if (!metadata) { - res.sendStatus(404); - return; - } - - setObjectHeaders(res, metadata); - return res.json(new OutgoingFirebaseMetadata(metadata)); - } - if (!req.query.name) { res.sendStatus(400); return; } + const bucketId = req.params.bucketId; const objectId = req.query.name.toString(); const uploadType = req.header("x-goog-upload-protocol"); @@ -459,17 +361,111 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return res.sendStatus(400); }; - // update metadata handler + const handleTokenRequest = (req: Request, res: Response) => { + if (!req.query.create_token && !req.query.delete_token) { + return res.sendStatus(400); + } + const bucketId = req.params.bucketId; + const decodedObjectId = decodeURIComponent(req.params.objectId); + const authorization = req.header("authorization"); + let metadata: StoredFileMetadata; + if (req.query.create_token) { + if (req.query.create_token !== "true") { + return res.sendStatus(400); + } + try { + metadata = storageLayer.handleCreateDownloadToken({ + bucketId, + decodedObjectId, + authorization, + }); + } catch (err) { + if (err instanceof ForbiddenError) { + return res.status(403).json({ + error: { + code: 403, + message: `Missing admin credentials.`, + }, + }); + } + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + throw err; + } + } else { + // delete download token + try { + metadata = storageLayer.handleDeleteDownloadToken({ + bucketId, + decodedObjectId, + token: req.query["delete_token"]?.toString() ?? "", + authorization, + }); + } catch (err) { + if (err instanceof ForbiddenError) { + return res.status(403).json({ + error: { + code: 403, + message: `Missing admin credentials.`, + }, + }); + } + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + throw err; + } + } + setObjectHeaders(res, metadata); + return res.json(new OutgoingFirebaseMetadata(metadata)); + }; + + const handleObjectPostRequest = async (req: Request, res: Response) => { + if (req.query.create_token || req.query.delete_token) { + return handleTokenRequest(req, res); + } + return handleUpload(req, res); + }; + + const handleMetadataUpdate = async (req: Request, res: Response) => { + let metadata: StoredFileMetadata; + try { + metadata = await storageLayer.handleUpdateObjectMetadata({ + bucketId: req.params.bucketId, + decodedObjectId: decodeURIComponent(req.params.objectId), + metadata: req.body as IncomingMetadata, + authorization: req.header("authorization"), + }); + } catch (err) { + if (err instanceof ForbiddenError) { + return res.status(403).json({ + error: { + code: 403, + message: `Permission denied. No WRITE permission.`, + }, + }); + } + if (err instanceof NotFoundError) { + return res.sendStatus(404); + } + throw err; + } + setObjectHeaders(res, metadata); + return res.json(new OutgoingFirebaseMetadata(metadata)); + }; + firebaseStorageAPI.patch("/b/:bucketId/o/:objectId", handleMetadataUpdate); firebaseStorageAPI.put("/b/:bucketId/o/:objectId?", async (req, res) => { switch (req.header("x-http-method-override")?.toLowerCase()) { case "patch": return handleMetadataUpdate(req, res); default: - return handleUpload(req, res); + return handleObjectPostRequest(req, res); } }); - firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleUpload); + + firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleObjectPostRequest); firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { try { diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 072a6bbf45f..6d4a9965e24 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -16,7 +16,7 @@ import { getProjectAdminSdkConfigOrCached, } from "../adminSdkConfig"; import { RulesetOperationMethod } from "./rules/types"; -import { RulesValidator } from "./rules/utils"; +import { AdminCredentialValidator, RulesValidator } from "./rules/utils"; import { Persistence } from "./persistence"; import { Upload, UploadStatus } from "./upload"; @@ -86,6 +86,22 @@ export type ListObjectsRequest = { maxResults?: number; authorization?: string; }; + +/** Parsed request object for {@link StorageLayer#handleCreateDownloadToken}. */ +export type CreateDownloadTokenRequest = { + bucketId: string; + decodedObjectId: string; + authorization?: string; +}; + +/** Parsed request object for {@link StorageLayer#handleDeleteDownloadToken}. */ +export type DeleteDownloadTokenRequest = { + bucketId: string; + decodedObjectId: string; + token: string; + authorization?: string; +}; + export class StorageLayer { private _files!: Map; private _buckets!: Map; @@ -93,7 +109,8 @@ export class StorageLayer { constructor( private _projectId: string, - private _validator: RulesValidator, + private _rulesValidator: RulesValidator, + private _adminCredsValidator: AdminCredentialValidator, private _persistence: Persistence ) { this.reset(); @@ -140,7 +157,7 @@ export class StorageLayer { ); let authorized = skipAuth || hasValidDownloadToken; if (!authorized) { - authorized = await this._validator.validate( + authorized = await this._rulesValidator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), RulesetOperationMethod.GET, { before: metadata?.asRulesResource() }, @@ -169,7 +186,7 @@ export class StorageLayer { return; } - public getBytes( + private getBytes( bucket: string, object: string, size?: number, @@ -192,7 +209,7 @@ export class StorageLayer { const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId); const authorized = skipAuth || - (await this._validator.validate( + (await this._rulesValidator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), RulesetOperationMethod.DELETE, { before: storedMetadata?.asRulesResource() }, @@ -246,7 +263,7 @@ export class StorageLayer { const authorized = skipAuth || - (await this._validator.validate( + (await this._rulesValidator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), RulesetOperationMethod.UPDATE, { @@ -291,7 +308,7 @@ export class StorageLayer { ); const authorized = skipAuth || - (await this._validator.validate( + (await this._rulesValidator.validate( ["b", upload.bucketId, "o", upload.objectId].join("/"), RulesetOperationMethod.CREATE, { after: metadata?.asRulesResource() }, @@ -320,7 +337,7 @@ export class StorageLayer { ): Promise { const authorized = skipAuth || - (await this._validator.validate( + (await this._rulesValidator.validate( ["b", request.bucketId, "o", request.prefix].join("/"), RulesetOperationMethod.LIST, {}, @@ -473,30 +490,34 @@ export class StorageLayer { }; } - public addDownloadToken(bucket: string, object: string): StoredFileMetadata | undefined { - const key = this.path(bucket, object); - const val = this._files.get(key); - if (!val) { - return undefined; + /** Creates a new Firebase download token for an object. */ + public handleCreateDownloadToken(request: CreateDownloadTokenRequest): StoredFileMetadata { + if (!this._adminCredsValidator.validate(request.authorization)) { + throw new ForbiddenError(); } - const md = val.metadata; - md.addDownloadToken(); - return md; + const metadata = this.getMetadata(request.bucketId, request.decodedObjectId); + if (!metadata) { + throw new NotFoundError(); + } + metadata.addDownloadToken(); + return metadata; } - public deleteDownloadToken( - bucket: string, - object: string, - token: string - ): StoredFileMetadata | undefined { - const key = this.path(bucket, object); - const val = this._files.get(key); - if (!val) { - return undefined; + /** + * Removes a Firebase download token from an object's metadata. If the token is not already + * present, calling this method is a no-op. This method will also regenerate a new token + * if the last remaining token is deleted. + */ + public handleDeleteDownloadToken(request: DeleteDownloadTokenRequest): StoredFileMetadata { + if (!this._adminCredsValidator.validate(request.authorization)) { + throw new ForbiddenError(); } - const md = val.metadata; - md.deleteDownloadToken(token); - return md; + const metadata = this.getMetadata(request.bucketId, request.decodedObjectId); + if (!metadata) { + throw new NotFoundError(); + } + metadata.deleteDownloadToken(request.token); + return metadata; } private path(bucket: string, object: string): string { diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index ae7144250ff..f7600d477d5 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -9,7 +9,7 @@ import { StorageRulesManager } from "./rules/manager"; import { StorageRulesetInstance, StorageRulesRuntime, StorageRulesIssues } from "./rules/runtime"; import { SourceFile } from "./rules/types"; import express = require("express"); -import { getRulesValidator } from "./rules/utils"; +import { getAdminCredentialValidator, getRulesValidator } from "./rules/utils"; import { Persistence } from "./persistence"; import { UploadService } from "./upload"; @@ -44,6 +44,7 @@ export class StorageEmulator implements EmulatorInstance { this._storageLayer = new StorageLayer( args.projectId, getRulesValidator(() => this.rules), + getAdminCredentialValidator(), this._persistence ); this._uploadService = new UploadService(this._persistence); diff --git a/src/emulator/storage/rules/utils.ts b/src/emulator/storage/rules/utils.ts index 11457626217..0bf209c253e 100644 --- a/src/emulator/storage/rules/utils.ts +++ b/src/emulator/storage/rules/utils.ts @@ -9,7 +9,8 @@ export type RulesVariableOverrides = { before?: RulesResourceMetadata; after?: RulesResourceMetadata; }; -/** A simple interface for fetching Rules verdicts. */ + +/** Authorizes storage requests via Firebase Rules rulesets. */ export interface RulesValidator { validate( path: string, @@ -19,12 +20,16 @@ export interface RulesValidator { ): Promise; } +/** Authorizes storage requests via admin credentials. */ +export interface AdminCredentialValidator { + validate(authorization?: string): boolean; +} + /** Provider for Storage security rules. */ export type RulesetProvider = () => StorageRulesetInstance | undefined; /** - * Returns a {@link RulesValidator} that pulls a Ruleset from a - * {@link RulesetProvider} on each run. + * Returns a validator that pulls a Ruleset from a {@link RulesetProvider} on each run. */ export function getRulesValidator(rulesetProvider: RulesetProvider): RulesValidator { return { @@ -45,6 +50,11 @@ export function getRulesValidator(rulesetProvider: RulesetProvider): RulesValida }; } +/** Returns a validator for admin credentials. */ +export function getAdminCredentialValidator(): AdminCredentialValidator { + return { validate: isValidAdminCredentials }; +} + /** Authorizes file access based on security rules. */ export async function isPermitted(opts: { ruleset?: StorageRulesetInstance; @@ -65,7 +75,7 @@ export async function isPermitted(opts: { } // Skip auth for UI - if (["Bearer owner", "Firebase owner"].includes(opts.authorization || "")) { + if (isValidAdminCredentials(opts.authorization)) { return true; } @@ -84,3 +94,7 @@ export async function isPermitted(opts: { return !!permitted; } + +function isValidAdminCredentials(authorization?: string) { + return ["Bearer owner", "Firebase owner"].includes(authorization ?? ""); +} diff --git a/src/test/emulators/storage/files.spec.ts b/src/test/emulators/storage/files.spec.ts index a34d1c4d2f7..9d513211624 100644 --- a/src/test/emulators/storage/files.spec.ts +++ b/src/test/emulators/storage/files.spec.ts @@ -17,6 +17,10 @@ const ALWAYS_FALSE_RULES_VALIDATOR = { validate: async () => Promise.resolve(false), }; +const ALWAYS_TRUE_ADMIN_CREDENTIAL_VALIDATOR = { + validate: () => true, +}; + describe("files", () => { it("can serialize and deserialize metadata", () => { const cf = new StorageCloudFunctions("demo-project"); @@ -132,7 +136,12 @@ describe("files", () => { }); const getStorageLayer = (rulesValidator: RulesValidator) => - new StorageLayer("project", rulesValidator, _persistence); + new StorageLayer( + "project", + rulesValidator, + ALWAYS_TRUE_ADMIN_CREDENTIAL_VALIDATOR, + _persistence + ); const getPersistenceTmpDir = () => `${tmpdir()}/firebase/storage/blobs`; }); From 528b29397560f6b88a1bdd24c4487b8c7e4761ff Mon Sep 17 00:00:00 2001 From: abhis3 Date: Thu, 10 Mar 2022 09:43:37 -0800 Subject: [PATCH 0141/1699] Rename `md` to `metadata` (#4282) * Rename md to metadata --- scripts/storage-emulator-integration/tests.ts | 28 +++--- src/emulator/storage/metadata.ts | 94 +++++++++---------- 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index 8dc71f627cd..a62b37ac9a9 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -475,13 +475,13 @@ describe("Storage emulator", () => { }); const cloudFile = testBucket.file(destination); - const md = { + const incomingMetadata = { metadata: { firebaseStorageDownloadTokens: "myFirstToken,mySecondToken", }, }; - await cloudFile.setMetadata(md); + await cloudFile.setMetadata(incomingMetadata); // Check that the tokens are saved in Firebase metadata await supertest(STORAGE_EMULATOR_HOST) @@ -489,13 +489,15 @@ describe("Storage emulator", () => { .expect(200) .then((res) => { const firebaseMd = res.body; - expect(firebaseMd.downloadTokens).to.equal(md.metadata.firebaseStorageDownloadTokens); + expect(firebaseMd.downloadTokens).to.equal( + incomingMetadata.metadata.firebaseStorageDownloadTokens + ); }); // Check that the tokens are saved in Cloud metadata - const [metadata] = await cloudFile.getMetadata(); - expect(metadata.metadata.firebaseStorageDownloadTokens).to.deep.equal( - md.metadata.firebaseStorageDownloadTokens + const [storedMetadata] = await cloudFile.getMetadata(); + expect(storedMetadata.metadata.firebaseStorageDownloadTokens).to.deep.equal( + incomingMetadata.metadata.firebaseStorageDownloadTokens ); }); }); @@ -1233,8 +1235,8 @@ describe("Storage emulator", () => { .set({ Authorization: "Bearer owner" }) .expect(200) .then((res) => { - const md = res.body; - expect(md.downloadTokens.split(",").length).to.deep.equal(2); + const metadata = res.body; + expect(metadata.downloadTokens.split(",").length).to.deep.equal(2); }); }); @@ -1268,8 +1270,8 @@ describe("Storage emulator", () => { .set({ Authorization: "Bearer owner" }) .expect(200) .then((res) => { - const md = res.body; - expect(md.downloadTokens.split(",")).to.deep.equal([tokens[1]]); + const metadata = res.body; + expect(metadata.downloadTokens.split(",")).to.deep.equal([tokens[1]]); }); }); @@ -1287,9 +1289,9 @@ describe("Storage emulator", () => { .set({ Authorization: "Bearer owner" }) .expect(200) .then((res) => { - const md = res.body; - expect(md.downloadTokens.split(",").length).to.deep.equal(1); - expect(md.downloadTokens.split(",")).to.not.deep.equal([token]); + const metadata = res.body; + expect(metadata.downloadTokens.split(",").length).to.deep.equal(1); + expect(metadata.downloadTokens.split(",")).to.not.deep.equal([token]); }); }); diff --git a/src/emulator/storage/metadata.ts b/src/emulator/storage/metadata.ts index a38da2865bf..b32fb3c5d13 100644 --- a/src/emulator/storage/metadata.ts +++ b/src/emulator/storage/metadata.ts @@ -297,25 +297,25 @@ export class OutgoingFirebaseMetadata { downloadTokens: string; metadata: object | undefined; - constructor(md: StoredFileMetadata) { - this.name = md.name; - this.bucket = md.bucket; - this.generation = md.generation.toString(); - this.metageneration = md.metageneration.toString(); - this.contentType = md.contentType; - this.timeCreated = toSerializedDate(md.timeCreated); - this.updated = toSerializedDate(md.updated); - this.storageClass = md.storageClass; - this.size = md.size.toString(); - this.md5Hash = md.md5Hash; - this.crc32c = md.crc32c; - this.etag = md.etag; - this.downloadTokens = md.downloadTokens.join(","); - this.contentEncoding = md.contentEncoding; - this.contentDisposition = md.contentDisposition; - this.metadata = md.customMetadata; - this.contentLanguage = md.contentLanguage; - this.cacheControl = md.cacheControl; + constructor(metadata: StoredFileMetadata) { + this.name = metadata.name; + this.bucket = metadata.bucket; + this.generation = metadata.generation.toString(); + this.metageneration = metadata.metageneration.toString(); + this.contentType = metadata.contentType; + this.timeCreated = toSerializedDate(metadata.timeCreated); + this.updated = toSerializedDate(metadata.updated); + this.storageClass = metadata.storageClass; + this.size = metadata.size.toString(); + this.md5Hash = metadata.md5Hash; + this.crc32c = metadata.crc32c; + this.etag = metadata.etag; + this.downloadTokens = metadata.downloadTokens.join(","); + this.contentEncoding = metadata.contentEncoding; + this.contentDisposition = metadata.contentDisposition; + this.metadata = metadata.customMetadata; + this.contentLanguage = metadata.contentLanguage; + this.cacheControl = metadata.cacheControl; } } @@ -388,31 +388,31 @@ export class CloudStorageObjectMetadata { selfLink: string; mediaLink: string; - constructor(md: StoredFileMetadata) { - this.name = md.name; - this.bucket = md.bucket; - this.generation = md.generation.toString(); - this.metageneration = md.metageneration.toString(); - this.contentType = md.contentType; - this.timeCreated = toSerializedDate(md.timeCreated); - this.updated = toSerializedDate(md.updated); - this.storageClass = md.storageClass; - this.size = md.size.toString(); - this.md5Hash = md.md5Hash; - this.etag = md.etag; + constructor(metadata: StoredFileMetadata) { + this.name = metadata.name; + this.bucket = metadata.bucket; + this.generation = metadata.generation.toString(); + this.metageneration = metadata.metageneration.toString(); + this.contentType = metadata.contentType; + this.timeCreated = toSerializedDate(metadata.timeCreated); + this.updated = toSerializedDate(metadata.updated); + this.storageClass = metadata.storageClass; + this.size = metadata.size.toString(); + this.md5Hash = metadata.md5Hash; + this.etag = metadata.etag; this.metadata = {}; - if (Object.keys(md.customMetadata || {})) { + if (Object.keys(metadata.customMetadata || {})) { this.metadata = { ...this.metadata, - ...md.customMetadata, + ...metadata.customMetadata, }; } - if (md.downloadTokens.length) { + if (metadata.downloadTokens.length) { this.metadata = { ...this.metadata, - firebaseStorageDownloadTokens: md.downloadTokens.join(","), + firebaseStorageDownloadTokens: metadata.downloadTokens.join(","), }; } @@ -420,30 +420,30 @@ export class CloudStorageObjectMetadata { delete this.metadata; } - if (md.contentLanguage) { - this.contentLanguage = md.contentLanguage; + if (metadata.contentLanguage) { + this.contentLanguage = metadata.contentLanguage; } - if (md.cacheControl) { - this.cacheControl = md.cacheControl; + if (metadata.cacheControl) { + this.cacheControl = metadata.cacheControl; } - if (md.customTime) { - this.customTime = toSerializedDate(md.customTime); + if (metadata.customTime) { + this.customTime = toSerializedDate(metadata.customTime); } // I'm not sure why but @google-cloud/storage calls .substr(4) on this value, so we need to pad it. - this.crc32c = "----" + Buffer.from([md.crc32c]).toString("base64"); + this.crc32c = "----" + Buffer.from([metadata.crc32c]).toString("base64"); - this.timeStorageClassUpdated = toSerializedDate(md.timeCreated); - this.id = `${md.bucket}/${md.name}/${md.generation}`; + this.timeStorageClassUpdated = toSerializedDate(metadata.timeCreated); + this.id = `${metadata.bucket}/${metadata.name}/${metadata.generation}`; this.selfLink = `http://${EmulatorRegistry.getInfo(Emulators.STORAGE)?.host}:${ EmulatorRegistry.getInfo(Emulators.STORAGE)?.port - }/storage/v1/b/${md.bucket}/o/${encodeURIComponent(md.name)}`; + }/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`; this.mediaLink = `http://${EmulatorRegistry.getInfo(Emulators.STORAGE)?.host}:${ EmulatorRegistry.getInfo(Emulators.STORAGE)?.port - }/download/storage/v1/b/${md.bucket}/o/${encodeURIComponent(md.name)}?generation=${ - md.generation + }/download/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}?generation=${ + metadata.generation }&alt=media`; } } From 74656c27fd6cefe6c4320884f4121121a211a310 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Thu, 10 Mar 2022 18:58:14 -0500 Subject: [PATCH 0142/1699] fix event type (#4286) --- src/deploy/functions/services/index.ts | 2 +- src/functions/events/v2.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 70bf9773090..9e51f9de0e5 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -54,7 +54,7 @@ export const EVENT_SERVICE_MAPPING: Record = { "google.cloud.storage.object.v1.archived": StorageService, "google.cloud.storage.object.v1.deleted": StorageService, "google.cloud.storage.object.v1.metadataUpdated": StorageService, - "firebase.firebasealerts.alerts.v1.published": FirebaseAlertsService, + "google.firebase.firebasealerts.alerts.v1.published": FirebaseAlertsService, }; /** diff --git a/src/functions/events/v2.ts b/src/functions/events/v2.ts index 851f5030917..09f22591f74 100644 --- a/src/functions/events/v2.ts +++ b/src/functions/events/v2.ts @@ -7,7 +7,7 @@ export const STORAGE_EVENTS = [ "google.cloud.storage.object.v1.metadataUpdated", ] as const; -export const FIREBASE_ALERTS_PUBLISH_EVENT = "firebase.firebasealerts.alerts.v1.published"; +export const FIREBASE_ALERTS_PUBLISH_EVENT = "google.firebase.firebasealerts.alerts.v1.published"; export type Event = | typeof PUBSUB_PUBLISH_EVENT From 7ef4a8868b7e2148f8a1682cf1ae0d326a78b4f7 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 14 Mar 2022 09:03:00 -0700 Subject: [PATCH 0143/1699] add changelog entry for --local (#4264) Co-authored-by: Elvis Sun --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e89be84ba17..8a49bae5c4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Fixes bug where functions' memory configurations weren't preserved in batched function deploys (#4253). +- Adds --local flag to ext:install, ext:update, ext:configure, and ext:uninstall, to save changes to firebase.json instead of deploying immediately. - `ext:export` now uses stable ordering for params in .env files (#4256). - Adds alerting event provider (#4258). From f11e998d3a0d3ae83a72bf4430523b7d7a1d921d Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 14 Mar 2022 09:25:37 -0700 Subject: [PATCH 0144/1699] Support demo- projects in Extensions Emulator (#4251) * Support demo- projects in Extensions Emulator * fixing defaults * better defaults * PR fixes * adding unit tests * fixing imports --- src/deploy/extensions/planner.ts | 15 +++-- src/emulator/constants.ts | 1 + src/emulator/controller.ts | 6 +- src/emulator/extensions/validation.ts | 4 +- src/emulator/extensionsEmulator.ts | 32 ++++++++--- src/extensions/extensionsHelper.ts | 32 +++++++---- .../emulators/extensions/validation.spec.ts | 24 +++++++- src/test/extensions/extensionsHelper.spec.ts | 55 ++++++++++++++++++- 8 files changed, 138 insertions(+), 31 deletions(-) diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 1d2c2310b57..85ce3a428e1 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -1,10 +1,9 @@ -import * as path from "path"; import * as semver from "semver"; -import { FirebaseError } from "../../error"; import * as extensionsApi from "../../extensions/extensionsApi"; -import { getFirebaseProjectParams, substituteParams } from "../../extensions/extensionsHelper"; import * as refs from "../../extensions/refs"; +import { FirebaseError } from "../../error"; +import { getFirebaseProjectParams, substituteParams } from "../../extensions/extensionsHelper"; import { logger } from "../../logger"; import { readInstanceParam } from "../../extensions/manifest"; @@ -71,8 +70,12 @@ export async function have(projectId: string): Promise { * want checks firebase.json and the extensions directory for which extensions * the user wants installed on their project. * @param projectId The project we are deploying to + * @param projectNumber The project number we are deploying to. Used for checking .env files. + * @param aliases An array of aliases for the project we are deploying to. Used for checking .env files. * @param projectDir The directory containing firebase.json and extensions/ * @param extensions The extensions section of firebase.jsonm + * @param emulatorMode Whether the output will be used by the Extensions emulator. + * If true, this will check {instanceId}.env.local for params and will respect `demo-` project rules. */ export async function want(args: { projectId: string; @@ -80,7 +83,7 @@ export async function want(args: { aliases: string[]; projectDir: string; extensions: Record; - checkLocal?: boolean; + emulatorMode?: boolean; }): Promise { const instanceSpecs: InstanceSpec[] = []; const errors: FirebaseError[] = []; @@ -96,9 +99,9 @@ export async function want(args: { projectId: args.projectId, projectNumber: args.projectNumber, aliases: args.aliases, - checkLocal: args.checkLocal, + checkLocal: args.emulatorMode, }); - const autoPopulatedParams = await getFirebaseProjectParams(args.projectId); + const autoPopulatedParams = await getFirebaseProjectParams(args.projectId, args.emulatorMode); const subbedParams = substituteParams(params, autoPopulatedParams); instanceSpecs.push({ diff --git a/src/emulator/constants.ts b/src/emulator/constants.ts index 57215705aed..c2fcdf67dfe 100644 --- a/src/emulator/constants.ts +++ b/src/emulator/constants.ts @@ -50,6 +50,7 @@ export class Constants { // GCP projects cannot start with 'demo' so we use 'demo-' as a prefix to denote // an intentionally fake project. static FAKE_PROJECT_ID_PREFIX = "demo-"; + static FAKE_PROJECT_NUMBER = "0"; static DEFAULT_DATABASE_EMULATOR_NAMESPACE = "fake-server"; diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 3eb2dd919b8..f55da5e07ad 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -447,10 +447,10 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) } if (shouldStart(options, Emulators.EXTENSIONS) && previews.extensionsemulator) { - // TODO: This should not error out when called with a fake project. - const projectNumber = await needProjectNumber(options); + const projectNumber = Constants.isDemoProject(projectId) + ? Constants.FAKE_PROJECT_NUMBER + : await needProjectNumber(options); const aliases = getAliases(options, projectId); - const extensionEmulator = new ExtensionsEmulator({ projectId, projectDir: options.config.projectDir, diff --git a/src/emulator/extensions/validation.ts b/src/emulator/extensions/validation.ts index 1c31ffd2dc1..01e210d1dc3 100644 --- a/src/emulator/extensions/validation.ts +++ b/src/emulator/extensions/validation.ts @@ -37,7 +37,9 @@ export async function getUnemulatedAPIs( if (unemulatedAPIs[api.apiName]) { unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId); } else { - const enabled = await check(projectId, api.apiName, "extensions", true); + const enabled = + !Constants.isDemoProject(projectId) && + (await check(projectId, api.apiName, "extensions", true)); unemulatedAPIs[api.apiName] = { apiName: api.apiName, instanceIds: [i.instanceId], diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 249be501a29..de5aea7b3d9 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -17,6 +17,7 @@ import { Emulators } from "./types"; import { checkForUnemulatedTriggerTypes, getUnemulatedAPIs } from "./extensions/validation"; import { enableApiURI } from "../ensureApiEnabled"; import { shortenUrl } from "../shortenUrl"; +import { Constants } from "./constants"; export interface ExtensionEmulatorArgs { projectId: string; @@ -49,7 +50,7 @@ export class ExtensionsEmulator { aliases: this.args.aliases ?? [], projectDir: this.args.projectDir, extensions: this.args.extensions, - checkLocal: true, + emulatorMode: true, }); } @@ -224,14 +225,27 @@ export class ExtensionsEmulator { apiToWarn.enabled ? "" : clc.bold.underline(enablementUri), ]); } - - this.logger.logLabeled( - "WARN", - "Extensions", - `The following Extensions make calls to Google Cloud APIs that do not have Emulators. ` + - `These calls will go to production Google Cloud APIs which may have real effects on ${this.args.projectId}.\n` + - table.toString() - ); + if (Constants.isDemoProject(this.args.projectId)) { + this.logger.logLabeled( + "WARN", + "Extensions", + "The following Extensions make calls to Google Cloud APIs that do not have Emulators. " + + `${clc.bold( + this.args.projectId + )} is a demo project, so these Extensions may not work as expected.\n` + + table.toString() + ); + } else { + this.logger.logLabeled( + "WARN", + "Extensions", + "The following Extensions make calls to Google Cloud APIs that do not have Emulators. " + + `These calls will go to production Google Cloud APIs which may have real effects on ${clc.bold( + this.args.projectId + )}.\n` + + table.toString() + ); + } } } diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 1967e0e5ce2..ebc3155b492 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -14,6 +14,7 @@ import { storageOrigin } from "../api"; import { archiveDirectory } from "../archiveDirectory"; import { convertOfficialExtensionsToList } from "./utils"; import { getFirebaseConfig } from "../functionsConfig"; +import { getProjectAdminSdkConfigOrCached } from "../emulator/adminSdkConfig"; import { getExtensionRegistry } from "./resolveSource"; import { FirebaseError } from "../error"; import { diagnose } from "./diagnose"; @@ -37,6 +38,7 @@ import { logger } from "../logger"; import { envOverride } from "../utils"; import { getLocalChangelog } from "./changelog"; import { getProjectNumber } from "../getProjectNumber"; +import { Constants } from "../emulator/constants"; /** * SpecParamType represents the exact strings that the extensions @@ -105,23 +107,33 @@ export function getDBInstanceFromURL(databaseUrl = ""): string { /** * Gets Firebase project specific param values. */ -export async function getFirebaseProjectParams(projectId: string): Promise> { - const body = await getFirebaseConfig({ project: projectId }); - const projectNumber = await getProjectNumber({ projectId }); +export async function getFirebaseProjectParams( + projectId: string, + emulatorMode: boolean = false +): Promise> { + const body = emulatorMode + ? await getProjectAdminSdkConfigOrCached(projectId) + : await getFirebaseConfig({ project: projectId }); + const projectNumber = + emulatorMode && Constants.isDemoProject(projectId) + ? Constants.FAKE_PROJECT_NUMBER + : await getProjectNumber({ projectId }); + const databaseURL = body?.databaseURL ?? `https://${projectId}.firebaseio.com`; + const storageBucket = body?.storageBucket ?? `${projectId}.appspot.com`; // This env variable is needed for parameter-less initialization of firebase-admin const FIREBASE_CONFIG = JSON.stringify({ - projectId: body.projectId, - databaseURL: body.databaseURL, - storageBucket: body.storageBucket, + projectId, + databaseURL, + storageBucket, }); return { - PROJECT_ID: body.projectId, + PROJECT_ID: projectId, PROJECT_NUMBER: projectNumber, - DATABASE_URL: body.databaseURL, - STORAGE_BUCKET: body.storageBucket, + DATABASE_URL: databaseURL, + STORAGE_BUCKET: storageBucket, FIREBASE_CONFIG, - DATABASE_INSTANCE: getDBInstanceFromURL(body.databaseURL), + DATABASE_INSTANCE: getDBInstanceFromURL(databaseURL), }; } diff --git a/src/test/emulators/extensions/validation.spec.ts b/src/test/emulators/extensions/validation.spec.ts index e3126b85b41..20fec28ff0c 100644 --- a/src/test/emulators/extensions/validation.spec.ts +++ b/src/test/emulators/extensions/validation.spec.ts @@ -76,9 +76,10 @@ describe("ExtensionsEmulator validation validation", () => { const testProjectId = "test-project"; const testAPI = "test.googleapis.com"; const sandbox = sinon.createSandbox(); + let checkStub: sinon.SinonStub; beforeEach(() => { - const checkStub = sandbox.stub(ensureApiEnabled, "check"); + checkStub = sandbox.stub(ensureApiEnabled, "check"); checkStub.withArgs(testProjectId, testAPI, "extensions", true).resolves(true); checkStub.throws("Unexpected API checked in test"); }); @@ -106,6 +107,27 @@ describe("ExtensionsEmulator validation validation", () => { }, ]); }); + + it("should not check on demo- projects", async () => { + const instanceIdWithUnemulatedAPI = "unemulated"; + const instanceId2WithUnemulatedAPI = "unemulated2"; + const instanceIdWithEmulatedAPI = "emulated"; + + const result = await validation.getUnemulatedAPIs(`demo-${testProjectId}`, [ + fakeInstanceSpecWithAPI(instanceIdWithEmulatedAPI, "firestore.googleapis.com"), + fakeInstanceSpecWithAPI(instanceIdWithUnemulatedAPI, testAPI), + fakeInstanceSpecWithAPI(instanceId2WithUnemulatedAPI, testAPI), + ]); + + expect(result).to.deep.equal([ + { + apiName: testAPI, + instanceIds: [instanceIdWithUnemulatedAPI, instanceId2WithUnemulatedAPI], + enabled: false, + }, + ]); + expect(checkStub.callCount).to.equal(0); + }); }); describe(`${validation.checkForUnemulatedTriggerTypes.name}`, () => { diff --git a/src/test/extensions/extensionsHelper.spec.ts b/src/test/extensions/extensionsHelper.spec.ts index b163751b02f..fd77dfefee8 100644 --- a/src/test/extensions/extensionsHelper.spec.ts +++ b/src/test/extensions/extensionsHelper.spec.ts @@ -4,7 +4,8 @@ import * as sinon from "sinon"; import { FirebaseError } from "../../error"; import * as extensionsApi from "../../extensions/extensionsApi"; import * as extensionsHelper from "../../extensions/extensionsHelper"; -import * as resolveSource from "../../extensions/resolveSource"; +import * as getProjectNumber from "../../getProjectNumber"; +import * as functionsConfig from "../../functionsConfig"; import { storage } from "../../gcp"; import * as archiveDirectory from "../../archiveDirectory"; import * as prompt from "../../prompt"; @@ -837,4 +838,56 @@ describe("extensionsHelper", () => { ); }); }); + + describe("getFirebaseProjectParams", () => { + const sandbox = sinon.createSandbox(); + let projectNumberStub: sinon.SinonStub; + let getFirebaseConfigStub: sinon.SinonStub; + + beforeEach(() => { + projectNumberStub = sandbox.stub(getProjectNumber, "getProjectNumber").resolves("1"); + getFirebaseConfigStub = sandbox.stub(functionsConfig, "getFirebaseConfig").resolves({ + projectId: "test", + storageBucket: "real-test.appspot.com", + databaseURL: "https://real-test.firebaseio.com", + locationId: "us-west1", + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should not call prodution when using a demo- project in emulator mode", async () => { + const res = await extensionsHelper.getFirebaseProjectParams("demo-test", true); + + expect(res).to.deep.equal({ + DATABASE_INSTANCE: "demo-test", + DATABASE_URL: "https://demo-test.firebaseio.com", + FIREBASE_CONFIG: + '{"projectId":"demo-test","databaseURL":"https://demo-test.firebaseio.com","storageBucket":"demo-test.appspot.com"}', + PROJECT_ID: "demo-test", + PROJECT_NUMBER: "0", + STORAGE_BUCKET: "demo-test.appspot.com", + }); + expect(projectNumberStub).not.to.have.been.called; + expect(getFirebaseConfigStub).not.to.have.been.called; + }); + + it("should return real values for non 'demo-' projects", async () => { + const res = await extensionsHelper.getFirebaseProjectParams("real-test", false); + + expect(res).to.deep.equal({ + DATABASE_INSTANCE: "real-test", + DATABASE_URL: "https://real-test.firebaseio.com", + FIREBASE_CONFIG: + '{"projectId":"real-test","databaseURL":"https://real-test.firebaseio.com","storageBucket":"real-test.appspot.com"}', + PROJECT_ID: "real-test", + PROJECT_NUMBER: "1", + STORAGE_BUCKET: "real-test.appspot.com", + }); + expect(projectNumberStub).to.have.been.called; + expect(getFirebaseConfigStub).to.have.been.called; + }); + }); }); From 0c897b3f35269b14c61b591fb8a2815c16672073 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 14 Mar 2022 10:12:44 -0700 Subject: [PATCH 0145/1699] Load project-specific environment variables in the Functions Emulator (#4273) Today, Functions Emulator does not load project-specific environment variables. This contradicts the public documentation which states: > When using a local Cloud Functions emulator, you can override environment variables for your project by setting up a .env.local file. Contents of .env.local take precedence over .env and the project-specific .env file. https://firebase.google.com/docs/functions/config-env#emulator_support The patch fixes the bug by making appropriate changes to load project-specific environment variables (either in `.env.projectId` or `.env.projectAlias`) when emulating functions. Fixes https://github.com/firebase/firebase-tools/issues/4239 --- CHANGELOG.md | 1 + src/emulator/controller.ts | 1 + src/emulator/functionsEmulator.ts | 2 ++ src/functions/env.ts | 9 +++-- src/serve/functions.ts | 1 + src/test/functions/env.spec.ts | 56 +++++++++++++++++++++++++++++++ 6 files changed, 65 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a49bae5c4f..2a23a5c1b35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Adds --local flag to ext:install, ext:update, ext:configure, and ext:uninstall, to save changes to firebase.json instead of deploying immediately. - `ext:export` now uses stable ordering for params in .env files (#4256). - Adds alerting event provider (#4258). +- Fixes bug where project-specific environment variables weren't loaded by the Functions Emulator (#4273). diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index f55da5e07ad..c803830936d 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -512,6 +512,7 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) host: functionsAddr.host, port: functionsAddr.port, debugPort: inspectFunctions, + projectAlias: options.projectAlias, }); await startEmulator(functionsEmulator); } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index a76d43cc406..6c3139792a9 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -119,6 +119,7 @@ export interface FunctionsEmulatorArgs { debugPort?: number; remoteEmulators?: { [key: string]: EmulatorInfo }; adminSdkConfig?: AdminSdkConfig; + projectAlias?: string; } // FunctionsRuntimeInstance is the handler for a running function invocation @@ -944,6 +945,7 @@ export class FunctionsEmulator implements EmulatorInstance { const projectInfo = { functionsSource: backend.functionsDir, projectId: this.args.projectId, + projectAlias: this.args.projectAlias, isEmulator: true, }; diff --git a/src/functions/env.ts b/src/functions/env.ts index 8733578ebd9..8cee9d6f450 100644 --- a/src/functions/env.ts +++ b/src/functions/env.ts @@ -196,13 +196,12 @@ function findEnvfiles( isEmulator?: boolean ): string[] { const files: string[] = [".env"]; + files.push(`.env.${projectId}`); + if (projectAlias) { + files.push(`.env.${projectAlias}`); + } if (isEmulator) { files.push(FUNCTIONS_EMULATOR_DOTENV); - } else { - files.push(`.env.${projectId}`); - if (projectAlias && projectAlias.length) { - files.push(`.env.${projectAlias}`); - } } return files diff --git a/src/serve/functions.ts b/src/serve/functions.ts index f5f0c4c42c0..5ed8d9a5d8b 100644 --- a/src/serve/functions.ts +++ b/src/serve/functions.ts @@ -46,6 +46,7 @@ export class FunctionsServer { projectId, projectDir: options.config.projectDir, emulatableBackends: [this.backend], + projectAlias: options.projectAlias, account, ...partialArgs, }; diff --git a/src/test/functions/env.spec.ts b/src/test/functions/env.spec.ts index a489f87d962..2668b5ed3c4 100644 --- a/src/test/functions/env.spec.ts +++ b/src/test/functions/env.spec.ts @@ -328,6 +328,20 @@ FOO=foo }); }); + it("loads envs, preferring ones from .env. for emulators too", () => { + createEnvFiles(tmpdir, { + ".env": "FOO=bad\nBAR=bar", + [`.env.${projectInfo.projectId}`]: "FOO=good", + }); + + expect( + env.loadUserEnvs({ ...projectInfo, functionsSource: tmpdir, isEmulator: true }) + ).to.be.deep.equal({ + FOO: "good", + BAR: "bar", + }); + }); + it("loads envs, preferring ones from .env.", () => { createEnvFiles(tmpdir, { ".env": "FOO=bad\nBAR=bar", @@ -340,6 +354,48 @@ FOO=foo }); }); + it("loads envs, preferring ones from .env. for emulators too", () => { + createEnvFiles(tmpdir, { + ".env": "FOO=bad\nBAR=bar", + [`.env.${projectInfo.projectAlias}`]: "FOO=good", + }); + + expect( + env.loadUserEnvs({ ...projectInfo, functionsSource: tmpdir, isEmulator: true }) + ).to.be.deep.equal({ + FOO: "good", + BAR: "bar", + }); + }); + + it("loads envs ignoring .env.local", () => { + createEnvFiles(tmpdir, { + ".env": "FOO=bad\nBAR=bar", + [`.env.${projectInfo.projectId}`]: "FOO=good", + ".env.local": "FOO=bad", + }); + + expect(env.loadUserEnvs({ ...projectInfo, functionsSource: tmpdir })).to.be.deep.equal({ + FOO: "good", + BAR: "bar", + }); + }); + + it("loads envs, preferring .env.local for the emulator", () => { + createEnvFiles(tmpdir, { + ".env": "FOO=bad\nBAR=bar", + [`.env.${projectInfo.projectId}`]: "FOO=another bad", + ".env.local": "FOO=good", + }); + + expect( + env.loadUserEnvs({ ...projectInfo, functionsSource: tmpdir, isEmulator: true }) + ).to.be.deep.equal({ + FOO: "good", + BAR: "bar", + }); + }); + it("throws an error if both .env. and .env. exists", () => { createEnvFiles(tmpdir, { ".env": "FOO=foo\nBAR=bar", From 641b916d9f7df67dd982255d70a17ad7f37ae477 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 14 Mar 2022 15:20:27 -0700 Subject: [PATCH 0146/1699] Only apply CORS to /backends in Functions emulator (#4295) * Only apply CORS to listBackends route * typo --- CHANGELOG.md | 1 + src/emulator/functionsEmulator.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a23a5c1b35..f264c6eca62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - `ext:export` now uses stable ordering for params in .env files (#4256). - Adds alerting event provider (#4258). - Fixes bug where project-specific environment variables weren't loaded by the Functions Emulator (#4273). +- Fixes bug where CORS was enabled too broadly on the Functions emulator (#4294). diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 6c3139792a9..3a666c565a4 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -238,7 +238,6 @@ export class FunctionsEmulator implements EmulatorInstance { this.workQueue.start(); const hub = express(); - hub.use(cors({ origin: true })); // Enable cors so the Emulator UI can call out to the Functions Emulator. const dataMiddleware: express.RequestHandler = (req, res, next) => { const chunks: Buffer[] = []; @@ -349,7 +348,7 @@ export class FunctionsEmulator implements EmulatorInstance { // The ordering here is important. The longer routes (background) // need to be registered first otherwise the HTTP functions consume // all events. - hub.get(listBackendsRoute, dataMiddleware, listBackendsHandler); + hub.get(listBackendsRoute, cors({ origin: true }), listBackendsHandler); // This route needs CORS so the Emulator UI can call it. hub.post(backgroundFunctionRoute, dataMiddleware, backgroundHandler); hub.post(multicastFunctionRoute, dataMiddleware, multicastHandler); hub.all(httpsFunctionRoutes, dataMiddleware, httpsHandler); From f519395541d686eb11082717be8338194c89e261 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Mon, 14 Mar 2022 18:54:52 -0400 Subject: [PATCH 0147/1699] Temporarily skip Storage Rules Manager unit tests (#4297) --- src/test/emulators/storage/rules/manager.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/emulators/storage/rules/manager.spec.ts b/src/test/emulators/storage/rules/manager.spec.ts index 18d670ce6db..35c42f297cc 100644 --- a/src/test/emulators/storage/rules/manager.spec.ts +++ b/src/test/emulators/storage/rules/manager.spec.ts @@ -9,7 +9,8 @@ import { StorageRulesRuntime } from "../../../../emulator/storage/rules/runtime" import { Persistence } from "../../../../emulator/storage/persistence"; import { RulesetOperationMethod } from "../../../../emulator/storage/rules/types"; -describe("Storage Rules Manager", function () { +// TODO(hsinpei: Make this an integration test +describe.skip("Storage Rules Manager", function () { const rulesRuntime = new StorageRulesRuntime(); const rulesManager = new StorageRulesManager(rulesRuntime); From 9871e536f8828c009040822f7ac042bfbb97f701 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 14 Mar 2022 17:41:32 -0700 Subject: [PATCH 0148/1699] Adding secret support for Extensions emulator (#4287) * adding secret support for Extensions * adding tests and populating secretEnv for cf3 backend * Capitalizing ParamType values and removing secretEnv from backendInfo * fixing integration tests * pr cleanup * pr fixes * oops, these were supposed to be in the next PR * fixing merge issue --- .../emulator-tests/functionsEmulator.spec.ts | 2 + .../functionsEmulatorRuntime.spec.ts | 1 + src/deploy/functions/backend.ts | 14 ++++- src/emulator/controller.ts | 1 + src/emulator/extensionsEmulator.ts | 10 ++-- src/emulator/functionsEmulator.ts | 29 +++++++--- src/emulator/functionsEmulatorShared.ts | 4 +- src/extensions/emulator/optionsHelper.ts | 57 +++++++++++++++++- src/extensions/emulator/specHelper.ts | 2 +- src/extensions/extensionsApi.ts | 3 +- src/serve/functions.ts | 1 + .../emulators/extensions/validation.spec.ts | 3 +- src/test/emulators/extensionsEmulator.spec.ts | 1 + .../extensions/emulator/optionsHelper.spec.ts | 58 ++++++++++++++++++- 14 files changed, 162 insertions(+), 24 deletions(-) diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index a1457885b78..3996083f264 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -41,6 +41,7 @@ const functionsEmulator = new FunctionsEmulator({ { functionsDir: MODULE_ROOT, env: {}, + secretEnv: [], }, ], quiet: true, @@ -49,6 +50,7 @@ const functionsEmulator = new FunctionsEmulator({ const testBackend = { functionsDir: MODULE_ROOT, env: {}, + secretEnv: [], nodeBinary: process.execPath, }; diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index ace509fc59c..3d9c9f45c73 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -22,6 +22,7 @@ const DO_NOTHING = () => { const testBackend = { functionsDir: MODULE_ROOT, env: {}, + secretEnv: [], nodeBinary: process.execPath, }; diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index d09bad32ba1..56e9a4cca3a 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -184,15 +184,23 @@ export interface TargetIds { project: string; } +/** + * Represents a Secret or Secret Version resource. + * Based on https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#secretenvvar + */ export interface SecretEnvVar { - key: string; - secret: string; - projectId: string; + key: string; // The environment variable this secret is accessible at + secret: string; // The id of the SecretVersion - ie for projects/myproject/secrets/mysecret, this is 'mysecret' + projectId: string; // The project containing the Secret // Internal use only. Users cannot pin secret to a specific version. version?: string; } +export function secretVersionName(s: SecretEnvVar): string { + return `projects/${s.projectId}/secrets/${s.secret}/versions/${s.version ?? "latest"}`; +} + export interface ServiceConfiguration { concurrency?: number; labels?: Record; diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index c803830936d..04932c1ed3b 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -437,6 +437,7 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) env: { ...options.extDevEnv, }, + secretEnv: [], // CF3 secrets are bound to specific functions, so we'll get them during trigger discovery. // TODO(b/213335255): predefinedTriggers and nodeMajorVersion are here to support ext:dev:emulators:* commands. // Ideally, we should handle that case via ExtensionEmulator. predefinedTriggers: options.extDevTriggers as ParsedTriggerDefinition[] | undefined, diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index de5aea7b3d9..b25bab8fd17 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -172,16 +172,14 @@ export class ExtensionsEmulator { const functionsDir = path.join(extensionDir, "functions"); // TODO(b/213335255): For local extensions, this should include extensionSpec instead of extensionVersion const env = Object.assign(this.autoPopulatedParams(instance), instance.params); - const { extensionTriggers, nodeMajorVersion } = await getExtensionFunctionInfo( - extensionDir, - instance.instanceId, - env - ); + const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = + await getExtensionFunctionInfo(extensionDir, instance.instanceId, env); const extension = await planner.getExtension(instance); const extensionVersion = await planner.getExtensionVersion(instance); return { functionsDir, - env, + env: nonSecretEnv, + secretEnv: secretEnvVariables, predefinedTriggers: extensionTriggers, nodeMajorVersion: nodeMajorVersion, extensionInstanceId: instance.instanceId, diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 3a666c565a4..f85ad8a9d25 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -62,6 +62,8 @@ import { accessSecretVersion } from "../gcp/secretManager"; import * as runtimes from "../deploy/functions/runtimes"; import * as backend from "../deploy/functions/backend"; import * as functionsEnv from "../functions/env"; +import { flattenArray } from "../functional"; +import { SecretEnvVar } from "../gcp/cloudfunctions"; const EVENT_INVOKE = "functions:invoke"; const LOCAL_SECRETS_FILE = ".secret.local"; @@ -85,6 +87,7 @@ const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs export interface EmulatableBackend { functionsDir: string; env: Record; + secretEnv: backend.SecretEnvVar[]; predefinedTriggers?: ParsedTriggerDefinition[]; nodeMajorVersion?: number; nodeBinary?: string; @@ -99,7 +102,7 @@ export interface EmulatableBackend { */ export interface BackendInfo { directory: string; - env: Record; + env: Record; // TODO: Consider exposing more information about where param values come from & if they are locally overwritten. functionTriggers: ParsedTriggerDefinition[]; extensionInstanceId?: string; extension?: Extension; // Only present for published extensions @@ -483,7 +486,10 @@ export class FunctionsEmulator implements EmulatorInstance { let triggerDefinitions: EmulatedTriggerDefinition[]; if (emulatableBackend.predefinedTriggers) { - triggerDefinitions = emulatedFunctionsByRegion(emulatableBackend.predefinedTriggers); + triggerDefinitions = emulatedFunctionsByRegion( + emulatableBackend.predefinedTriggers, + emulatableBackend.secretEnv + ); } else { const runtimeConfig = this.getRuntimeConfig(emulatableBackend); const runtimeDelegateContext: runtimes.DelegateContext = { @@ -833,9 +839,14 @@ export class FunctionsEmulator implements EmulatorInstance { .filter((t) => !t.backend.extensionInstanceId) .map((t) => t.def); return this.args.emulatableBackends.map((e: EmulatableBackend) => { + const envWithSecrets = Object.assign({}, e.env); + for (const s of e.secretEnv) { + envWithSecrets[s.key] = backend.secretVersionName(s); + } + return { directory: e.functionsDir, - env: e.env, + env: envWithSecrets, extensionInstanceId: e.extensionInstanceId, // Present on all extensions extension: e.extension, // Only present on published extensions extensionVersion: e.extensionVersion, // Only present on published extensions @@ -1083,11 +1094,15 @@ export class FunctionsEmulator implements EmulatorInstance { if (trigger) { const secrets: backend.SecretEnvVar[] = trigger.secretEnvironmentVariables || []; const accesses = secrets - .filter((s) => !secretEnvs[s.secret]) + .filter((s) => !secretEnvs[s.key]) .map(async (s) => { - this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.key}@latest`); - const value = await accessSecretVersion(this.getProjectId(), s.key, "latest"); - return [s.secret, value]; + this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.secret}@latest`); + const value = await accessSecretVersion( + this.getProjectId(), + s.secret, + s.version ?? "latest" + ); + return [s.key, value]; }); const accessResults = await allSettled(accesses); diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 668b8e1d521..bd3572749e5 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -211,7 +211,8 @@ export function emulatedFunctionsFromEndpoints( * @return A list of all CloudFunctions in the deployment, with copies for each region. */ export function emulatedFunctionsByRegion( - definitions: ParsedTriggerDefinition[] + definitions: ParsedTriggerDefinition[], + secretEnvVariables: backend.SecretEnvVar[] = [] ): EmulatedTriggerDefinition[] { const regionDefinitions: EmulatedTriggerDefinition[] = []; for (const def of definitions) { @@ -227,6 +228,7 @@ export function emulatedFunctionsByRegion( defDeepCopy.region = region; defDeepCopy.id = `${region}-${defDeepCopy.name}`; defDeepCopy.platform = defDeepCopy.platform || "gcfv1"; + defDeepCopy.secretEnvironmentVariables = secretEnvVariables; regionDefinitions.push(defDeepCopy); } diff --git a/src/extensions/emulator/optionsHelper.ts b/src/extensions/emulator/optionsHelper.ts index 8a36a8ed498..5046d0b4fc4 100644 --- a/src/extensions/emulator/optionsHelper.ts +++ b/src/extensions/emulator/optionsHelper.ts @@ -6,13 +6,14 @@ import * as paramHelper from "../paramHelper"; import * as specHelper from "./specHelper"; import * as localHelper from "../localHelper"; import * as triggerHelper from "./triggerHelper"; -import { ExtensionSpec, Resource } from "../extensionsApi"; +import { ExtensionSpec, Param, ParamType, Resource } from "../extensionsApi"; import * as extensionsHelper from "../extensionsHelper"; import { Config } from "../../config"; import { FirebaseError } from "../../error"; import { EmulatorLogger } from "../../emulator/emulatorLogger"; import { needProjectId } from "../../projectUtils"; import { Emulators } from "../../emulator/types"; +import { SecretEnvVar } from "../../deploy/functions/backend"; export async function buildOptions(options: any): Promise { const extDevDir = localHelper.findExtensionYaml(process.cwd()); @@ -47,13 +48,15 @@ export async function buildOptions(options: any): Promise { export async function getExtensionFunctionInfo( extensionDir: string, instanceId: string, - params: Record + paramValues: Record ): Promise<{ nodeMajorVersion: number; extensionTriggers: ParsedTriggerDefinition[]; + nonSecretEnv: Record; + secretEnvVariables: SecretEnvVar[]; }> { const spec = await specHelper.readExtensionYaml(extensionDir); - const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, params); + const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, paramValues); const extensionTriggers: ParsedTriggerDefinition[] = functionResources .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r)) .map((trigger) => { @@ -61,11 +64,59 @@ export async function getExtensionFunctionInfo( return trigger; }); const nodeMajorVersion = specHelper.getNodeVersion(functionResources); + const nonSecretEnv = getNonSecretEnv(spec.params, paramValues); + const secretEnvVariables = getSecretEnvVars(spec.params, paramValues); return { extensionTriggers, nodeMajorVersion, + nonSecretEnv, + secretEnvVariables, }; } +const isSecretParam = (p: Param) => + p.type === extensionsHelper.SpecParamType.SECRET || p.type === ParamType.SECRET; +/** + * getNonSecretEnv checks extension spec for secret params, and returns env without those secret params + * @param params A list of params to check for secret params + * @param paramValues A Record of all params to their values + */ +export function getNonSecretEnv( + params: Param[], + paramValues: Record +): Record { + const getNonSecretEnv: Record = Object.assign({}, paramValues); + const secretParams = params.filter(isSecretParam); + for (const p of secretParams) { + delete getNonSecretEnv[p.param]; + } + return getNonSecretEnv; +} + +/** + * getSecretEnvVars checks which params are secret, and returns a list of SecretEnvVar for each one that is is in use + * @param params A list of params to check for secret params + * @param paramValues A Record of all params to their values + */ +export function getSecretEnvVars( + params: Param[], + paramValues: Record +): SecretEnvVar[] { + const secretEnvVar: SecretEnvVar[] = []; + const secretParams = params.filter(isSecretParam); + for (const s of secretParams) { + if (paramValues[s.param]) { + const [, projectId, , secret, , version] = paramValues[s.param].split("/"); + secretEnvVar.push({ + key: s.param, + secret, + projectId, + version, + }); + } + // TODO: Throw an error if a required secret is missing? + } + return secretEnvVar; +} // Exported for testing export function getParams(options: any, extensionSpec: ExtensionSpec) { diff --git a/src/extensions/emulator/specHelper.ts b/src/extensions/emulator/specHelper.ts index 5120b4a3322..6662676edae 100644 --- a/src/extensions/emulator/specHelper.ts +++ b/src/extensions/emulator/specHelper.ts @@ -3,7 +3,7 @@ import * as _ from "lodash"; import * as path from "path"; import * as fs from "fs-extra"; -import { ExtensionSpec, Resource } from "../extensionsApi"; +import { ExtensionSpec, ParamType, Resource } from "../extensionsApi"; import { FirebaseError } from "../../error"; import { substituteParams } from "../extensionsHelper"; import { parseRuntimeVersion } from "../../emulator/functionsEmulatorUtils"; diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index e7e2083caff..5eb7b826339 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -10,6 +10,7 @@ import { FirebaseError } from "../error"; import { logger } from "../logger"; import * as operationPoller from "../operation-poller"; import * as refs from "./refs"; +import { SpecParamType } from "./extensionsHelper"; const VERSION = "v1beta"; const PAGE_SIZE_MAX = 100; @@ -149,7 +150,7 @@ export interface Param { label: string; description?: string; default?: string; - type?: ParamType; + type?: ParamType | SpecParamType; // TODO(b/224618262): This is SpecParamType when publishing & ParamType when looking at API responses. Choose one. options?: ParamOption[]; required?: boolean; validationRegex?: string; diff --git a/src/serve/functions.ts b/src/serve/functions.ts index 5ed8d9a5d8b..deedf16dbee 100644 --- a/src/serve/functions.ts +++ b/src/serve/functions.ts @@ -38,6 +38,7 @@ export class FunctionsServer { functionsDir, nodeMajorVersion, env: {}, + secretEnv: [], }; // Normally, these two fields are included in args (and typed as such). // However, some poorly-typed tests may not have them and we need to provide diff --git a/src/test/emulators/extensions/validation.spec.ts b/src/test/emulators/extensions/validation.spec.ts index 20fec28ff0c..f79ef27bbdc 100644 --- a/src/test/emulators/extensions/validation.spec.ts +++ b/src/test/emulators/extensions/validation.spec.ts @@ -54,6 +54,7 @@ function getTestEmulatableBackend( return { functionsDir: ".", env: {}, + secretEnv: [], predefinedTriggers, }; } @@ -71,7 +72,7 @@ function getTestParsedTriggerDefinition(args: { }; } -describe("ExtensionsEmulator validation validation", () => { +describe("ExtensionsEmulator validation", () => { describe(`${validation.getUnemulatedAPIs.name}`, () => { const testProjectId = "test-project"; const testAPI = "test.googleapis.com"; diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts index 40e280a5732..c76023e3e59 100644 --- a/src/test/emulators/extensionsEmulator.spec.ts +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -72,6 +72,7 @@ describe("Extensions Emulator", () => { PROJECT_ID: "test-project", STORAGE_BUCKET: "test-project.appspot.com", }, + secretEnv: [], extensionInstanceId: "ext-test", functionsDir: "src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions", diff --git a/src/test/extensions/emulator/optionsHelper.spec.ts b/src/test/extensions/emulator/optionsHelper.spec.ts index f470f310d38..b77d39deee8 100644 --- a/src/test/extensions/emulator/optionsHelper.spec.ts +++ b/src/test/extensions/emulator/optionsHelper.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import * as sinon from "sinon"; import * as optionsHelper from "../../../extensions/emulator/optionsHelper"; -import { ExtensionSpec } from "../../../extensions/extensionsApi"; +import { ExtensionSpec, Param, ParamType } from "../../../extensions/extensionsApi"; import * as paramHelper from "../../../extensions/paramHelper"; describe("optionsHelper", () => { @@ -118,4 +118,60 @@ describe("optionsHelper", () => { }); }); }); + + const TEST_SELECT_PARAM: Param = { + param: "SELECT_PARAM", + label: "A select param", + type: ParamType.SELECT, + }; + const TEST_STRING_PARAM: Param = { + param: "STRING_PARAM", + label: "A string param", + type: ParamType.STRING, + }; + const TEST_MULTISELECT_PARAM: Param = { + param: "MULTISELECT_PARAM", + label: "A multiselect param", + type: ParamType.MULTISELECT, + }; + const TEST_SECRET_PARAM: Param = { + param: "SECRET_PARAM", + label: "A secret param", + type: ParamType.SECRET, + }; + const TEST_PARAMS: Param[] = [ + TEST_SELECT_PARAM, + TEST_STRING_PARAM, + TEST_MULTISELECT_PARAM, + TEST_SECRET_PARAM, + ]; + const TEST_PARAM_VALUES = { + SELECT_PARAM: "select", + STRING_PARAM: "string", + MULTISELECT_PARAM: "multiselect", + SECRET_PARAM: "projects/test/secrets/mysecret/versionms/latest", + }; + + describe("getNonSecretEnv", () => { + it("should return only params that are not secret", () => { + expect(optionsHelper.getNonSecretEnv(TEST_PARAMS, TEST_PARAM_VALUES)).to.deep.equal({ + SELECT_PARAM: "select", + STRING_PARAM: "string", + MULTISELECT_PARAM: "multiselect", + }); + }); + }); + + describe("getSecretEnv", () => { + it("should return only params that are secret", () => { + expect(optionsHelper.getSecretEnvVars(TEST_PARAMS, TEST_PARAM_VALUES)).to.have.deep.members([ + { + projectId: "test", + key: "SECRET_PARAM", + secret: "mysecret", + version: "latest", + }, + ]); + }); + }); }); From 9795181587cc39812e1c3fbe9852aa1ef0bf6840 Mon Sep 17 00:00:00 2001 From: Rich Hodgkins Date: Tue, 15 Mar 2022 17:21:13 +0000 Subject: [PATCH 0149/1699] Storage emulator fixes and added rewriteTo support (#3647) * Added missing return type * Set default cache-control header * Try to derive eTag from file bytes * Use content-encoding from metadata * Added in other file headers * Improved comment * Extracted out crc32c encoding to separate function * Added in x-goog-hash header with encoded crc32c value and MD5 hash * Match 404 object errors to API * Fixed range requests Simplified to use express' Request#range method and corrected end to be used as an inclusive bound * When STORAGE_EMULATOR_DEBUG env is set, still return 501 status code * Added support for rewriteTo and copyTo in the storage emulator * Added contentDisposition into metadata * Convert custom metadata values to strings and added integration test to admin SDK for removing values * Match 404 object errors to API for non-GET endpoints * Changed to use declared imports to match other fs usage * No need to check for directory existence before trying to create it when using the recursive flag * Stored files flat inside bucket directory, and URL encode the path components * Fixed typo in skipped test * Corrected list objects endpoint when using prefix and delimiter to return correct items and include prefixes * Support for pageTokens and maxResults in objects: list * Refactored Firebase storage#listAll endpoint to call generic listItems * #upload test to check provided custom metadata is set * Better tests for #copy to check original files metadata is copied * Updated #copy() to overwrite all custom metadata, not only provided keys * Updated #copy() to convert null custom metadata values to empty strings * Refactored objects: rewrite / copy endpoint to method in storage layer * Handle firebaseStorageDownloadTokens for objects: rewrite / copy * Consistent case for constant test variable * Changed iterating over objects with Object.keys to use Object.entries * Clearer circumventing the type-system * Base eTags on object generation and metadata generation for uniqueness across objects regardless of file content * Refactored StorageLayer#listItemsAndPrefixes to use #listItems and moved specfic Firebase and gcloud formats to those APIs * Removed unused import * Handled cache control and content language metadata and added tests * Integration tests for File#exists * Updated CHANGELOG * Comment on boolean argument * Fixed end to end test storage import/export --- CHANGELOG.md | 5 + scripts/storage-emulator-integration/tests.ts | 591 +++++++++++++++++- src/emulator/storage/apis/firebase.ts | 25 +- src/emulator/storage/apis/gcloud.ts | 119 +++- src/emulator/storage/crc.ts | 9 + src/emulator/storage/files.ts | 223 ++++--- src/emulator/storage/metadata.ts | 31 +- src/emulator/storage/persistence.ts | 29 +- 8 files changed, 845 insertions(+), 187 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f264c6eca62..492028b8072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,8 @@ - Adds alerting event provider (#4258). - Fixes bug where project-specific environment variables weren't loaded by the Functions Emulator (#4273). - Fixes bug where CORS was enabled too broadly on the Functions emulator (#4294). +- Adds `rewriteTo` / `copyTo` endpoints to Cloud Storage Emulator (#3647, #3751). +- Addes `createReadStream` support to Cloud Storage Emulator (#3469). +- Fixes `list` endpoint in Cloud Storage Emulator (#3647). +- Fixes `getFiles` for Cloud Storage Emulator (#3778). +- Fixes `exists` for Cloud Storage Emulator (#3764). diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index a62b37ac9a9..eea69d1f59b 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -9,7 +9,7 @@ import * as puppeteer from "puppeteer"; import * as request from "request"; import * as crypto from "crypto"; import * as os from "os"; -import { Bucket, Storage } from "@google-cloud/storage"; +import { Bucket, Storage, CopyOptions } from "@google-cloud/storage"; import supertest = require("supertest"); import { IMAGE_FILE_BASE64 } from "../../src/test/emulators/fixtures"; @@ -265,24 +265,250 @@ describe("Storage emulator", () => { // TODO(abehaskins): This test is temporarily disabled due to a credentials issue it.skip("should handle large (resumable) uploads", async () => { - await testBucket.upload(largeFilePath), - { - resumable: true, - }; + await testBucket.upload(largeFilePath, { resumable: true }); + }); + + it("should upload with provided metadata", async () => { + const metadata = { + contentDisposition: "attachment", + cacheControl: "private,max-age=30", + contentLanguage: "de-DE", + metadata: { foo: "bar" }, + }; + const [, fileMetadata] = await testBucket.upload(smallFilePath, { + resumable: false, + metadata, + }); + + expect(fileMetadata).to.deep.include(metadata); + }); + + it("should be able to upload file named 'prefix/file.txt' when file named 'prefix' already exists", async () => { + await testBucket.upload(smallFilePath, { + destination: "prefix", + }); + await testBucket.upload(smallFilePath, { + destination: "prefix/file.txt", + }); + }); + + it("should be able to upload file named 'prefix' when file named 'prefix/file.txt' already exists", async () => { + await testBucket.upload(smallFilePath, { + destination: "prefix/file.txt", + }); + await testBucket.upload(smallFilePath, { + destination: "prefix", + }); }); }); describe("#getFiles()", () => { - it("should list files", async () => { - await testBucket.upload(smallFilePath, { - destination: "testing/shoveler.svg", + const TESTING_FILE = "testing/shoveler.svg"; + const PREFIX_FILE = "prefix"; + const PREFIX_1_FILE = PREFIX_FILE + "/1.txt"; + const PREFIX_2_FILE = PREFIX_FILE + "/2.txt"; + const PREFIX_SUB_DIRECTORY_FILE = PREFIX_FILE + "/dir/file.txt"; + + beforeEach(async () => { + await Promise.all( + [ + TESTING_FILE, + PREFIX_FILE, + PREFIX_1_FILE, + PREFIX_2_FILE, + PREFIX_SUB_DIRECTORY_FILE, + ].map(async (f) => { + await testBucket.upload(smallFilePath, { + destination: f, + }); + }) + ); + }); + + it("should list all files in bucket", async () => { + // This is only test that uses autoPagination as the other tests look at the prefixes response + const [files] = await testBucket.getFiles(); + + expect(files.map((file) => file.name)).to.deep.equal([ + PREFIX_FILE, + PREFIX_1_FILE, + PREFIX_2_FILE, + PREFIX_SUB_DIRECTORY_FILE, + TESTING_FILE, + ]); + }); + + it("should list all files in bucket using maxResults and pageToken", async () => { + const [files1, , { nextPageToken: nextPageToken1 }] = await testBucket.getFiles({ + maxResults: 3, }); - const [files, prefixes] = await testBucket.getFiles({ - directory: "testing", + + expect(nextPageToken1).to.be.a("string").and.not.empty; + expect(files1.map((file) => file.name)).to.deep.equal([ + PREFIX_FILE, + PREFIX_1_FILE, + PREFIX_2_FILE, + ]); + + const [files2, , { nextPageToken: nextPageToken2 }] = await testBucket.getFiles({ + maxResults: 3, + pageToken: nextPageToken1, + }); + + expect(nextPageToken2).to.be.undefined; + expect(files2.map((file) => file.name)).to.deep.equal([ + PREFIX_SUB_DIRECTORY_FILE, + TESTING_FILE, + ]); + }); + + it("should list files with prefix", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "prefix", + }); + + expect(prefixes).to.be.undefined; + expect(files.map((file) => file.name)).to.deep.equal([ + PREFIX_FILE, + PREFIX_1_FILE, + PREFIX_2_FILE, + PREFIX_SUB_DIRECTORY_FILE, + ]); + }); + + it("should list files using common delimiter", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + delimiter: "/", + }); + + expect(prefixes).to.be.deep.equal(["prefix/", "testing/"]); + expect(files.map((file) => file.name)).to.deep.equal([PREFIX_FILE]); + }); + + it("should list files using other delimiter", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + delimiter: "dir", + }); + + expect(prefixes).to.be.deep.equal(["prefix/dir"]); + expect(files.map((file) => file.name)).to.deep.equal([ + PREFIX_FILE, + PREFIX_1_FILE, + PREFIX_2_FILE, + TESTING_FILE, + ]); + }); + + it("should list files using same prefix and delimiter of p", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "p", + delimiter: "p", }); expect(prefixes).to.be.undefined; - expect(files.map((file) => file.name)).to.deep.equal(["testing/shoveler.svg"]); + expect(files.map((file) => file.name)).to.deep.equal([ + PREFIX_FILE, + PREFIX_1_FILE, + PREFIX_2_FILE, + PREFIX_SUB_DIRECTORY_FILE, + ]); + }); + + it("should list files using same prefix and delimiter of t", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "t", + delimiter: "t", + }); + + expect(prefixes).to.be.deep.equal(["test"]); + expect(files.map((file) => file.name)).to.be.empty; + }); + + it("should list files using prefix=p and delimiter=t", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "p", + delimiter: "t", + }); + + expect(prefixes).to.be.deep.equal(["prefix/1.t", "prefix/2.t", "prefix/dir/file.t"]); + expect(files.map((file) => file.name)).to.deep.equal([PREFIX_FILE]); + }); + + it("should list files in sub-directory (using prefix and delimiter)", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "prefix/", + delimiter: "/", + }); + + expect(prefixes).to.be.deep.equal(["prefix/dir/"]); + expect(files.map((file) => file.name)).to.deep.equal([PREFIX_1_FILE, PREFIX_2_FILE]); + }); + + it("should list files in sub-directory (using prefix)", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "prefix/", + }); + + expect(prefixes).to.be.undefined; + expect(files.map((file) => file.name)).to.deep.equal([ + PREFIX_1_FILE, + PREFIX_2_FILE, + PREFIX_SUB_DIRECTORY_FILE, + ]); + }); + + it("should list files in sub-directory (using directory)", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + directory: "testing/", + }); + + expect(prefixes).to.be.undefined; + expect(files.map((file) => file.name)).to.deep.equal([TESTING_FILE]); + }); + + it("should list no files for unused prefix", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "blah/", + }); + + expect(prefixes).to.be.undefined; + expect(files).to.be.empty; + }); + + it("should list files using prefix=pref and delimiter=i", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "pref", + delimiter: "i", + }); + + expect(prefixes).to.be.deep.equal(["prefi"]); + expect(files).to.be.empty; + }); + + it("should list files using prefix=prefi and delimiter=i", async () => { + const [files, , { prefixes }] = await testBucket.getFiles({ + autoPaginate: false, + prefix: "prefi", + delimiter: "i", + }); + + expect(prefixes).to.be.deep.equal(["prefix/di"]); + expect(files.map((file) => file.name)).to.deep.equal([ + PREFIX_FILE, + PREFIX_1_FILE, + PREFIX_2_FILE, + ]); }); }); }); @@ -316,6 +542,39 @@ describe("Storage emulator", () => { }); }); + describe("#exists()", () => { + it("should return false for a file that does not exist", async () => { + // Ensure that the file exists on the bucket before deleting it + const [exists] = await testBucket.file("no-file").exists(); + expect(exists).to.equal(false); + }); + + it("should return true for a file that exists", async () => { + // We use a nested path to ensure that we don't need to decode + // the objectId in the gcloud emulator API + const bucketFilePath = "file/to/exists"; + await testBucket.upload(smallFilePath, { + destination: bucketFilePath, + }); + + const [exists] = await testBucket.file(bucketFilePath).exists(); + expect(exists).to.equal(true); + }); + + it("should return false when called on a directory containing files", async () => { + // We use a nested path to ensure that we don't need to decode + // the objectId in the gcloud emulator API + const path = "file/to"; + const bucketFilePath = path + "/exists"; + await testBucket.upload(smallFilePath, { + destination: bucketFilePath, + }); + + const [exists] = await testBucket.file(path).exists(); + expect(exists).to.equal(false); + }); + }); + describe("#delete()", () => { it("should delete a file from the bucket", async () => { // We use a nested path to ensure that we don't need to decode @@ -338,6 +597,15 @@ describe("Storage emulator", () => { const [existsAfter] = await toDeleteFile.exists(); expect(existsAfter).to.equal(false); }); + + it("should throw 404 object error for file not found", async () => { + await expect(testBucket.file("blah").delete()) + .to.be.eventually.rejectedWith(`No such object: ${storageBucket}/blah`) + .and.nested.include({ + code: 404, + "errors[0].reason": "notFound", + }); + }); }); describe("#download()", () => { @@ -350,6 +618,261 @@ describe("Storage emulator", () => { const actualContent = fs.readFileSync(smallFilePath); expect(downloadContent).to.deep.equal(actualContent); }); + + it("should return partial content of the file", async () => { + await testBucket.upload(smallFilePath); + const [downloadContent] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + // Request 10 bytes (range requests are inclusive) + .download({ start: 10, end: 19 }); + + const actualContent = fs.readFileSync(smallFilePath).slice(10, 20); + expect(downloadContent).to.have.lengthOf(10).and.deep.equal(actualContent); + }); + + it("should throw 404 error for file not found", async () => { + const err = (await expect( + testBucket.file("blah").download() + ).to.be.eventually.rejectedWith(`No such object: ${storageBucket}/blah`)) as Error; + + expect(err).to.have.property("code", 404); + expect(err).not.have.nested.property("errors[0]"); + }); + }); + + describe("#copy()", () => { + const COPY_DESTINATION_FILENAME = "copied_file"; + + it("should copy the file", async () => { + await testBucket.upload(smallFilePath); + + const file = testBucket.file(COPY_DESTINATION_FILENAME); + const [, resp] = await testBucket.file(smallFilePath.split("/").slice(-1)[0]).copy(file); + + expect(resp) + .to.have.all.keys(["kind", "totalBytesRewritten", "objectSize", "done", "resource"]) + .and.include({ + kind: "storage#rewriteResponse", + totalBytesRewritten: String(SMALL_FILE_SIZE), + objectSize: String(SMALL_FILE_SIZE), + done: true, + }); + + const [copiedContent] = await file.download(); + + const actualContent = fs.readFileSync(smallFilePath); + expect(copiedContent).to.deep.equal(actualContent); + }); + + it("should copy the file to a different bucket", async () => { + await testBucket.upload(smallFilePath); + + const otherBucket = testBucket.storage.bucket("other-bucket"); + const file = otherBucket.file(COPY_DESTINATION_FILENAME); + const [, { resource: metadata }] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .copy(file); + + expect(metadata).to.have.property("bucket", otherBucket.name); + + const [copiedContent] = await file.download(); + + const actualContent = fs.readFileSync(smallFilePath); + expect(copiedContent).to.deep.equal(actualContent); + }); + + it("should return the metadata of the destination file", async () => { + await testBucket.upload(smallFilePath); + + const file = testBucket.file(COPY_DESTINATION_FILENAME); + const [, { resource: actualMetadata }] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .copy(file); + + const [expectedMetadata] = await file.getMetadata(); + expect(actualMetadata).to.deep.equal(expectedMetadata); + }); + + it("should copy the file preserving the original metadata", async () => { + const [, source] = await testBucket.upload(smallFilePath, { + metadata: { + cacheControl: "private,no-store", + metadata: { + hello: "world", + }, + }, + }); + + const file = testBucket.file(COPY_DESTINATION_FILENAME); + await testBucket.file(smallFilePath.split("/").slice(-1)[0]).copy(file); + + const [metadata] = await file.getMetadata(); + + expect(metadata).to.have.all.keys(source).and.deep.include({ + bucket: source.bucket, + contentType: source.contentType, + crc32c: source.crc32c, + cacheControl: source.cacheControl, + metadata: source.metadata, + }); + + const metadataTypes: { [s: string]: string } = {}; + + for (const key in metadata) { + if (metadata[key]) { + metadataTypes[key] = typeof metadata[key]; + } + } + + expect(metadataTypes).to.deep.equal({ + bucket: "string", + contentType: "string", + contentDisposition: "string", + generation: "string", + md5Hash: "string", + crc32c: "string", + cacheControl: "string", + etag: "string", + metageneration: "string", + storageClass: "string", + name: "string", + size: "string", + timeCreated: "string", + updated: "string", + id: "string", + kind: "string", + mediaLink: "string", + selfLink: "string", + timeStorageClassUpdated: "string", + metadata: "object", + }); + }); + + it("should copy the file and overwrite with the provided custom metadata", async () => { + const [, source] = await testBucket.upload(smallFilePath, { + metadata: { + cacheControl: "private,no-store", + metadata: { + hello: "world", + }, + }, + }); + + const file = testBucket.file(COPY_DESTINATION_FILENAME); + const metadata = { foo: "bar" }; + const cacheControl = "private,max-age=10,immutable"; + // Types for CopyOptions are wrong (@google-cloud/storage sub-dependency needs + // update to include https://github.com/googleapis/nodejs-storage/pull/1406 + // and https://github.com/googleapis/nodejs-storage/pull/1426) + const copyOpts: CopyOptions & { [key: string]: unknown } = { + metadata, + cacheControl, + }; + const [, { resource: metadata1 }] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .copy(file, copyOpts); + + expect(metadata1).to.deep.include({ + bucket: source.bucket, + contentType: source.contentType, + crc32c: source.crc32c, + metadata, + cacheControl, + }); + + // Also double check with a new metadata fetch + const [metadata2] = await file.getMetadata(); + expect(metadata2).to.deep.equal(metadata1); + }); + + it("should set null custom metadata values to empty strings", async () => { + const [, source] = await testBucket.upload(smallFilePath); + + const file = testBucket.file(COPY_DESTINATION_FILENAME); + const metadata = { foo: "bar", nullMetadata: null }; + const cacheControl = "private,max-age=10,immutable"; + // Types for CopyOptions are wrong (@google-cloud/storage sub-dependency needs + // update to include https://github.com/googleapis/nodejs-storage/pull/1406 + // and https://github.com/googleapis/nodejs-storage/pull/1426) + const copyOpts: CopyOptions & { [key: string]: unknown } = { + metadata, + cacheControl, + }; + const [, { resource: metadata1 }] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .copy(file, copyOpts); + + expect(metadata1).to.deep.include({ + bucket: source.bucket, + contentType: source.contentType, + crc32c: source.crc32c, + metadata: { + foo: "bar", + // Sets null metadata values to empty strings + nullMetadata: "", + }, + cacheControl, + }); + + // Also double check with a new metadata fetch + const [metadata2] = await file.getMetadata(); + expect(metadata2).to.deep.equal(metadata1); + }); + + it("should preserve firebaseStorageDownloadTokens", async () => { + const firebaseStorageDownloadTokens = "token1,token2"; + await testBucket.upload(smallFilePath, { + metadata: { + metadata: { + firebaseStorageDownloadTokens, + }, + }, + }); + + const file = testBucket.file(COPY_DESTINATION_FILENAME); + const [, { resource: metadata }] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .copy(file); + + expect(metadata).to.deep.include({ + metadata: { + firebaseStorageDownloadTokens, + }, + }); + }); + + it("should remove firebaseStorageDownloadTokens when overwriting custom metadata", async () => { + await testBucket.upload(smallFilePath, { + metadata: { + metadata: { + firebaseStorageDownloadTokens: "token1,token2", + }, + }, + }); + + const file = testBucket.file(COPY_DESTINATION_FILENAME); + const metadata = { foo: "bar" }; + // Types for CopyOptions are wrong (@google-cloud/storage sub-dependency needs + // update to include https://github.com/googleapis/nodejs-storage/pull/1406 + // and https://github.com/googleapis/nodejs-storage/pull/1426) + const copyOpts: CopyOptions & { [key: string]: unknown } = { + metadata, + }; + const [, { resource: metadataOut }] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .copy(file, copyOpts); + + expect(metadataOut).to.deep.include({ metadata }); + }); + + it("should not support the use of a rewriteToken", async () => { + await testBucket.upload(smallFilePath); + + const file = testBucket.file(COPY_DESTINATION_FILENAME); + await expect( + testBucket.file(smallFilePath.split("/").slice(-1)[0]).copy(file, { token: "foo-bar" }) + ).to.eventually.be.rejected.and.have.property("code", 501); + }); }); describe("#makePublic()", () => { @@ -428,9 +951,11 @@ describe("Storage emulator", () => { expect(metadataTypes).to.deep.equal({ bucket: "string", contentType: "string", + contentDisposition: "string", generation: "string", md5Hash: "string", crc32c: "string", + cacheControl: "string", etag: "string", metageneration: "string", storageClass: "string", @@ -500,6 +1025,15 @@ describe("Storage emulator", () => { incomingMetadata.metadata.firebaseStorageDownloadTokens ); }); + + it("should throw 404 object error for file not found", async () => { + await expect(testBucket.file("blah").getMetadata()) + .to.be.eventually.rejectedWith(`No such object: ${storageBucket}/blah`) + .and.nested.include({ + code: 404, + "errors[0].reason": "notFound", + }); + }); }); describe("#setMetadata()", () => { @@ -533,9 +1067,11 @@ describe("Storage emulator", () => { expect(metadataTypes).to.deep.equal({ bucket: "string", contentType: "string", + contentDisposition: "string", generation: "string", md5Hash: "string", crc32c: "string", + cacheControl: "string", etag: "string", metageneration: "string", storageClass: "string", @@ -578,6 +1114,38 @@ describe("Storage emulator", () => { expect(metadata.metadata.is_over).to.equal("9000"); }); + it("should convert non-string fields under .metadata to strings", async () => { + await testBucket.upload(smallFilePath); + const [metadata] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .setMetadata({ metadata: { booleanValue: true, numberValue: -1 } }); + + expect(metadata.metadata).to.deep.equal({ + booleanValue: "true", + numberValue: "-1", + }); + }); + + it("should remove fields under .metadata when setting to null", async () => { + await testBucket.upload(smallFilePath); + const [metadata1] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .setMetadata({ metadata: { foo: "bar", hello: "world" } }); + + expect(metadata1.metadata).to.deep.equal({ + foo: "bar", + hello: "world", + }); + + const [metadata2] = await testBucket + .file(smallFilePath.split("/").slice(-1)[0]) + .setMetadata({ metadata: { foo: null } }); + + expect(metadata2.metadata).to.deep.equal({ + hello: "world", + }); + }); + it("should ignore any unknown fields", async () => { await testBucket.upload(smallFilePath); const [metadata] = await testBucket @@ -1098,6 +1666,7 @@ describe("Storage emulator", () => { contentDisposition: "string", contentEncoding: "string", contentType: "string", + cacheControl: "string", fullPath: "string", generation: "string", md5Hash: "string", diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 9dac1dc732b..98338784ff2 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -126,17 +126,20 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { res.setHeader("Content-Type", metadata.contentType); setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined }); - const byteRange = [...(req.header("range") || "").split("bytes="), "", ""]; - const [rangeStart, rangeEnd] = byteRange[1].split("-"); - if (rangeStart) { - const range = { - start: parseInt(rangeStart), - end: rangeEnd ? parseInt(rangeEnd) : data.byteLength, - }; - res.setHeader("Content-Range", `bytes ${range.start}-${range.end - 1}/${data.byteLength}`); - return res.status(206).end(data.slice(range.start, range.end)); + const byteRange = req.range(data.byteLength, { combine: true }); + + if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) { + const range = byteRange[0]; + res.setHeader( + "Content-Range", + `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}` + ); + // Byte range requests are inclusive for start and end + res.status(206).end(data.slice(range.start, range.end + 1)); + } else { + res.end(data); } - return res.end(data); + return; } // Object metadata request @@ -151,7 +154,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { response = await storageLayer.handleListObjects({ bucketId: req.params.bucketId, prefix: req.query.prefix ? req.query.prefix.toString() : "", - delimiter: req.query.delimiter ? req.query.delimiter.toString() : "/", + delimiter: req.query.delimiter ? req.query.delimiter.toString() : "", pageToken: req.query.pageToken?.toString(), maxResults: maxResults ? +maxResults : undefined, authorization: req.header("authorization"), diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index e4b93612d3b..fdbb01f3cd2 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -10,7 +10,8 @@ import { import { EmulatorRegistry } from "../../registry"; import { StorageEmulator } from "../index"; import { EmulatorLogger } from "../../emulatorLogger"; -import { GetObjectResponse, StorageLayer } from "../files"; +import { GetObjectResponse } from "../files"; +import { crc32cToString } from "../crc"; import type { Request, Response } from "express"; import { parseObjectUploadMultipartRequest } from "../multipart"; import { Upload, UploadNotActiveError } from "../upload"; @@ -52,7 +53,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { ); } catch (err) { if (err instanceof NotFoundError) { - return res.sendStatus(404); + return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { throw new Error("Request failed unexpectedly due to Firebase Rules."); @@ -80,7 +81,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { ); } catch (err) { if (err instanceof NotFoundError) { - return res.sendStatus(404); + return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { throw new Error("Request failed unexpectedly due to Firebase Rules."); @@ -96,7 +97,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { if (req.query.maxResults) { maxRes = +req.query.maxResults.toString(); } - const delimiter = req.query.delimiter ? req.query.delimiter.toString() : "/"; + const delimiter = req.query.delimiter ? req.query.delimiter.toString() : ""; const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined; const prefix = req.query.prefix ? req.query.prefix.toString() : ""; @@ -107,7 +108,8 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { pageToken, maxRes ); - res.json(listResult); + + res.json({ ...listResult, kind: "#storage/objects" }); }); gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { @@ -121,7 +123,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { ); } catch (err) { if (err instanceof NotFoundError) { - return res.sendStatus(404); + return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { throw new Error("Request failed unexpectedly due to Firebase Rules."); @@ -196,7 +198,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { ); } catch (err) { if (err instanceof NotFoundError) { - return res.sendStatus(404); + return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { throw new Error("Request failed unexpectedly due to Firebase Rules."); @@ -303,7 +305,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { ); } catch (err) { if (err instanceof NotFoundError) { - return res.sendStatus(404); + return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { throw new Error("Request failed unexpectedly due to Firebase Rules."); @@ -313,11 +315,58 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res); }); + gcloudStorageAPI.post( + "/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", + (req, res, next) => { + const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); + + if (!md) { + return sendObjectNotFound(req, res); + } + + if (req.params.method === "rewriteTo" && req.query.rewriteToken) { + // Don't yet support multi-request copying + return next(); + } + + const metadata = storageLayer.copyFile( + md, + req.params.destBucketId, + req.params.destObjectId, + req.body + ); + + if (!metadata) { + res.sendStatus(400); + return; + } + + const resource = new CloudStorageObjectMetadata(metadata); + + res.status(200); + if (req.params.method === "copyTo") { + // See https://cloud.google.com/storage/docs/json_api/v1/objects/copy#response + return res.json(resource); + } else if (req.params.method === "rewriteTo") { + // See https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite#response + return res.json({ + kind: "storage#rewriteResponse", + totalBytesRewritten: String(metadata.size), + objectSize: String(metadata.size), + done: true, + resource, + }); + } else { + return next(); + } + } + ); + gcloudStorageAPI.all("/**", (req, res) => { if (process.env.STORAGE_EMULATOR_DEBUG) { console.table(req.headers); console.log(req.method, req.url); - res.json("endpoint not implemented"); + res.status(501).json("endpoint not implemented"); } else { res.sendStatus(501); } @@ -326,7 +375,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return gcloudStorageAPI; } -function sendFileBytes(md: StoredFileMetadata, data: Buffer, req: Request, res: Response) { +function sendFileBytes(md: StoredFileMetadata, data: Buffer, req: Request, res: Response): void { const isGZipped = md.contentEncoding === "gzip"; if (isGZipped) { data = gunzipSync(data); @@ -335,20 +384,48 @@ function sendFileBytes(md: StoredFileMetadata, data: Buffer, req: Request, res: res.setHeader("Accept-Ranges", "bytes"); res.setHeader("Content-Type", md.contentType); res.setHeader("Content-Disposition", md.contentDisposition); - res.setHeader("Content-Encoding", "identity"); + res.setHeader("Content-Encoding", md.contentEncoding); + res.setHeader("ETag", md.etag); + res.setHeader("Cache-Control", md.cacheControl); + res.setHeader("x-goog-generation", `${md.generation}`); + res.setHeader("x-goog-metadatageneration", `${md.metageneration}`); + res.setHeader("x-goog-storage-class", md.storageClass); + res.setHeader("x-goog-hash", `crc32c=${crc32cToString(md.crc32c)},md5=${md.md5Hash}`); - const byteRange = [...(req.header("range") || "").split("bytes="), "", ""]; + const byteRange = req.range(data.byteLength, { combine: true }); - const [rangeStart, rangeEnd] = byteRange[1].split("-"); - - if (rangeStart) { - const range = { - start: parseInt(rangeStart), - end: rangeEnd ? parseInt(rangeEnd) : data.byteLength, - }; - res.setHeader("Content-Range", `bytes ${range.start}-${range.end - 1}/${data.byteLength}`); - res.status(206).end(data.slice(range.start, range.end)); + if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) { + const range = byteRange[0]; + res.setHeader( + "Content-Range", + `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}` + ); + // Byte range requests are inclusive for start and end + res.status(206).end(data.slice(range.start, range.end + 1)); } else { res.end(data); } } + +/** Sends 404 matching API */ +function sendObjectNotFound(req: Request, res: Response): void { + res.status(404); + const message = `No such object: ${req.params.bucketId}/${req.params.objectId}`; + if (req.method === "GET" && req.query.alt === "media") { + res.send(message); + } else { + res.json({ + error: { + code: 404, + message, + errors: [ + { + message, + domain: "global", + reason: "notFound", + }, + ], + }, + }); + } +} diff --git a/src/emulator/storage/crc.ts b/src/emulator/storage/crc.ts index deed2c336d0..093d1308efd 100644 --- a/src/emulator/storage/crc.ts +++ b/src/emulator/storage/crc.ts @@ -40,3 +40,12 @@ export function crc32c(bytes: Buffer): number { return (crc ^ -1) >>> 0; } + +/** + * Adapted from: + * - https://github.com/googleapis/nodejs-storage/blob/0c1fa3934a52a608366a8c6c798c43516dd03dbf/src/file.ts#L1406-L1409 + */ +export function crc32cToString(crc32cValue: number | string): string { + // Does the reverse of https://stackoverflow.com/q/25096737/849645 + return "----" + Buffer.from([crc32cValue]).toString("base64"); +} diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 6d4a9965e24..0d68a529cc9 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -1,3 +1,6 @@ +import { existsSync, readFileSync, readdirSync, statSync } from "fs"; +import { tmpdir } from "os"; +import { v4 } from "uuid"; import { ListItem, ListResponse } from "./list"; import { CloudStorageBucketMetadata, @@ -7,7 +10,6 @@ import { } from "./metadata"; import { NotFoundError, ForbiddenError } from "./errors"; import * as path from "path"; -import * as fs from "fs"; import * as fse from "fs-extra"; import { StorageCloudFunctions } from "./cloudFunctions"; import { logger } from "../../logger"; @@ -300,7 +302,10 @@ export class StorageLayer { name: upload.objectId, bucket: upload.bucketId, contentType: upload.metadata.contentType || "application/octet-stream", + contentDisposition: upload.metadata.contentDisposition, contentEncoding: upload.metadata.contentEncoding, + contentLanguage: upload.metadata.contentLanguage, + cacheControl: upload.metadata.cacheControl, customMetadata: upload.metadata.metadata, }, this._cloudFunctions, @@ -327,6 +332,61 @@ export class StorageLayer { return metadata; } + public copyFile( + sourceFile: StoredFileMetadata, + destinationBucket: string, + destinationObject: string, + incomingMetadata?: IncomingMetadata + ): StoredFileMetadata { + const filePath = this.path(destinationBucket, destinationObject); + + this._persistence.deleteFile(filePath, /* failSilently = */ true); + + const bytes = this.getBytes(sourceFile.bucket, sourceFile.name) as Buffer; + this._persistence.appendBytes(filePath, bytes); + + const newMetadata: IncomingMetadata = { + ...sourceFile, + metadata: sourceFile.customMetadata, + ...incomingMetadata, + }; + if ( + sourceFile.downloadTokens.length && + // Only copy download tokens if we're not overwriting any custom metadata + !(incomingMetadata?.metadata && Object.keys(incomingMetadata?.metadata).length) + ) { + if (!newMetadata.metadata) newMetadata.metadata = {}; + newMetadata.metadata.firebaseStorageDownloadTokens = sourceFile.downloadTokens.join(","); + } + if (newMetadata.metadata) { + // Convert null metadata values to empty strings + for (const [k, v] of Object.entries(newMetadata.metadata)) { + if (v === null) newMetadata.metadata[k] = ""; + } + } + + const copiedFileMetadata = new StoredFileMetadata( + { + name: destinationObject, + bucket: destinationBucket, + contentType: newMetadata.contentType || "application/octet-stream", + contentDisposition: newMetadata.contentDisposition, + contentEncoding: newMetadata.contentEncoding, + contentLanguage: newMetadata.contentLanguage, + cacheControl: newMetadata.cacheControl, + customMetadata: newMetadata.metadata, + }, + this._cloudFunctions, + bytes, + incomingMetadata + ); + const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(filePath)); + this._files.set(filePath, file); + + this._cloudFunctions.dispatch("finalize", new CloudStorageObjectMetadata(file.metadata)); + return file.metadata; + } + /** * Lists all files and prefixes (folders) at a path. * @throws {ForbiddenError} if the request is not authorized. @@ -346,65 +406,72 @@ export class StorageLayer { if (!authorized) { throw new ForbiddenError(); } - return this.listItemsAndPrefixes( + const itemsResults = this.listItems( request.bucketId, request.prefix, request.delimiter, request.pageToken, request.maxResults ); + return new ListResponse( + itemsResults.prefixes ?? [], + itemsResults.items?.map((i) => new ListItem(i.name, i.bucket)) ?? [], + itemsResults.nextPageToken + ); } - private listItemsAndPrefixes( + public listItems( bucket: string, prefix: string, delimiter: string, pageToken: string | undefined, maxResults: number | undefined - ): ListResponse { - if (!delimiter) { - delimiter = "/"; - } - - if (!prefix.endsWith(delimiter)) { - prefix += delimiter; - } - - if (!prefix.startsWith(delimiter)) { - prefix = delimiter + prefix; - } - - let items = []; + ): { + prefixes?: string[]; + items?: CloudStorageObjectMetadata[]; + nextPageToken?: string; + } { + let items: Array = []; const prefixes = new Set(); for (const [, file] of this._files) { if (file.metadata.bucket !== bucket) { continue; } - let name = `${delimiter}${file.metadata.name}`; + const name = file.metadata.name; if (!name.startsWith(prefix)) { continue; } - name = name.substring(prefix.length); - if (name.startsWith(delimiter)) { - name = name.substring(prefix.length); + let includeMetadata = true; + if (delimiter) { + const delimiterIdx = name.indexOf(delimiter); + const delimiterAfterPrefixIdx = name.indexOf(delimiter, prefix.length); + // items[] contains object metadata for objects whose names do not contain delimiter, or whose names only have instances of delimiter in their prefix. + includeMetadata = delimiterIdx === -1 || delimiterAfterPrefixIdx === -1; + if (delimiterAfterPrefixIdx !== -1) { + // prefixes[] contains truncated object names for objects whose names contain delimiter after any prefix. Object names are truncated beyond the first applicable instance of the delimiter. + prefixes.add(name.slice(0, delimiterAfterPrefixIdx + delimiter.length)); + } } - const startAtIndex = name.indexOf(delimiter); - if (startAtIndex === -1) { - if (!file.metadata.name.endsWith("/")) { - items.push(file.metadata.name); - } - } else { - const prefixPath = prefix + name.substring(0, startAtIndex + 1); - prefixes.add(prefixPath); + if (includeMetadata) { + items.push(file.metadata); } } - items.sort(); + // Order items by name + items.sort((a, b) => { + if (a.name === b.name) { + return 0; + } else if (a.name < b.name) { + return -1; + } else { + return 1; + } + }); if (pageToken) { - const idx = items.findIndex((v) => v === pageToken); + const idx = items.findIndex((v) => v.name === pageToken); if (idx !== -1) { items = items.slice(idx); } @@ -416,77 +483,15 @@ export class StorageLayer { let nextPageToken = undefined; if (items.length > maxResults) { - nextPageToken = items[maxResults]; + nextPageToken = items[maxResults].name; items = items.slice(0, maxResults); } - return new ListResponse( - [...prefixes].sort(), - items.map((i) => new ListItem(i, bucket)), - nextPageToken - ); - } - - public listItems( - bucket: string, - prefix: string, - delimiter: string, - pageToken: string | undefined, - maxResults: number | undefined - ) { - if (!delimiter) { - delimiter = "/"; - } - - if (!prefix) { - prefix = ""; - } - - if (!prefix.endsWith(delimiter)) { - prefix += delimiter; - } - - let items = []; - for (const [, file] of this._files) { - if (file.metadata.bucket !== bucket) { - continue; - } - - let name = file.metadata.name; - if (!name.startsWith(prefix)) { - continue; - } - - name = name.substring(prefix.length); - if (name.startsWith(delimiter)) { - name = name.substring(prefix.length); - } - - items.push(this.path(file.metadata.bucket, file.metadata.name)); - } - - items.sort(); - if (pageToken) { - const idx = items.findIndex((v) => v === pageToken); - if (idx !== -1) { - items = items.slice(idx); - } - } - - if (!maxResults) { - maxResults = 1000; - } - return { - kind: "#storage/objects", - items: items.map((item) => { - const storedFile = this._files.get(item); - if (!storedFile) { - return console.warn(`No file ${item}`); - } - - return new CloudStorageObjectMetadata(storedFile.metadata); - }), + nextPageToken, + prefixes: prefixes.size > 0 ? [...prefixes].sort() : undefined, + items: + items.length > 0 ? items.map((item) => new CloudStorageObjectMetadata(item)) : undefined, }; } @@ -521,10 +526,7 @@ export class StorageLayer { } private path(bucket: string, object: string): string { - const directory = path.dirname(object); - const filename = path.basename(object) + (object.endsWith("/") ? "/" : ""); - - return path.join(bucket, directory, encodeURIComponent(filename)); + return path.join(bucket, object); } public get dirPath(): string { @@ -557,10 +559,8 @@ export class StorageLayer { await fse.ensureDir(metadataDirPath); for await (const [p, file] of this._files.entries()) { - const metadataExportPath = path.join(metadataDirPath, p) + ".json"; - const metadataExportDirPath = path.dirname(metadataExportPath); + const metadataExportPath = path.join(metadataDirPath, encodeURIComponent(p)) + ".json"; - await fse.ensureDir(metadataExportDirPath); await fse.writeFile(metadataExportPath, StoredFileMetadata.toJSON(file.metadata)); } } @@ -572,7 +572,7 @@ export class StorageLayer { import(storageExportPath: string) { // Restore list of buckets const bucketsFile = path.join(storageExportPath, "buckets.json"); - const bucketsList = JSON.parse(fs.readFileSync(bucketsFile, "utf-8")) as BucketsList; + const bucketsList = JSON.parse(readFileSync(bucketsFile, "utf-8")) as BucketsList; for (const b of bucketsList.buckets) { const bucketMetadata = new CloudStorageBucketMetadata(b.id); this._buckets.set(b.id, bucketMetadata); @@ -590,10 +590,7 @@ export class StorageLayer { logger.debug(`Skipping unexpected storage metadata file: ${f}`); continue; } - const metadata = StoredFileMetadata.fromJSON( - fs.readFileSync(f, "utf-8"), - this._cloudFunctions - ); + const metadata = StoredFileMetadata.fromJSON(readFileSync(f, "utf-8"), this._cloudFunctions); // To get the blob path from the metadata path: // 1) Get the relative path to the metadata export dir @@ -602,7 +599,7 @@ export class StorageLayer { const blobPath = metadataRelPath.substring(0, metadataRelPath.length - dotJson.length); const blobAbsPath = path.join(blobsDir, blobPath); - if (!fs.existsSync(blobAbsPath)) { + if (!existsSync(blobAbsPath)) { logger.warn(`Could not find file "${blobPath}" in storage export.`); continue; } @@ -616,10 +613,10 @@ export class StorageLayer { } private *walkDirSync(dir: string): Generator { - const files = fs.readdirSync(dir); + const files = readdirSync(dir); for (const file of files) { const p = path.join(dir, file); - if (fs.statSync(p).isDirectory()) { + if (statSync(p).isDirectory()) { yield* this.walkDirSync(p); } else { yield p; diff --git a/src/emulator/storage/metadata.ts b/src/emulator/storage/metadata.ts index b32fb3c5d13..a5c607af79c 100644 --- a/src/emulator/storage/metadata.ts +++ b/src/emulator/storage/metadata.ts @@ -3,7 +3,7 @@ import * as crypto from "crypto"; import { EmulatorRegistry } from "../registry"; import { Emulators } from "../types"; import { StorageCloudFunctions } from "./cloudFunctions"; -import { crc32c } from "./crc"; +import { crc32c, crc32cToString } from "./crc"; type RulesResourceMetadataOverrides = { [Property in keyof RulesResourceMetadata]?: RulesResourceMetadata[Property]; @@ -32,7 +32,7 @@ export class StoredFileMetadata { contentEncoding: string; contentDisposition: string; contentLanguage?: string; - cacheControl?: string; + cacheControl: string; customTime?: Date; crc32c: string; etag: string; @@ -58,14 +58,19 @@ export class StoredFileMetadata { this.metageneration = opts.metageneration || 1; this.generation = opts.generation || Date.now(); this.storageClass = opts.storageClass || "STANDARD"; - this.etag = opts.etag || "someETag"; this.contentDisposition = opts.contentDisposition || "inline"; - this.cacheControl = opts.cacheControl; + // Use same default value GCS uses (see https://cloud.google.com/storage/docs/metadata#caching_data) + this.cacheControl = opts.cacheControl || "public, max-age=3600"; this.contentLanguage = opts.contentLanguage; this.customTime = opts.customTime; this.contentEncoding = opts.contentEncoding || "identity"; this.customMetadata = opts.customMetadata; this.downloadTokens = opts.downloadTokens || []; + if (opts.etag) { + this.etag = opts.etag; + } else { + this.etag = generateETag(this.generation, this.metageneration); + } // Special handling for date fields this.timeCreated = opts.timeCreated ? new Date(opts.timeCreated) : new Date(); @@ -180,7 +185,11 @@ export class StoredFileMetadata { } if (incoming.metadata) { - this.customMetadata = incoming.metadata; + // Convert all values to strings + this.customMetadata = this.customMetadata ? { ...this.customMetadata } : {}; + for (const [k, v] of Object.entries(incoming.metadata)) { + this.customMetadata[k] = v === null ? (null as unknown as string) : String(v); + } } if (incoming.contentLanguage) { @@ -381,6 +390,7 @@ export class CloudStorageObjectMetadata { etag: string; metadata?: { [s: string]: string }; contentLanguage?: string; + contentDisposition: string; cacheControl?: string; customTime?: string; id: string; @@ -394,6 +404,7 @@ export class CloudStorageObjectMetadata { this.generation = metadata.generation.toString(); this.metageneration = metadata.metageneration.toString(); this.contentType = metadata.contentType; + this.contentDisposition = metadata.contentDisposition; this.timeCreated = toSerializedDate(metadata.timeCreated); this.updated = toSerializedDate(metadata.updated); this.storageClass = metadata.storageClass; @@ -432,8 +443,7 @@ export class CloudStorageObjectMetadata { this.customTime = toSerializedDate(metadata.customTime); } - // I'm not sure why but @google-cloud/storage calls .substr(4) on this value, so we need to pad it. - this.crc32c = "----" + Buffer.from([metadata.crc32c]).toString("base64"); + this.crc32c = crc32cToString(metadata.crc32c); this.timeStorageClassUpdated = toSerializedDate(metadata.timeCreated); this.id = `${metadata.bucket}/${metadata.name}/${metadata.generation}`; @@ -474,3 +484,10 @@ function generateMd5Hash(bytes: Buffer): string { hash.update(bytes); return hash.digest("base64"); } + +function generateETag(generation: number, metadatageneration: number): string { + const hash = crypto.createHash("sha1"); + hash.update(`${generation}/${metadatageneration}`); + // Trim padding + return hash.digest("base64").slice(0, -1); +} diff --git a/src/emulator/storage/persistence.ts b/src/emulator/storage/persistence.ts index 47a61a2052e..229d19e2e2b 100644 --- a/src/emulator/storage/persistence.ts +++ b/src/emulator/storage/persistence.ts @@ -1,4 +1,4 @@ -import { openSync, closeSync, readSync, unlinkSync, renameSync, existsSync, mkdirSync } from "fs"; +import { openSync, closeSync, readSync, unlinkSync, renameSync, mkdirSync } from "fs"; import * as rimraf from "rimraf"; import * as fs from "fs"; import * as path from "path"; @@ -12,11 +12,9 @@ export class Persistence { public reset(dirPath: string) { this._dirPath = dirPath; - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { - recursive: true, - }); - } + mkdirSync(dirPath, { + recursive: true, + }); } public get dirPath(): string { @@ -26,15 +24,6 @@ export class Persistence { appendBytes(fileName: string, bytes: Buffer): string { const filepath = this.getDiskPath(fileName); - const encodedSlashIndex = filepath.toLowerCase().lastIndexOf("%2f"); - const dirPath = - encodedSlashIndex >= 0 ? filepath.substring(0, encodedSlashIndex) : path.dirname(filepath); - - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { - recursive: true, - }); - } let fd; try { @@ -85,18 +74,10 @@ export class Persistence { } renameFile(oldName: string, newName: string): void { - const dirPath = this.getDiskPath(path.dirname(newName)); - - if (!existsSync(dirPath)) { - mkdirSync(dirPath, { - recursive: true, - }); - } - renameSync(this.getDiskPath(oldName), this.getDiskPath(newName)); } getDiskPath(fileName: string): string { - return path.join(this._dirPath, fileName); + return path.join(this._dirPath, encodeURIComponent(fileName)); } } From e8c89a759b7129d6c5f941dc356327f7beefee68 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 15 Mar 2022 17:27:11 +0000 Subject: [PATCH 0150/1699] 10.3.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index edfd93c344a..fb0d98e47f5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.2.2", + "version": "10.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.2.2", + "version": "10.3.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index e546e5a0dbf..e94ecd2451f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.2.2", + "version": "10.3.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From dae91d554cfb4456cd17fedfe9b6d921ac320c1a Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 15 Mar 2022 17:27:36 +0000 Subject: [PATCH 0151/1699] [firebase-release] Removed change log and reset repo after 10.3.0 release --- CHANGELOG.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 492028b8072..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +0,0 @@ -- Fixes bug where functions' memory configurations weren't preserved in batched function deploys (#4253). -- Adds --local flag to ext:install, ext:update, ext:configure, and ext:uninstall, to save changes to firebase.json instead of deploying immediately. -- `ext:export` now uses stable ordering for params in .env files (#4256). -- Adds alerting event provider (#4258). -- Fixes bug where project-specific environment variables weren't loaded by the Functions Emulator (#4273). -- Fixes bug where CORS was enabled too broadly on the Functions emulator (#4294). -- Adds `rewriteTo` / `copyTo` endpoints to Cloud Storage Emulator (#3647, #3751). -- Addes `createReadStream` support to Cloud Storage Emulator (#3469). -- Fixes `list` endpoint in Cloud Storage Emulator (#3647). -- Fixes `getFiles` for Cloud Storage Emulator (#3778). -- Fixes `exists` for Cloud Storage Emulator (#3764). From 143644059d5f773aff6741e74d27f7f7bfe92cd9 Mon Sep 17 00:00:00 2001 From: abhis3 Date: Tue, 15 Mar 2022 15:35:40 -0700 Subject: [PATCH 0152/1699] Fix broken unit test for resumable uploads (#4288) * Fix broken unit test for resumable uploads --- scripts/storage-emulator-integration/tests.ts | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index eea69d1f59b..ebe5ea56a92 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -263,9 +263,53 @@ describe("Storage emulator", () => { }); }); - // TODO(abehaskins): This test is temporarily disabled due to a credentials issue - it.skip("should handle large (resumable) uploads", async () => { - await testBucket.upload(largeFilePath, { resumable: true }); + it("should handle resumable uploads", async () => { + const fileName = "test_upload.jpg"; + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post(`/upload/storage/v1/b/${storageBucket}/o?name=${fileName}&uploadType=resumable`) + .send({}) + .set({ + Authorization: "Bearer owner", + }) + .expect(200) + .then((res) => new URL(res.header["location"])); + + const metadata = await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .expect(200) + .then((res) => res.body); + + const metadataTypes: { [s: string]: string } = {}; + + for (const key in metadata) { + if (metadata[key]) { + metadataTypes[key] = typeof metadata[key]; + } + } + + expect(metadata.name).to.equal(fileName); + expect(metadata.contentType).to.equal("application/octet-stream"); + expect(metadataTypes).to.deep.equal({ + kind: "string", + name: "string", + bucket: "string", + cacheControl: "string", + contentDisposition: "string", + generation: "string", + metageneration: "string", + contentType: "string", + timeCreated: "string", + updated: "string", + storageClass: "string", + size: "string", + md5Hash: "string", + etag: "string", + crc32c: "string", + timeStorageClassUpdated: "string", + id: "string", + selfLink: "string", + mediaLink: "string", + }); }); it("should upload with provided metadata", async () => { From 8d1ee44a459c69b4cf4ea966b94cc97356063cc6 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Wed, 16 Mar 2022 09:42:46 -0400 Subject: [PATCH 0153/1699] Refactor extension's getParam to allow multiple configurations (#4292) * update getParams * Fix update commands * update param asking * Add tests * rename * Rename variables * Fix update bug * Fix tests * PR fixes * pr fixes --- src/commands/ext-configure.ts | 16 ++-- src/commands/ext-install.ts | 17 ++-- src/commands/ext-update.ts | 9 ++- src/extensions/askUserForParam.ts | 86 +++++++++++---------- src/extensions/extensionsHelper.ts | 5 +- src/extensions/manifest.ts | 2 +- src/extensions/paramHelper.ts | 70 +++++++++++++---- src/test/extensions/askUserForParam.spec.ts | 20 +++-- src/test/extensions/paramHelper.spec.ts | 77 +++++++++++++----- 9 files changed, 207 insertions(+), 95 deletions(-) diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index a86c8c7a9df..81dc02fcab4 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -19,6 +19,7 @@ import * as refs from "../extensions/refs"; import * as manifest from "../extensions/manifest"; import { Options } from "../options"; import { partition } from "../functional"; +import { getBaseParamBindings } from "../extensions/paramHelper"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -67,7 +68,7 @@ export default new Command("ext:configure ") // Ask for mutable param values from user. paramHelper.setNewDefaults(tbdParams, oldParamValues); - const mutableParamsValues = await paramHelper.getParams({ + const mutableParamsBindingOptions = await paramHelper.getParams({ projectId, paramSpecs: tbdParams, nonInteractive: false, @@ -79,7 +80,7 @@ export default new Command("ext:configure ") // Merge with old immutable params. const newParamValues = { ...oldParamValues, - ...mutableParamsValues, + ...getBaseParamBindings(mutableParamsBindingOptions), }; await manifest.writeToManifest( @@ -123,7 +124,7 @@ export default new Command("ext:configure ") paramHelper.getParamsWithCurrentValuesAsDefaults(existingInstance); const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => param.immutable); - const params = await paramHelper.getParams({ + const paramBindingOptions = await paramHelper.getParams({ projectId, paramSpecs: paramSpecWithNewDefaults, nonInteractive: options.nonInteractive, @@ -131,13 +132,14 @@ export default new Command("ext:configure ") instanceId, reconfiguring: true, }); + const paramBindings = getBaseParamBindings(paramBindingOptions); if (immutableParams.length) { const plural = immutableParams.length > 1; logger.info(`The following param${plural ? "s are" : " is"} immutable:`); for (const { param } of immutableParams) { const value = _.get(existingInstance, `config.params.${param}`); logger.info(`param: ${param}, value: ${value}`); - params[param] = value; + paramBindings[param] = value; } logger.info( (plural @@ -148,7 +150,11 @@ export default new Command("ext:configure ") } spinner.start(); - const res = await extensionsApi.configureInstance({ projectId, instanceId, params }); + const res = await extensionsApi.configureInstance({ + projectId, + instanceId, + params: paramBindings, + }); spinner.stop(); utils.logLabeledSuccess(logPrefix, `successfully configured ${clc.bold(instanceId)}.`); utils.logLabeledBullet( diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index b6264e6b75f..20db914d9d5 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -41,6 +41,7 @@ import { logger } from "../logger"; import { previews } from "../previews"; import { Options } from "../options"; import * as manifest from "../extensions/manifest"; +import { getBaseParamBindings, ParamBindingOptions } from "../extensions/paramHelper"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -257,13 +258,14 @@ async function installToManifest(options: InstallExtensionOptions): Promise } else { choice = "installNew"; } - let params: Record; + let paramBindingOptions: { [key: string]: ParamBindingOptions }; + let paramBindings: Record; switch (choice) { case "installNew": instanceId = await promptForValidInstanceId(`${instanceId}-${getRandomString(4)}`); - params = await paramHelper.getParams({ + paramBindingOptions = await paramHelper.getParams({ projectId, paramSpecs: spec.params, nonInteractive, paramsEnvPath, instanceId, }); + paramBindings = getBaseParamBindings(paramBindingOptions); spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes..."; spinner.start(); await extensionsApi.createInstance({ @@ -388,7 +392,7 @@ async function installExtension(options: InstallExtensionOptions): Promise instanceId, extensionSource: source, extensionVersionRef: extVersion?.ref, - params, + params: paramBindings, }); spinner.stop(); utils.logLabeledSuccess( @@ -398,13 +402,14 @@ async function installExtension(options: InstallExtensionOptions): Promise ); break; case "updateExisting": - params = await paramHelper.getParams({ + paramBindingOptions = await paramHelper.getParams({ projectId, paramSpecs: spec.params, nonInteractive, paramsEnvPath, instanceId, }); + paramBindings = getBaseParamBindings(paramBindingOptions); spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes..."; spinner.start(); await update({ @@ -412,7 +417,7 @@ async function installExtension(options: InstallExtensionOptions): Promise instanceId, source, extRef: extVersion?.ref, - params, + params: paramBindings, }); spinner.stop(); utils.logLabeledSuccess( diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 7ec9c432cbf..845871f9691 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -125,7 +125,7 @@ export default new Command("ext:update [updateSource]") projectDir: config.projectDir, }); - const newParams = await paramHelper.getParamsForUpdate({ + const newParamBindingOptions = await paramHelper.getParamsForUpdate({ spec: oldExtensionVersion.spec, newSpec: newExtensionVersion.spec, currentParams: oldParamValues, @@ -134,13 +134,14 @@ export default new Command("ext:update [updateSource]") nonInteractive: options.nonInteractive, instanceId, }); + const newParamBindings = paramHelper.getBaseParamBindings(newParamBindingOptions); await manifest.writeToManifest( [ { instanceId, ref: refs.parse(newExtensionVersion.ref), - params: newParams, + params: newParamBindings, }, ], config, @@ -322,7 +323,7 @@ export default new Command("ext:update [updateSource]") } // make a copy of existingParams -- they get overridden by paramHelper.getParamsForUpdate const oldParamValues = { ...existingParams }; - const newParams = await paramHelper.getParamsForUpdate({ + const newParamBindings = await paramHelper.getParamsForUpdate({ spec: existingSpec, newSpec, currentParams: existingParams, @@ -331,6 +332,8 @@ export default new Command("ext:update [updateSource]") nonInteractive: options.nonInteractive, instanceId, }); + const newParams = paramHelper.getBaseParamBindings(newParamBindings); + spinner.start(); const updateOptions: UpdateOptions = { projectId, diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index 40a33ce9f97..a9d44d88a88 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -11,6 +11,7 @@ import { convertExtensionOptionToLabeledList, getRandomString, onceWithJoin } fr import { logger } from "../logger"; import { promptOnce } from "../prompt"; import * as utils from "../utils"; +import { ParamBindingOptions } from "./paramHelper"; enum SecretUpdateAction { LEAVE, @@ -61,12 +62,51 @@ export function checkResponse(response: string, spec: Param): boolean { return valid; } -export async function askForParam( +/** + * Prompt users for params based on paramSpecs defined by the extension developer. + * @param paramSpecs Array of params to ask the user about, parsed from extension.yaml. + * @param firebaseProjectParams Autopopulated Firebase project-specific params + * @return Promisified map of env vars to values. + */ +export async function ask( projectId: string, instanceId: string, - paramSpec: Param, + paramSpecs: Param[], + firebaseProjectParams: { [key: string]: string }, reconfiguring: boolean -): Promise { +): Promise<{ [key: string]: ParamBindingOptions }> { + if (_.isEmpty(paramSpecs)) { + logger.debug("No params were specified for this extension."); + return {}; + } + + utils.logLabeledBullet(logPrefix, "answer the questions below to configure your extension:"); + const substituted = substituteParams(paramSpecs, firebaseProjectParams); + const result: { [key: string]: ParamBindingOptions } = {}; + const promises = _.map(substituted, (paramSpec: Param) => { + return async () => { + result[paramSpec.param] = await askForParam({ + projectId, + instanceId, + paramSpec, + reconfiguring, + }); + }; + }); + // chaining together the promises so they get executed one after another + await promises.reduce((prev, cur) => prev.then(cur as any), Promise.resolve()); + logger.info(); + return result; +} + +export async function askForParam(args: { + projectId: string; + instanceId: string; + paramSpec: Param; + reconfiguring: boolean; +}): Promise { + const paramSpec = args.paramSpec; + let valid = false; let response = ""; const description = paramSpec.description || ""; @@ -117,9 +157,9 @@ export async function askForParam( valid = checkResponse(response, paramSpec); break; case ParamType.SECRET: - response = reconfiguring - ? await promptReconfigureSecret(projectId, instanceId, paramSpec) - : await promptCreateSecret(projectId, instanceId, paramSpec); + response = args.reconfiguring + ? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec) + : await promptCreateSecret(args.projectId, args.instanceId, paramSpec); valid = true; break; default: @@ -133,7 +173,7 @@ export async function askForParam( valid = checkResponse(response, paramSpec); } } - return response; + return { baseValue: response }; } async function promptReconfigureSecret( @@ -251,35 +291,3 @@ export function getInquirerDefault(options: ParamOption[], def: string): string }); return defaultOption ? defaultOption.label || defaultOption.value : ""; } - -/** - * Prompt users for params based on paramSpecs defined by the extension developer. - * @param paramSpecs Array of params to ask the user about, parsed from extension.yaml. - * @param firebaseProjectParams Autopopulated Firebase project-specific params - * @return Promisified map of env vars to values. - */ -export async function ask( - projectId: string, - instanceId: string, - paramSpecs: Param[], - firebaseProjectParams: { [key: string]: string }, - reconfiguring: boolean -): Promise<{ [key: string]: string }> { - if (_.isEmpty(paramSpecs)) { - logger.debug("No params were specified for this extension."); - return {}; - } - - utils.logLabeledBullet(logPrefix, "answer the questions below to configure your extension:"); - const substituted = substituteParams(paramSpecs, firebaseProjectParams); - const result: any = {}; - const promises = _.map(substituted, (paramSpec: Param) => { - return async () => { - result[paramSpec.param] = await askForParam(projectId, instanceId, paramSpec, reconfiguring); - }; - }); - // chaining together the promises so they get executed one after another - await promises.reduce((prev, cur) => prev.then(cur as any), Promise.resolve()); - logger.info(); - return result; -} diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index ebc3155b492..facadca36b5 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -166,7 +166,10 @@ export function substituteParams(original: T, params: Record) * @param paramSpec information on params parsed from extension.yaml * @return JSON object of params */ -export function populateDefaultParams(paramVars: Record, paramSpecs: Param[]): any { +export function populateDefaultParams( + paramVars: Record, + paramSpecs: Param[] +): Record { const newParams = paramVars; for (const param of paramSpecs) { diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index aba21ce5fb5..e9bc6f209f2 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -180,7 +180,7 @@ export function readInstanceParam(args: { function readParamsFile(projectDir: string, fileName: string): Record { const paramPath = path.join(projectDir, ENV_DIRECTORY, fileName); const params = readEnvFile(paramPath); - return params as Record; + return params; } // TODO(lihes): Add a docs link once exists. diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 6ca61f9a259..203c8e4d340 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -16,6 +16,44 @@ import * as askUserForParam from "./askUserForParam"; import * as track from "../track"; import * as env from "../functions/env"; +/** + * Interface for holding different param values for different environments/configs. + * + * baseValue: The base value of the configurations, stored in {instance-id}.env. + * local: The local value used by extensions emulators. Only used by secrets in {instance-id}.secret.env for now. + */ +export interface ParamBindingOptions { + baseValue: string; + local?: string; + // Add project specific key:value here when we want to support that. +} + +export function getBaseParamBindings(params: { [key: string]: ParamBindingOptions }): { + [key: string]: string; +} { + let ret = {}; + for (const [k, v] of Object.entries(params)) { + ret = { + ...ret, + ...{ [k]: v.baseValue }, + }; + } + return ret; +} + +export function buildBindingOptionsWithBaseValue(baseParams: { [key: string]: string }): { + [key: string]: ParamBindingOptions; +} { + let paramOptions: { [key: string]: ParamBindingOptions } = {}; + for (const [k, v] of Object.entries(baseParams)) { + paramOptions = { + ...paramOptions, + ...{ [k]: { baseValue: v } }, + }; + } + return paramOptions; +} + /** * A mutator to switch the defaults for a list of params to new ones. * For convenience, this also returns the params @@ -63,7 +101,7 @@ export async function getParams(args: { nonInteractive?: boolean; paramsEnvPath?: string; reconfiguring?: boolean; -}): Promise<{ [key: string]: string }> { +}): Promise<{ [key: string]: ParamBindingOptions }> { let params: any; if (args.nonInteractive && !args.paramsEnvPath) { const paramsMessage = args.paramSpecs @@ -105,8 +143,8 @@ export async function getParamsForUpdate(args: { paramsEnvPath?: string; nonInteractive?: boolean; instanceId: string; -}) { - let params: any; +}): Promise<{ [key: string]: ParamBindingOptions }> { + let params: { [key: string]: ParamBindingOptions }; if (args.nonInteractive && !args.paramsEnvPath) { const paramsMessage = args.newSpec.params .map((p) => { @@ -152,7 +190,7 @@ export async function promptForNewParams(args: { currentParams: { [option: string]: string }; projectId: string; instanceId: string; -}): Promise { +}): Promise<{ [option: string]: ParamBindingOptions }> { const firebaseProjectParams = await getFirebaseProjectParams(args.projectId); const comparer = (param1: extensionsApi.Param, param2: extensionsApi.Param) => { return param1.type === param2.type && param1.param === param2.param; @@ -185,23 +223,24 @@ export async function promptForNewParams(args: { if (paramsDiffAdditions.length) { logger.info("To update this instance, configure the following new parameters:"); for (const param of paramsDiffAdditions) { - const chosenValue = await askUserForParam.askForParam( - args.projectId, - args.instanceId, - param, - false - ); - args.currentParams[param.param] = chosenValue; + const chosenValue = await askUserForParam.askForParam({ + projectId: args.projectId, + instanceId: args.instanceId, + paramSpec: param, + reconfiguring: false, + }); + args.currentParams[param.param] = chosenValue.baseValue; } } - return args.currentParams; + + return buildBindingOptionsWithBaseValue(args.currentParams); } function getParamsFromFile(args: { projectId: string; paramSpecs: extensionsApi.Param[]; paramsEnvPath: string; -}): Record { +}): Record { let envParams; try { envParams = readEnvFile(args.paramsEnvPath); @@ -213,10 +252,11 @@ function getParamsFromFile(args: { const params = populateDefaultParams(envParams, args.paramSpecs); validateCommandLineParams(params, args.paramSpecs); logger.info(`Using param values from ${args.paramsEnvPath}`); - return params; + + return buildBindingOptionsWithBaseValue(params); } -export function readEnvFile(envPath: string) { +export function readEnvFile(envPath: string): Record { const buf = fs.readFileSync(path.resolve(envPath), "utf8"); const result = env.parse(buf.toString().trim()); if (result.errors.length) { diff --git a/src/test/extensions/askUserForParam.spec.ts b/src/test/extensions/askUserForParam.spec.ts index 7493719bb79..9f1e953354e 100644 --- a/src/test/extensions/askUserForParam.spec.ts +++ b/src/test/extensions/askUserForParam.spec.ts @@ -215,7 +215,12 @@ describe("askUserForParam", () => { }); it("should keep prompting user until valid input is given", async () => { - await askForParam("project-id", "instance-id", testSpec, false); + await askForParam({ + projectId: "project-id", + instanceId: "instance-id", + paramSpec: testSpec, + reconfiguring: false, + }); expect(promptStub.calledThrice).to.be.true; }); }); @@ -262,12 +267,17 @@ describe("askUserForParam", () => { }); it("should keep prompting user until valid input is given", async () => { - const result = await askForParam("project-id", "instance-id", secretSpec, false); + const result = await askForParam({ + projectId: "project-id", + instanceId: "instance-id", + paramSpec: secretSpec, + reconfiguring: false, + }); expect(promptStub.calledOnce).to.be.true; expect(grantRole.calledOnce).to.be.true; - expect(result).to.be.equal( - `projects/${stubSecret.projectId}/secrets/${stubSecret.name}/versions/${stubSecretVersion.versionId}` - ); + expect(result).to.be.eql({ + baseValue: `projects/${stubSecret.projectId}/secrets/${stubSecret.name}/versions/${stubSecretVersion.versionId}`, + }); }); }); diff --git a/src/test/extensions/paramHelper.spec.ts b/src/test/extensions/paramHelper.spec.ts index e65ea69d615..4ad36b2b42d 100644 --- a/src/test/extensions/paramHelper.spec.ts +++ b/src/test/extensions/paramHelper.spec.ts @@ -75,6 +75,43 @@ const SPEC = { }; describe("paramHelper", () => { + describe(`${paramHelper.getBaseParamBindings.name}`, () => { + it("should extract the baseValue param bindings", () => { + const input = { + pokeball: { + baseValue: "pikachu", + local: "local", + }, + greatball: { + baseValue: "eevee", + }, + }; + const output = paramHelper.getBaseParamBindings(input); + expect(output).to.eql({ + pokeball: "pikachu", + greatball: "eevee", + }); + }); + }); + + describe(`${paramHelper.buildBindingOptionsWithBaseValue.name}`, () => { + it("should build given baseValue values", () => { + const input = { + pokeball: "pikachu", + greatball: "eevee", + }; + const output = paramHelper.buildBindingOptionsWithBaseValue(input); + expect(output).to.eql({ + pokeball: { + baseValue: "pikachu", + }, + greatball: { + baseValue: "eevee", + }, + }); + }); + }); + describe("getParams", () => { let envStub: sinon.SinonStub; let promptStub: sinon.SinonStub; @@ -110,8 +147,8 @@ describe("paramHelper", () => { }); expect(params).to.eql({ - A_PARAMETER: "aValue", - ANOTHER_PARAMETER: "value", + A_PARAMETER: { baseValue: "aValue" }, + ANOTHER_PARAMETER: { baseValue: "value" }, }); }); @@ -132,8 +169,8 @@ describe("paramHelper", () => { }); expect(params).to.eql({ - A_PARAMETER: "aValue", - ANOTHER_PARAMETER: "default", + A_PARAMETER: { baseValue: "aValue" }, + ANOTHER_PARAMETER: { baseValue: "default" }, }); }); @@ -154,7 +191,7 @@ describe("paramHelper", () => { }); expect(params).to.eql({ - A_PARAMETER: "aValue", + A_PARAMETER: { baseValue: "aValue" }, }); }); @@ -230,8 +267,8 @@ describe("paramHelper", () => { }); expect(params).to.eql({ - A_PARAMETER: "user input", - ANOTHER_PARAMETER: "user input", + A_PARAMETER: { baseValue: "user input" }, + ANOTHER_PARAMETER: { baseValue: "user input" }, }); expect(promptStub).to.have.been.calledTwice; @@ -367,9 +404,9 @@ describe("paramHelper", () => { }); const expected = { - ANOTHER_PARAMETER: "value", - NEW_PARAMETER: "user input", - THIRD_PARAMETER: "user input", + ANOTHER_PARAMETER: { baseValue: "value" }, + NEW_PARAMETER: { baseValue: "user input" }, + THIRD_PARAMETER: { baseValue: "user input" }, }; expect(newParams).to.eql(expected); expect(promptStub.callCount).to.equal(2); @@ -408,9 +445,9 @@ describe("paramHelper", () => { }); const expected = { - ANOTHER_PARAMETER: "user input", - NEW_PARAMETER: "user input", - THIRD_PARAMETER: "user input", + ANOTHER_PARAMETER: { baseValue: "user input" }, + NEW_PARAMETER: { baseValue: "user input" }, + THIRD_PARAMETER: { baseValue: "user input" }, }; expect(newParams).to.eql(expected); }); @@ -432,8 +469,8 @@ describe("paramHelper", () => { }); const expected = { - ANOTHER_PARAMETER: "value", - A_PARAMETER: "value", + ANOTHER_PARAMETER: { baseValue: "value" }, + A_PARAMETER: { baseValue: "value" }, }; expect(newParams).to.eql(expected); expect(promptStub).not.to.have.been.called; @@ -457,9 +494,9 @@ describe("paramHelper", () => { }); const expected = { - ANOTHER_PARAMETER: "value", - NEW_PARAMETER: "test-proj", - THIRD_PARAMETER: "user input", + ANOTHER_PARAMETER: { baseValue: "value" }, + NEW_PARAMETER: { baseValue: "test-proj" }, + THIRD_PARAMETER: { baseValue: "user input" }, }; expect(newParams).to.eql(expected); expect(promptStub.callCount).to.equal(2); @@ -497,8 +534,8 @@ describe("paramHelper", () => { }); const expected = { - ANOTHER_PARAMETER: "value", - A_PARAMETER: "value", + ANOTHER_PARAMETER: { baseValue: "value" }, + A_PARAMETER: { baseValue: "value" }, }; expect(newParams).to.eql(expected); expect(promptStub).not.to.have.been.called; From 6e17f4e71ae3ddc3574f5383a898acdf8eaeef5a Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 16 Mar 2022 09:33:38 -0700 Subject: [PATCH 0154/1699] Read {instanceId}.secret.local for Extension secret overrides (#4300) * Read {instanceId}.secret.local for Extension secret overrides * separating out and testing getSecretLocalPath --- src/emulator/functionsEmulator.ts | 11 ++-- src/emulator/functionsEmulatorShared.ts | 18 +++++- src/extensions/manifest.ts | 2 +- .../emulators/functionsEmulatorShared.spec.ts | 58 ++++++++++++++++--- 4 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index f85ad8a9d25..cae2fb31c2a 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -41,6 +41,7 @@ import { ParsedTriggerDefinition, emulatedFunctionsFromEndpoints, emulatedFunctionsByRegion, + getSecretLocalPath, } from "./functionsEmulatorShared"; import { EmulatorRegistry } from "./registry"; import { EmulatorLogger, Verbosity } from "./emulatorLogger"; @@ -62,11 +63,8 @@ import { accessSecretVersion } from "../gcp/secretManager"; import * as runtimes from "../deploy/functions/runtimes"; import * as backend from "../deploy/functions/backend"; import * as functionsEnv from "../functions/env"; -import { flattenArray } from "../functional"; -import { SecretEnvVar } from "../gcp/cloudfunctions"; const EVENT_INVOKE = "functions:invoke"; -const LOCAL_SECRETS_FILE = ".secret.local"; /* * The Realtime Database emulator expects the `path` field in its trigger @@ -1078,15 +1076,16 @@ export class FunctionsEmulator implements EmulatorInstance { ): Promise> { let secretEnvs: Record = {}; + const secretPath = getSecretLocalPath(backend, this.args.projectDir); try { - const data = fs.readFileSync(path.join(backend.functionsDir, LOCAL_SECRETS_FILE), "utf8"); + const data = fs.readFileSync(secretPath, "utf8"); secretEnvs = functionsEnv.parseStrict(data); } catch (e: any) { if (e.code !== "ENOENT") { this.logger.logLabeled( "ERROR", "functions", - `Failed to read local secrets file ${LOCAL_SECRETS_FILE}: ${e.message}` + `Failed to read local secrets file ${secretPath}: ${e.message}` ); } } @@ -1122,7 +1121,7 @@ export class FunctionsEmulator implements EmulatorInstance { "functions", "Unable to access secret environment variables from Google Cloud Secret Manager. " + "Make sure the credential used for the Functions Emulator have access " + - `or provide override values in ${LOCAL_SECRETS_FILE}:\n\t` + + `or provide override values in ${secretPath}:\n\t` + errs.join("\n\t") ); } diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index bd3572749e5..1f32843bec6 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -7,9 +7,10 @@ import * as fs from "fs"; import * as backend from "../deploy/functions/backend"; import { Constants } from "./constants"; -import { InvokeRuntimeOpts } from "./functionsEmulator"; +import { EmulatableBackend, InvokeRuntimeOpts } from "./functionsEmulator"; import { copyIfPresent } from "../gcp/proto"; import { logger } from "../logger"; +import { ENV_DIRECTORY } from "../extensions/manifest"; export type SignatureType = "http" | "event" | "cloudevent"; @@ -372,3 +373,18 @@ export function getSignatureType(def: EmulatedTriggerDefinition): SignatureType // that allows CF3v1 functions to target GCFv2 and vice versa. return def.platform === "gcfv2" ? "cloudevent" : "event"; } + +const LOCAL_SECRETS_FILE = ".secret.local"; + +/** + * getSecretLocalPath returns the expected location for a .secret.local override file. + */ +export function getSecretLocalPath(backend: EmulatableBackend, projectDir: string) { + const secretsFile = backend.extensionInstanceId + ? `${backend.extensionInstanceId}${LOCAL_SECRETS_FILE}` + : LOCAL_SECRETS_FILE; + const secretDirectory = backend.extensionInstanceId + ? path.join(projectDir, ENV_DIRECTORY) + : backend.functionsDir; + return path.join(secretDirectory, secretsFile); +} diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index e9bc6f209f2..7def7ada78b 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -10,7 +10,7 @@ import { FirebaseError } from "../error"; import * as utils from "../utils"; import { logPrefix } from "./extensionsHelper"; -const ENV_DIRECTORY = "extensions"; +export const ENV_DIRECTORY = "extensions"; /** * Write a list of instanceSpecs to extensions manifest. diff --git a/src/test/emulators/functionsEmulatorShared.spec.ts b/src/test/emulators/functionsEmulatorShared.spec.ts index 4459322695f..72f37faa8fe 100644 --- a/src/test/emulators/functionsEmulatorShared.spec.ts +++ b/src/test/emulators/functionsEmulatorShared.spec.ts @@ -1,5 +1,6 @@ import { expect } from "chai"; -import { getFunctionService } from "../../emulator/functionsEmulatorShared"; +import { EmulatableBackend } from "../../emulator/functionsEmulator"; +import * as functionsEmulatorShared from "../../emulator/functionsEmulatorShared"; const baseDef = { platform: "gcfv1" as const, @@ -10,7 +11,7 @@ const baseDef = { }; describe("FunctionsEmulatorShared", () => { - describe("getFunctionService", () => { + describe(`${functionsEmulatorShared.getFunctionService.name}`, () => { it("should get service from event trigger definition", () => { const def = { ...baseDef, @@ -20,7 +21,7 @@ describe("FunctionsEmulatorShared", () => { service: "pubsub.googleapis.com", }, }; - expect(getFunctionService(def)).to.be.eql("pubsub.googleapis.com"); + expect(functionsEmulatorShared.getFunctionService(def)).to.be.eql("pubsub.googleapis.com"); }); it("should return unknown if trigger definition is not event-based", () => { @@ -28,7 +29,7 @@ describe("FunctionsEmulatorShared", () => { ...baseDef, httpsTrigger: {}, }; - expect(getFunctionService(def)).to.be.eql("unknown"); + expect(functionsEmulatorShared.getFunctionService(def)).to.be.eql("unknown"); }); it("should infer pubsub service based on eventType", () => { @@ -39,7 +40,7 @@ describe("FunctionsEmulatorShared", () => { eventType: "google.cloud.pubsub.topic.v1.messagePublished", }, }; - expect(getFunctionService(def)).to.be.eql("pubsub.googleapis.com"); + expect(functionsEmulatorShared.getFunctionService(def)).to.be.eql("pubsub.googleapis.com"); }); it("should infer firestore service based on eventType", () => { @@ -50,7 +51,7 @@ describe("FunctionsEmulatorShared", () => { eventType: "providers/cloud.firestore/eventTypes/document.write", }, }; - expect(getFunctionService(def)).to.be.eql("firestore.googleapis.com"); + expect(functionsEmulatorShared.getFunctionService(def)).to.be.eql("firestore.googleapis.com"); }); it("should infer database service based on eventType", () => { @@ -61,7 +62,7 @@ describe("FunctionsEmulatorShared", () => { eventType: "providers/google.firebase.database/eventTypes/ref.write", }, }; - expect(getFunctionService(def)).to.be.eql("firebaseio.com"); + expect(functionsEmulatorShared.getFunctionService(def)).to.be.eql("firebaseio.com"); }); it("should infer storage service based on eventType", () => { @@ -72,7 +73,7 @@ describe("FunctionsEmulatorShared", () => { eventType: "google.storage.object.finalize", }, }; - expect(getFunctionService(def)).to.be.eql("storage.googleapis.com"); + expect(functionsEmulatorShared.getFunctionService(def)).to.be.eql("storage.googleapis.com"); }); it("should infer auth service based on eventType", () => { @@ -83,7 +84,46 @@ describe("FunctionsEmulatorShared", () => { eventType: "providers/firebase.auth/eventTypes/user.create", }, }; - expect(getFunctionService(def)).to.be.eql("firebaseauth.googleapis.com"); + expect(functionsEmulatorShared.getFunctionService(def)).to.be.eql( + "firebaseauth.googleapis.com" + ); }); }); + + describe(`${functionsEmulatorShared.getSecretLocalPath.name}`, () => { + const testProjectDir = "project/dir"; + const tests: { + desc: string; + in: EmulatableBackend; + expected: string; + }[] = [ + { + desc: "should return the correct location for an Extension backend", + in: { + functionsDir: "extensions/functions", + env: {}, + secretEnv: [], + extensionInstanceId: "my-extension-instance", + }, + expected: "project/dir/extensions/my-extension-instance.secret.local", + }, + { + desc: "should return the correct location for a CF3 backend", + in: { + functionsDir: "test/cf3", + env: {}, + secretEnv: [], + }, + expected: "test/cf3/.secret.local", + }, + ]; + + for (const t of tests) { + it(t.desc, () => { + expect(functionsEmulatorShared.getSecretLocalPath(t.in, testProjectDir)).to.equal( + t.expected + ); + }); + } + }); }); From 4a3c102e8e68e55c2ff24417e9e1387332345538 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 16 Mar 2022 14:36:23 -0400 Subject: [PATCH 0155/1699] Moved Storage rules integration tests under scripts/ (#4309) --- .../rules/manager.test.ts | 17 +++++++-------- .../rules/runtime.test.ts | 21 ++++++++----------- scripts/storage-emulator-integration/run.sh | 2 ++ 3 files changed, 19 insertions(+), 21 deletions(-) rename src/test/emulators/storage/rules/manager.spec.ts => scripts/storage-emulator-integration/rules/manager.test.ts (85%) rename src/test/emulators/storage/rules/runtime.spec.ts => scripts/storage-emulator-integration/rules/runtime.test.ts (94%) diff --git a/src/test/emulators/storage/rules/manager.spec.ts b/scripts/storage-emulator-integration/rules/manager.test.ts similarity index 85% rename from src/test/emulators/storage/rules/manager.spec.ts rename to scripts/storage-emulator-integration/rules/manager.test.ts index 35c42f297cc..3f024145ea0 100644 --- a/src/test/emulators/storage/rules/manager.spec.ts +++ b/scripts/storage-emulator-integration/rules/manager.test.ts @@ -2,15 +2,14 @@ import { expect } from "chai"; import { tmpdir } from "os"; import { v4 as uuidv4 } from "uuid"; -import { FirebaseError } from "../../../../error"; -import { StorageRulesFiles, TIMEOUT_MED } from "../../fixtures"; -import { StorageRulesManager } from "../../../../emulator/storage/rules/manager"; -import { StorageRulesRuntime } from "../../../../emulator/storage/rules/runtime"; -import { Persistence } from "../../../../emulator/storage/persistence"; -import { RulesetOperationMethod } from "../../../../emulator/storage/rules/types"; - -// TODO(hsinpei: Make this an integration test -describe.skip("Storage Rules Manager", function () { +import { FirebaseError } from "../../../src/error"; +import { StorageRulesFiles, TIMEOUT_MED } from "../../../src/test/emulators/fixtures"; +import { StorageRulesManager } from "../../../src/emulator/storage/rules/manager"; +import { StorageRulesRuntime } from "../../../src/emulator/storage/rules/runtime"; +import { Persistence } from "../../../src/emulator/storage/persistence"; +import { RulesetOperationMethod } from "../../../src/emulator/storage/rules/types"; + +describe("Storage Rules Manager", function () { const rulesRuntime = new StorageRulesRuntime(); const rulesManager = new StorageRulesManager(rulesRuntime); diff --git a/src/test/emulators/storage/rules/runtime.spec.ts b/scripts/storage-emulator-integration/rules/runtime.test.ts similarity index 94% rename from src/test/emulators/storage/rules/runtime.spec.ts rename to scripts/storage-emulator-integration/rules/runtime.test.ts index 424ffc9ffbe..1bf1922a321 100644 --- a/src/test/emulators/storage/rules/runtime.spec.ts +++ b/scripts/storage-emulator-integration/rules/runtime.test.ts @@ -1,19 +1,16 @@ import { RulesetVerificationOpts, StorageRulesRuntime, -} from "../../../../emulator/storage/rules/runtime"; +} from "../../../src/emulator/storage/rules/runtime"; import { expect } from "chai"; -import { StorageRulesFiles } from "../../fixtures"; +import { StorageRulesFiles } from "../../emulator-tests/fixtures"; import * as jwt from "jsonwebtoken"; -import { EmulatorLogger } from "../../../../emulator/emulatorLogger"; -import { ExpressionValue } from "../../../../emulator/storage/rules/expressionValue"; -import { RulesetOperationMethod } from "../../../../emulator/storage/rules/types"; -import { - downloadIfNecessary, - getDownloadDetails, -} from "../../../../emulator/downloadableEmulators"; -import { Emulators } from "../../../../emulator/types"; -import { RulesResourceMetadata } from "../../../../emulator/storage/metadata"; +import { EmulatorLogger } from "../../../src/emulator/emulatorLogger"; +import { ExpressionValue } from "../../../src/emulator/storage/rules/expressionValue"; +import { RulesetOperationMethod } from "../../../src/emulator/storage/rules/types"; +import { downloadIfNecessary } from "../../../src/emulator/downloadableEmulators"; +import { Emulators } from "../../../src/emulator/types"; +import { RulesResourceMetadata } from "../../../src/emulator/storage/metadata"; const TOKENS = { signedInUser: jwt.sign( @@ -46,7 +43,7 @@ function createFakeResourceMetadata(params: { }; } -describe.skip("Storage Rules Runtime", function () { +describe("Storage Rules Runtime", function () { let runtime: StorageRulesRuntime; // eslint-disable-next-line @typescript-eslint/no-invalid-this diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index e13154ce737..b362338fc96 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -7,6 +7,8 @@ set -e # Immediately exit on failure # Prepare the storage emulator rules runtime firebase setup:emulators:storage +mocha scripts/storage-emulator-integration/rules/*.test.ts + mocha \ --require ts-node/register \ --require source-map-support/register \ From c8337ee60e82a54ce877e71e7e1e342906df5ffb Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 16 Mar 2022 12:05:27 -0700 Subject: [PATCH 0156/1699] Trigger parser can create callable triggers (#4310) Fixes #4307 and #4302. `endpointFromFunction` was correctly identifying that `labels["deployment-callable"]` gives an endpoint a `callableTrigger` but `parseTriggers.ts` had no codepath that gave them a `callableTrigger` (instead falling back to an `httpsTriggerr` and labels). --- CHANGELOG.md | 1 + .../functions/runtimes/node/parseTriggers.ts | 15 +++++++++----- src/gcp/cloudfunctions.ts | 15 ++++++++++++-- .../runtimes/node/parseTriggers.spec.ts | 20 +++++++++++++++++++ 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..595cff98428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes bug where deploying callable functions failed (#4310). diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 8c689703f8f..5921f05155c 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -195,12 +195,17 @@ export function addResourcesToBackend( reason: "Needed for task queue functions.", }); } else if (annotation.httpsTrigger) { - const trigger: backend.HttpsTrigger = {}; - if (annotation.failurePolicy) { - logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`); + if (annotation.labels?.["deployment-callable"]) { + delete annotation.labels["deployment-callable"]; + triggered = { callableTrigger: {} }; + } else { + const trigger: backend.HttpsTrigger = {}; + if (annotation.failurePolicy) { + logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`); + } + proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker"); + triggered = { httpsTrigger: trigger }; } - proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker"); - triggered = { httpsTrigger: trigger }; } else if (annotation.schedule) { want.requiredAPIs.push({ api: "cloudscheduler.googleapis.com", diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 052b4e8c3d2..2f4d99f650d 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -474,7 +474,18 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { taskQueueTrigger: {}, }; - } else if (gcfFunction.labels?.["deployment-callable"]) { + } else if ( + gcfFunction.labels?.["deployment-callable"] || + // NOTE: "deployment-callabled" is a typo we introduced in https://github.com/firebase/firebase-tools/pull/4124. + // More than a month passed before we caught this typo, and we expect many callable functions in production + // to have this typo. It is convenient for users for us to treat the typo-ed label as a valid marker for callable + // function, so we do that here. + // + // The typo will be overwritten as callable functions are re-deployed. Eventually, there may be no callable + // functions with the typo-ed label, but we can't ever be sure. Sadly, we may have to carry this scar for a very long + // time. + gcfFunction.labels?.["deployment-callabled"] + ) { trigger = { callableTrigger: {}, }; @@ -600,7 +611,7 @@ export function functionFromEndpoint( } else { gcfFunction.httpsTrigger = {}; if (backend.isCallableTriggered(endpoint)) { - gcfFunction.labels = { ...gcfFunction.labels, "deployment-callabled": "true" }; + gcfFunction.labels = { ...gcfFunction.labels, "deployment-callable": "true" }; } if (endpoint.securityLevel) { gcfFunction.httpsTrigger.securityLevel = endpoint.securityLevel; diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index 942e88d6bb0..968c3782c10 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -65,6 +65,26 @@ describe("addResourcesToBackend", () => { expect(result).to.deep.equal(expected); }); + it("should handle a callable trigger", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + httpsTrigger: {}, + labels: { + "deployment-callable": "true", + }, + }; + + const result = backend.empty(); + parseTriggers.addResourcesToBackend("project", "nodejs16", trigger, result); + + const expected: backend.Backend = backend.of({ + ...BASIC_ENDPOINT, + callableTrigger: {}, + labels: {}, + }); + expect(result).to.deep.equal(expected); + }); + it("should handle a minimal task queue trigger", () => { const trigger: parseTriggers.TriggerAnnotation = { ...BASIC_TRIGGER, From eb4f3a42166e22de5626d2a8a0c4a0ae18d99603 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 16 Mar 2022 19:43:36 +0000 Subject: [PATCH 0157/1699] 10.3.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index fb0d98e47f5..e9f69c521ff 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.3.0", + "version": "10.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.3.0", + "version": "10.3.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index e94ecd2451f..98ebf8dafd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.3.0", + "version": "10.3.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 132216497fa85d1df3e6a4eaa67a50c0d09af99c Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 16 Mar 2022 19:44:00 +0000 Subject: [PATCH 0158/1699] [firebase-release] Removed change log and reset repo after 10.3.1 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 595cff98428..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes bug where deploying callable functions failed (#4310). From c69b43757248078e0487eb9d9b666a30b1f8e9ed Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 16 Mar 2022 16:34:18 -0400 Subject: [PATCH 0159/1699] Add StorageRulesManagerRegistry to handle rules files for multiple targets (#4281) * Address PR feedback; add JSDocs for new method * Make StorageRulesManager an interface; add StorageRulesManagerRegistry * Modify RulesetProvider to take resource parameter * Rebase; fix lint error * Change emulator arg to take SourceFile only * PR feedback, mostly re: API usage * Don't delete source file or ruleset on stop(); add CHANGELOG * Address PR feedback * Moved Storage rules integration tests under scripts/ (#4309) * Make StorageRulesManager an interface; add StorageRulesManagerRegistry * Modify RulesetProvider to take resource parameter * Change emulator arg to take SourceFile only * PR feedback, mostly re: API usage * Don't delete source file or ruleset on stop(); add CHANGELOG * Address PR feedback * Fix lint * Add test for failing to find rules for given resource --- CHANGELOG.md | 1 + .../rules/manager.test.ts | 117 ++++++++------ src/emulator/storage/apis/firebase.ts | 14 +- src/emulator/storage/files.ts | 5 + src/emulator/storage/index.ts | 29 ++-- src/emulator/storage/rules/config.ts | 28 ++-- src/emulator/storage/rules/manager.ts | 145 +++++++++++++----- src/emulator/storage/rules/runtime.ts | 5 + src/emulator/storage/rules/utils.ts | 6 +- src/emulator/storage/server.ts | 5 +- src/fsutils.ts | 14 +- src/test/emulators/fixtures.ts | 7 + .../emulators/storage/rules/config.spec.ts | 54 +++++-- 13 files changed, 286 insertions(+), 144 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..e3de49fcfc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Adds support for configuration with multiple storage targets (#4281). diff --git a/scripts/storage-emulator-integration/rules/manager.test.ts b/scripts/storage-emulator-integration/rules/manager.test.ts index 3f024145ea0..d2afe88ef3f 100644 --- a/scripts/storage-emulator-integration/rules/manager.test.ts +++ b/scripts/storage-emulator-integration/rules/manager.test.ts @@ -1,92 +1,109 @@ import { expect } from "chai"; -import { tmpdir } from "os"; -import { v4 as uuidv4 } from "uuid"; -import { FirebaseError } from "../../../src/error"; -import { StorageRulesFiles, TIMEOUT_MED } from "../../../src/test/emulators/fixtures"; -import { StorageRulesManager } from "../../../src/emulator/storage/rules/manager"; +import { + createTmpDir, + StorageRulesFiles, + TIMEOUT_LONG, +} from "../../../src/test/emulators/fixtures"; +import { + createStorageRulesManager, + StorageRulesManager, +} from "../../../src/emulator/storage/rules/manager"; import { StorageRulesRuntime } from "../../../src/emulator/storage/rules/runtime"; import { Persistence } from "../../../src/emulator/storage/persistence"; -import { RulesetOperationMethod } from "../../../src/emulator/storage/rules/types"; +import { RulesetOperationMethod, SourceFile } from "../../../src/emulator/storage/rules/types"; +import { isPermitted } from "../../../src/emulator/storage/rules/utils"; +import { readFile } from "../../../src/fsutils"; describe("Storage Rules Manager", function () { const rulesRuntime = new StorageRulesRuntime(); - const rulesManager = new StorageRulesManager(rulesRuntime); + const rules = [ + { resource: "bucket_0", rules: StorageRulesFiles.readWriteIfTrue }, + { resource: "bucket_1", rules: StorageRulesFiles.readWriteIfAuth }, + ]; + const opts = { method: RulesetOperationMethod.GET, file: {}, path: "/b/bucket_2/o/" }; + let rulesManager: StorageRulesManager; // eslint-disable-next-line @typescript-eslint/no-invalid-this - this.timeout(TIMEOUT_MED); + this.timeout(TIMEOUT_LONG); - before(async () => { + beforeEach(async () => { await rulesRuntime.start(); + + rulesManager = createStorageRulesManager(rules, rulesRuntime); + await rulesManager.start(); }); - after(async () => { + afterEach(async () => { rulesRuntime.stop(); - await rulesManager.close(); + await rulesManager.stop(); }); - it("should load ruleset from SourceFile object", async () => { - await rulesManager.setSourceFile(StorageRulesFiles.readWriteIfTrue); - expect(rulesManager.ruleset).not.to.be.undefined; + it("should load multiple rulesets on start", async () => { + const bucket0Ruleset = rulesManager.getRuleset("bucket_0"); + expect(await isPermitted({ ...opts, ruleset: bucket0Ruleset! })).to.be.true; + + const bucket1Ruleset = rulesManager.getRuleset("bucket_1"); + expect(await isPermitted({ ...opts, ruleset: bucket1Ruleset! })).to.be.false; }); - it("should load ruleset from file path", async () => { - // Write rules to file - const fileName = "storage.rules"; - const testDir = `${tmpdir()}/${uuidv4()}`; - const persistence = new Persistence(testDir); - persistence.appendBytes(fileName, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); + it("should load single ruleset on start", async () => { + const otherRulesManager = createStorageRulesManager( + StorageRulesFiles.readWriteIfTrue, + rulesRuntime + ); + await otherRulesManager.start(); - await rulesManager.setSourceFile(`${testDir}/${fileName}`); + const ruleset = otherRulesManager.getRuleset("default"); + expect(await isPermitted({ ...opts, ruleset: ruleset! })).to.be.true; - expect(rulesManager.ruleset).not.to.be.undefined; + await otherRulesManager.stop(); + }); + + it("should load ruleset on update with SourceFile object", async () => { + expect(rulesManager.getRuleset("bucket_2")).to.be.undefined; + await rulesManager.updateSourceFile(StorageRulesFiles.readWriteIfTrue, "bucket_2"); + expect(rulesManager.getRuleset("bucket_2")).not.to.be.undefined; }); it("should set source file", async () => { - await rulesManager.setSourceFile(StorageRulesFiles.readWriteIfTrue); - const opts = { method: RulesetOperationMethod.GET, file: {}, path: "/b/bucket/o/" }; - expect((await rulesManager.ruleset!.verify(opts)).permitted).to.be.true; + await rulesManager.updateSourceFile(StorageRulesFiles.readWriteIfTrue, "bucket_2"); - const issues = await rulesManager.setSourceFile(StorageRulesFiles.readWriteIfAuth); + expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket_2")! })).to.be + .true; + + const issues = await rulesManager.updateSourceFile( + StorageRulesFiles.readWriteIfAuth, + "bucket_2" + ); expect(issues.errors.length).to.equal(0); expect(issues.warnings.length).to.equal(0); - expect((await rulesManager.ruleset!.verify(opts)).permitted).to.be.false; + expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket_2")! })).to.be + .false; }); it("should reload ruleset on changes to source file", async () => { - const opts = { method: RulesetOperationMethod.GET, file: {}, path: "/b/bucket/o/" }; - // Write rules to file const fileName = "storage.rules"; - const testDir = `${tmpdir()}/${uuidv4()}`; + const testDir = createTmpDir("storage-files"); const persistence = new Persistence(testDir); persistence.appendBytes(fileName, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); - await rulesManager.setSourceFile(`${testDir}/${fileName}`); - expect((await rulesManager.ruleset!.verify(opts)).permitted).to.be.true; + const sourceFile = getSourceFile(testDir, fileName); + await rulesManager.updateSourceFile(sourceFile, "bucket_2"); + expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket_2")! })).to.be + .true; // Write new rules to file persistence.deleteFile(fileName); persistence.appendBytes(fileName, Buffer.from(StorageRulesFiles.readWriteIfAuth.content)); - await rulesManager.setSourceFile(`${testDir}/${fileName}`); - expect((await rulesManager.ruleset!.verify(opts)).permitted).to.be.false; - }); - - it("should throw FirebaseError when attempting to set invalid source file", async () => { - const invalidFileName = "foo"; - await expect(rulesManager.setSourceFile(invalidFileName)).to.be.rejectedWith( - FirebaseError, - `File not found: ${invalidFileName}` - ); - }); - - it("should delete ruleset when storage manager is closed", async () => { - await rulesManager.setSourceFile(StorageRulesFiles.readWriteIfTrue); - expect(rulesManager.ruleset).not.to.be.undefined; - - await rulesManager.close(); - expect(rulesManager.ruleset).to.be.undefined; + expect(await isPermitted(opts)).to.be.false; }); }); + +function getSourceFile(testDir: string, fileName: string): SourceFile { + const filePath = `${testDir}/${fileName}`; + return { name: filePath, content: readFile(filePath) }; +} diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 98338784ff2..85dc33837a1 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -64,8 +64,11 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }); } - firebaseStorageAPI.use((req, res, next) => { - if (!emulator.rules) { + // Automatically create a bucket for any route which uses a bucket + firebaseStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => { + const bucketId = req.params[0]; + storageLayer.createBucket(bucketId); + if (!emulator.rulesManager.getRuleset(bucketId)) { EmulatorLogger.forEmulator(Emulators.STORAGE).log( "WARN", "Permission denied because no Storage ruleset is currently loaded, check your rules for syntax errors." @@ -77,13 +80,6 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }, }); } - - next(); - }); - - // Automatically create a bucket for any route which uses a bucket - firebaseStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => { - storageLayer.createBucket(req.params[0]); next(); }); diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 0d68a529cc9..a44ee9b5957 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -161,6 +161,7 @@ export class StorageLayer { if (!authorized) { authorized = await this._rulesValidator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), + request.bucketId, RulesetOperationMethod.GET, { before: metadata?.asRulesResource() }, request.authorization @@ -213,6 +214,7 @@ export class StorageLayer { skipAuth || (await this._rulesValidator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), + request.bucketId, RulesetOperationMethod.DELETE, { before: storedMetadata?.asRulesResource() }, request.authorization @@ -267,6 +269,7 @@ export class StorageLayer { skipAuth || (await this._rulesValidator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), + request.bucketId, RulesetOperationMethod.UPDATE, { before: storedMetadata?.asRulesResource(), @@ -315,6 +318,7 @@ export class StorageLayer { skipAuth || (await this._rulesValidator.validate( ["b", upload.bucketId, "o", upload.objectId].join("/"), + upload.bucketId, RulesetOperationMethod.CREATE, { after: metadata?.asRulesResource() }, upload.authorization @@ -399,6 +403,7 @@ export class StorageLayer { skipAuth || (await this._rulesValidator.validate( ["b", request.bucketId, "o", request.prefix].join("/"), + request.bucketId, RulesetOperationMethod.LIST, {}, request.authorization diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index f7600d477d5..b98d0724384 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -5,8 +5,8 @@ import { EmulatorInfo, EmulatorInstance, Emulators } from "../types"; import { createApp } from "./server"; import { StorageLayer } from "./files"; import { EmulatorLogger } from "../emulatorLogger"; -import { StorageRulesManager } from "./rules/manager"; -import { StorageRulesetInstance, StorageRulesRuntime, StorageRulesIssues } from "./rules/runtime"; +import { createStorageRulesManager, StorageRulesManager } from "./rules/manager"; +import { StorageRulesRuntime } from "./rules/runtime"; import { SourceFile } from "./rules/types"; import express = require("express"); import { getAdminCredentialValidator, getRulesValidator } from "./rules/utils"; @@ -15,14 +15,17 @@ import { UploadService } from "./upload"; export type RulesConfig = { resource: string; - rules: string; + rules: SourceFile; }; export interface StorageEmulatorArgs { projectId: string; port?: number; host?: string; - rules: RulesConfig[]; + + // Either a single set of rules to be applied to all resources or a mapping of resource to rules + rules: SourceFile | RulesConfig[]; + auto_download?: boolean; } @@ -39,11 +42,11 @@ export class StorageEmulator implements EmulatorInstance { constructor(private args: StorageEmulatorArgs) { this._rulesRuntime = new StorageRulesRuntime(); - this._rulesManager = new StorageRulesManager(this._rulesRuntime); + this._rulesManager = createStorageRulesManager(this.args.rules, this._rulesRuntime); this._persistence = new Persistence(this.getPersistenceTmpDir()); this._storageLayer = new StorageLayer( args.projectId, - getRulesValidator(() => this.rules), + getRulesValidator((resource: string) => this._rulesManager.getRuleset(resource)), getAdminCredentialValidator(), this._persistence ); @@ -58,8 +61,8 @@ export class StorageEmulator implements EmulatorInstance { return this._uploadService; } - get rules(): StorageRulesetInstance | undefined { - return this._rulesManager.ruleset; + get rulesManager(): StorageRulesManager { + return this._rulesManager; } get logger(): EmulatorLogger { @@ -75,9 +78,7 @@ export class StorageEmulator implements EmulatorInstance { async start(): Promise { const { host, port } = this.getInfo(); await this._rulesRuntime.start(this.args.auto_download); - - // TODO(hsinpei): set source file for multiple resources - await this._rulesManager.setSourceFile(this.args.rules[0].rules); + await this._rulesManager.start(); this._app = await createApp(this.args.projectId, this); const server = this._app.listen(port, host); this.destroyServer = utils.createDestroyer(server); @@ -87,13 +88,9 @@ export class StorageEmulator implements EmulatorInstance { // No-op } - async setRules(rules: SourceFile): Promise { - return this._rulesManager.setSourceFile(rules); - } - async stop(): Promise { await this._persistence.deleteAll(); - await this._rulesManager.close(); + await this._rulesManager.stop(); return this.destroyServer ? this.destroyServer() : Promise.resolve(); } diff --git a/src/emulator/storage/rules/config.ts b/src/emulator/storage/rules/config.ts index 8fc95eeda27..3786ecae718 100644 --- a/src/emulator/storage/rules/config.ts +++ b/src/emulator/storage/rules/config.ts @@ -1,12 +1,24 @@ import { RulesConfig } from ".."; import { FirebaseError } from "../../../error"; +import { readFile } from "../../../fsutils"; import { Options } from "../../../options"; +import { SourceFile } from "./types"; -function getAbsoluteRulesPath(rules: string, options: Options): string { - return options.config.path(rules); +function getSourceFile(rules: string, options: Options): SourceFile { + const path = options.config.path(rules); + return { name: path, content: readFile(path) }; } -export function getStorageRulesConfig(projectId: string, options: Options): RulesConfig[] { +/** + * Parses rules file for each target specified in the storage config under {@link options}. + * @returns The rules file path if the storage config does not specify a target and an array + * of project resources and their corresponding rules files otherwise. + * @throws {FirebaseError} if storage config is missing or rules file is missing or invalid. + */ +export function getStorageRulesConfig( + projectId: string, + options: Options +): SourceFile | RulesConfig[] { const storageConfig = options.config.data.storage; if (!storageConfig) { throw new FirebaseError( @@ -14,7 +26,7 @@ export function getStorageRulesConfig(projectId: string, options: Options): Rule ); } - // Single resource + // No target specified if (!Array.isArray(storageConfig)) { if (!storageConfig.rules) { throw new FirebaseError( @@ -22,12 +34,10 @@ export function getStorageRulesConfig(projectId: string, options: Options): Rule ); } - // TODO(hsinpei): set default resource - const resource = "default"; - return [{ resource, rules: getAbsoluteRulesPath(storageConfig.rules, options) }]; + return getSourceFile(storageConfig.rules, options); } - // Multiple resources + // Multiple targets const results: RulesConfig[] = []; const { rc } = options; for (const targetConfig of storageConfig) { @@ -36,7 +46,7 @@ export function getStorageRulesConfig(projectId: string, options: Options): Rule } rc.requireTarget(projectId, "storage", targetConfig.target); rc.target(projectId, "storage", targetConfig.target).forEach((resource: string) => { - results.push({ resource, rules: getAbsoluteRulesPath(targetConfig.rules, options) }); + results.push({ resource, rules: getSourceFile(targetConfig.rules, options) }); }); } return results; diff --git a/src/emulator/storage/rules/manager.ts b/src/emulator/storage/rules/manager.ts index 1f599cf8a45..ddceebe475a 100644 --- a/src/emulator/storage/rules/manager.ts +++ b/src/emulator/storage/rules/manager.ts @@ -1,54 +1,87 @@ import * as chokidar from "chokidar"; -import * as fs from "fs"; import { EmulatorLogger } from "../../emulatorLogger"; import { Emulators } from "../../types"; -import { FirebaseError } from "../../../error"; import { SourceFile } from "./types"; import { StorageRulesIssues, StorageRulesRuntime, StorageRulesetInstance } from "./runtime"; +import { RulesConfig } from ".."; /** - * Loads and maintains a {@link StorageRulesetInstance} for a given source file. Listens for - * changes to the file and updates the ruleset accordingly. + * Keeps track of rules source file(s) and generated ruleset(s), either one for all storage + * resources or different rules for different resources. + * + * Example usage: + * + * ``` + * const rulesManager = createStorageRulesManager(initialRules); + * rulesManager.start(); + * rulesManager.updateSourceFile(newRules); + * rulesManager.stop(); + * ``` */ -export class StorageRulesManager { - private _sourceFile?: SourceFile; +export interface StorageRulesManager { + /** Sets source file for each resource using the most recent rules. */ + start(): Promise; + + /** + * Retrieves the generated ruleset for the resource. Returns undefined if the resource is invalid + * or if the ruleset has not been generated. + */ + getRuleset(resource: string): StorageRulesetInstance | undefined; + + /** + * Updates the source file and, correspondingly, the file watcher and ruleset for the resource. + * Returns an array of errors and/or warnings that arise from loading the ruleset. + */ + updateSourceFile(rules: SourceFile, resource: string): Promise; + + /** Removes listeners from all files for all managed resources. */ + stop(): Promise; +} + +/** + * Creates either a {@link DefaultStorageRulesManager} to manage rules for all resources or a + * {@link ResourceBasedStorageRulesManager} for a subset of them, keyed by resource name. + */ +export function createStorageRulesManager( + rules: SourceFile | RulesConfig[], + runtime: StorageRulesRuntime +): StorageRulesManager { + return Array.isArray(rules) + ? new ResourceBasedStorageRulesManager(rules, runtime) + : new DefaultStorageRulesManager(rules, runtime); +} + +/** + * Maintains a {@link StorageRulesetInstance} for a given source file. Listens for changes to the + * file and updates the ruleset accordingly. + */ +class DefaultStorageRulesManager implements StorageRulesManager { + private _rules: SourceFile; private _ruleset?: StorageRulesetInstance; private _watcher = new chokidar.FSWatcher(); private _logger = EmulatorLogger.forEmulator(Emulators.STORAGE); - constructor(private _runtime: StorageRulesRuntime) {} + constructor(_rules: SourceFile, private _runtime: StorageRulesRuntime) { + this._rules = _rules; + } - get ruleset(): StorageRulesetInstance | undefined { - return this._ruleset; + start(): Promise { + return this.updateSourceFile(this._rules); } - /** - * Updates the source file and, correspondingly, the file watcher and ruleset. - * @throws {FirebaseError} if file path is invalid. - */ - public async setSourceFile(rules: SourceFile | string): Promise { - const prevRulesFile = this._sourceFile?.name; - let rulesFile: string; - if (typeof rules === "string") { - this._sourceFile = { name: rules, content: readSourceFile(rules) }; - rulesFile = rules; - } else { - // Allow invalid file path here for testing - this._sourceFile = rules; - rulesFile = rules.name; - } + getRuleset(): StorageRulesetInstance | undefined { + return this._ruleset; + } + async updateSourceFile(rules: SourceFile): Promise { + const prevRulesFile = this._rules.name; + this._rules = rules; const issues = await this.loadRuleset(); - this.updateWatcher(rulesFile, prevRulesFile); + this.updateWatcher(rules.name, prevRulesFile); return issues; } - /** - * Deletes source file, ruleset, and removes listeners from all files. - */ - public async close(): Promise { - delete this._sourceFile; - delete this._ruleset; + async stop(): Promise { await this._watcher.close(); } @@ -75,14 +108,13 @@ export class StorageRulesManager { } private async loadRuleset(): Promise { - const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this._sourceFile!] }); + const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this._rules] }); if (ruleset) { this._ruleset = ruleset; return issues; } - delete this._ruleset; issues.all.forEach((issue: string) => { try { const parsedIssue = JSON.parse(issue); @@ -100,13 +132,46 @@ export class StorageRulesManager { } } -function readSourceFile(fileName: string): string { - try { - return fs.readFileSync(fileName).toString(); - } catch (error: any) { - if (error.code === "ENOENT") { - throw new FirebaseError(`File not found: ${fileName}`); +/** + * Maintains a mapping from storage resource to {@link DefaultStorageRulesManager} and + * directs calls to the appropriate instance. + */ +class ResourceBasedStorageRulesManager implements StorageRulesManager { + private _rulesManagers = new Map(); + + constructor(_rulesConfig: RulesConfig[], private _runtime: StorageRulesRuntime) { + for (const { resource, rules } of _rulesConfig) { + this.createRulesManager(resource, rules); } - throw error; + } + + async start(): Promise { + const allIssues = new StorageRulesIssues(); + for (const rulesManager of this._rulesManagers.values()) { + allIssues.extend(await rulesManager.start()); + } + return allIssues; + } + + getRuleset(resource: string): StorageRulesetInstance | undefined { + return this._rulesManagers.get(resource)?.getRuleset(); + } + + updateSourceFile(rules: SourceFile, resource: string): Promise { + const rulesManager = + this._rulesManagers.get(resource) || this.createRulesManager(resource, rules); + return rulesManager.updateSourceFile(rules); + } + + async stop(): Promise { + await Promise.all( + Array.from(this._rulesManagers.values(), async (rulesManager) => await rulesManager.stop()) + ); + } + + private createRulesManager(resource: string, rules: SourceFile): DefaultStorageRulesManager { + const rulesManager = new DefaultStorageRulesManager(rules, this._runtime); + this._rulesManagers.set(resource, rulesManager); + return rulesManager; } } diff --git a/src/emulator/storage/rules/runtime.ts b/src/emulator/storage/rules/runtime.ts index 1ebdefcb3a9..64bffd9d12e 100644 --- a/src/emulator/storage/rules/runtime.ts +++ b/src/emulator/storage/rules/runtime.ts @@ -84,6 +84,11 @@ export class StorageRulesIssues { exist(): boolean { return !!(this.errors.length || this.warnings.length); } + + extend(other: StorageRulesIssues): void { + this.errors.push(...other.errors); + this.warnings.push(...other.warnings); + } } export class StorageRulesRuntime { diff --git a/src/emulator/storage/rules/utils.ts b/src/emulator/storage/rules/utils.ts index 0bf209c253e..e6b9d3b4b50 100644 --- a/src/emulator/storage/rules/utils.ts +++ b/src/emulator/storage/rules/utils.ts @@ -14,6 +14,7 @@ export type RulesVariableOverrides = { export interface RulesValidator { validate( path: string, + bucketId: string, method: RulesetOperationMethod, variableOverrides: RulesVariableOverrides, authorization?: string @@ -26,7 +27,7 @@ export interface AdminCredentialValidator { } /** Provider for Storage security rules. */ -export type RulesetProvider = () => StorageRulesetInstance | undefined; +export type RulesetProvider = (resource: string) => StorageRulesetInstance | undefined; /** * Returns a validator that pulls a Ruleset from a {@link RulesetProvider} on each run. @@ -35,12 +36,13 @@ export function getRulesValidator(rulesetProvider: RulesetProvider): RulesValida return { validate: async ( path: string, + bucketId: string, method: RulesetOperationMethod, variableOverrides: RulesVariableOverrides, authorization?: string ) => { return await isPermitted({ - ruleset: rulesetProvider(), + ruleset: rulesetProvider(bucketId), file: variableOverrides, path, method, diff --git a/src/emulator/storage/server.ts b/src/emulator/storage/server.ts index 49af5e0c5e0..c8a3e3c1114 100644 --- a/src/emulator/storage/server.ts +++ b/src/emulator/storage/server.ts @@ -92,7 +92,10 @@ export function createApp( const name = file.name; const content = file.content; - const issues = await emulator.setRules({ name, content }); + const issues = await emulator.rulesManager.updateSourceFile( + { name, content }, + req.params.bucketId + ); if (issues.errors.length > 0) { res.status(400).json({ diff --git a/src/fsutils.ts b/src/fsutils.ts index ef32aa64c9a..c1071f22e6f 100644 --- a/src/fsutils.ts +++ b/src/fsutils.ts @@ -1,4 +1,5 @@ -import { statSync } from "fs"; +import { readFileSync, statSync } from "fs"; +import { FirebaseError } from "./error"; export function fileExistsSync(path: string): boolean { try { @@ -15,3 +16,14 @@ export function dirExistsSync(path: string): boolean { return false; } } + +export function readFile(path: string): string { + try { + return readFileSync(path).toString(); + } catch (e: unknown) { + if ((e as NodeJS.ErrnoException).code === "ENOENT") { + throw new FirebaseError(`File not found: ${path}`); + } + throw e; + } +} diff --git a/src/test/emulators/fixtures.ts b/src/test/emulators/fixtures.ts index b2e99928425..5d031f2921d 100644 --- a/src/test/emulators/fixtures.ts +++ b/src/test/emulators/fixtures.ts @@ -1,8 +1,15 @@ +import * as fs from "fs"; +import * as path from "path"; +import { tmpdir } from "os"; import { findModuleRoot, FunctionsRuntimeBundle } from "../../emulator/functionsEmulatorShared"; export const TIMEOUT_LONG = 10000; export const TIMEOUT_MED = 5000; +export function createTmpDir(dirName: string) { + return fs.mkdtempSync(path.join(tmpdir(), dirName)); +} + export const MODULE_ROOT = findModuleRoot("firebase-tools", __dirname); export const FunctionRuntimeBundles: { [key: string]: FunctionsRuntimeBundle } = { onCreate: { diff --git a/src/test/emulators/storage/rules/config.spec.ts b/src/test/emulators/storage/rules/config.spec.ts index 09f1d66bc82..09573bd6f50 100644 --- a/src/test/emulators/storage/rules/config.spec.ts +++ b/src/test/emulators/storage/rules/config.spec.ts @@ -1,39 +1,43 @@ import * as path from "path"; import { expect } from "chai"; -import { readFileSync } from "fs-extra"; -import { tmpdir } from "os"; -import { v4 as uuidv4 } from "uuid"; -import { Config } from "../../../../config"; import { Options } from "../../../../options"; import { RC } from "../../../../rc"; import { getStorageRulesConfig } from "../../../../emulator/storage/rules/config"; -import { StorageRulesFiles } from "../../fixtures"; +import { createTmpDir, StorageRulesFiles } from "../../fixtures"; import { Persistence } from "../../../../emulator/storage/persistence"; import { FirebaseError } from "../../../../error"; +import { RulesConfig } from "../../../../emulator/storage"; +import { SourceFile } from "../../../../emulator/storage/rules/types"; const PROJECT_ID = "test-project"; describe("Storage Rules Config", () => { - const tmpDir = `${tmpdir()}/${uuidv4()}`; + const tmpDir = createTmpDir("storage-files"); const persistence = new Persistence(tmpDir); const resolvePath = (fileName: string) => path.resolve(tmpDir, fileName); it("should parse rules config for single target", () => { const rulesFile = "storage.rules"; - persistence.appendBytes(rulesFile, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); + const rulesContent = Buffer.from(StorageRulesFiles.readWriteIfTrue.content); + persistence.appendBytes(rulesFile, rulesContent); const config = getOptions({ data: { storage: { rules: rulesFile } }, path: resolvePath, }); - const result = getStorageRulesConfig(PROJECT_ID, config); + const result = getStorageRulesConfig(PROJECT_ID, config) as SourceFile; - expect(result.length).to.equal(1); - expect(result[0].rules).to.equal(`${tmpDir}/storage.rules`); + expect(result.name).to.equal(`${tmpDir}/storage.rules`); + expect(result.content).to.contain("allow read, write: if true"); }); it("should parse rules file for multiple targets", () => { + const mainRulesContent = Buffer.from(StorageRulesFiles.readWriteIfTrue.content); + const otherRulesContent = Buffer.from(StorageRulesFiles.readWriteIfAuth.content); + persistence.appendBytes("storage_main.rules", mainRulesContent); + persistence.appendBytes("storage_other.rules", otherRulesContent); + const config = getOptions({ data: { storage: [ @@ -43,15 +47,24 @@ describe("Storage Rules Config", () => { }, path: resolvePath, }); - config.rc.applyTarget(PROJECT_ID, "storage", "main", ["bucket_1", "bucket_2"]); - config.rc.applyTarget(PROJECT_ID, "storage", "other", ["bucket_3"]); + config.rc.applyTarget(PROJECT_ID, "storage", "main", ["bucket_0", "bucket_1"]); + config.rc.applyTarget(PROJECT_ID, "storage", "other", ["bucket_2"]); - const result = getStorageRulesConfig(PROJECT_ID, config); + const result = getStorageRulesConfig(PROJECT_ID, config) as RulesConfig[]; expect(result.length).to.equal(3); - expect(result[0]).to.eql({ resource: "bucket_1", rules: `${tmpDir}/storage_main.rules` }); - expect(result[1]).to.eql({ resource: "bucket_2", rules: `${tmpDir}/storage_main.rules` }); - expect(result[2]).to.eql({ resource: "bucket_3", rules: `${tmpDir}/storage_other.rules` }); + + expect(result[0].resource).to.eql("bucket_0"); + expect(result[0].rules.name).to.equal(`${tmpDir}/storage_main.rules`); + expect(result[0].rules.content).to.contain("allow read, write: if true"); + + expect(result[1].resource).to.eql("bucket_1"); + expect(result[1].rules.name).to.equal(`${tmpDir}/storage_main.rules`); + expect(result[1].rules.content).to.contain("allow read, write: if true"); + + expect(result[2].resource).to.eql("bucket_2"); + expect(result[2].rules.name).to.equal(`${tmpDir}/storage_other.rules`); + expect(result[2].rules.content).to.contain("allow read, write: if request.auth!=null"); }); it("should throw FirebaseError when storage config is missing", () => { @@ -69,6 +82,15 @@ describe("Storage Rules Config", () => { "Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration" ); }); + + it("should throw FirebaseError when rules file is invalid", () => { + const invalidFileName = "foo"; + const config = getOptions({ data: { storage: { rules: invalidFileName } }, path: resolvePath }); + expect(() => getStorageRulesConfig(PROJECT_ID, config)).to.throw( + FirebaseError, + `File not found: ${resolvePath(invalidFileName)}` + ); + }); }); function getOptions(config: any): Options { From 341e176d5b1e5af1a12960fbacddbc3c1e052bd1 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 16 Mar 2022 17:57:35 -0400 Subject: [PATCH 0160/1699] Remove --require from mocha commands in integration test scripts (#4312) * Remove -require from mocha commands in integration test scripts * Add ui-debug.log to .gitignore --- .gitignore | 1 + scripts/client-integration-tests/run.sh | 6 +----- scripts/emulator-tests/run.sh | 5 +---- scripts/extensions-deploy-tests/run.sh | 6 +----- scripts/extensions-emulator-tests/run.sh | 6 +----- scripts/storage-emulator-integration/run.sh | 6 +----- scripts/triggers-end-to-end-tests/run.sh | 7 +------ 7 files changed, 7 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 9852be09c6a..9e75eba6a61 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ node_modules firebase-debug.log firebase-debug.*.log npm-debug.log +ui-debug.log yarn.lock .npmrc diff --git a/scripts/client-integration-tests/run.sh b/scripts/client-integration-tests/run.sh index 42dba1ee2d5..a46669d2bea 100755 --- a/scripts/client-integration-tests/run.sh +++ b/scripts/client-integration-tests/run.sh @@ -2,8 +2,4 @@ source scripts/set-default-credentials.sh -mocha \ - --require ts-node/register \ - --require source-map-support/register \ - --require src/test/helpers/mocha-bootstrap.ts \ - scripts/client-integration-tests/tests.ts \ No newline at end of file +mocha scripts/client-integration-tests/tests.ts \ No newline at end of file diff --git a/scripts/emulator-tests/run.sh b/scripts/emulator-tests/run.sh index 0c2023c1d5e..8b9a00f46c0 100755 --- a/scripts/emulator-tests/run.sh +++ b/scripts/emulator-tests/run.sh @@ -15,7 +15,4 @@ trap cleanup EXIT cp package.json dev/package.json # Run the tests from the built dev directory. -mocha \ - --require ts-node/register \ - --require src/test/helpers/mocha-bootstrap.ts \ - dev/scripts/emulator-tests/*.spec.* +mocha dev/scripts/emulator-tests/*.spec.* diff --git a/scripts/extensions-deploy-tests/run.sh b/scripts/extensions-deploy-tests/run.sh index a4677b43c29..7b7490b66eb 100755 --- a/scripts/extensions-deploy-tests/run.sh +++ b/scripts/extensions-deploy-tests/run.sh @@ -4,8 +4,4 @@ set -e # Immediately exit on failure # Globally link the CLI for the testing framework ./scripts/npm-link.sh -mocha \ - --require ts-node/register \ - --require source-map-support/register \ - --require src/test/helpers/mocha-bootstrap.ts \ - scripts/extensions-deploy-tests/tests.ts +mocha scripts/extensions-deploy-tests/tests.ts diff --git a/scripts/extensions-emulator-tests/run.sh b/scripts/extensions-emulator-tests/run.sh index 7588dd30382..11aa6919d17 100755 --- a/scripts/extensions-emulator-tests/run.sh +++ b/scripts/extensions-emulator-tests/run.sh @@ -8,8 +8,4 @@ cd scripts/extensions-emulator-tests/greet-the-world npm i cd - # Return to root so that we don't need a relative path for mocha -mocha \ - --require ts-node/register \ - --require source-map-support/register \ - --require src/test/helpers/mocha-bootstrap.ts \ - scripts/extensions-emulator-tests/tests.ts +mocha scripts/extensions-emulator-tests/tests.ts diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index b362338fc96..efc1b79bb74 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -9,8 +9,4 @@ firebase setup:emulators:storage mocha scripts/storage-emulator-integration/rules/*.test.ts -mocha \ - --require ts-node/register \ - --require source-map-support/register \ - --require src/test/helpers/mocha-bootstrap.ts \ - scripts/storage-emulator-integration/tests.ts +mocha scripts/storage-emulator-integration/tests.ts diff --git a/scripts/triggers-end-to-end-tests/run.sh b/scripts/triggers-end-to-end-tests/run.sh index 2399b8996ba..a2dad9bf9ab 100755 --- a/scripts/triggers-end-to-end-tests/run.sh +++ b/scripts/triggers-end-to-end-tests/run.sh @@ -8,11 +8,6 @@ source scripts/set-default-credentials.sh npm install ) -npx mocha \ - --require ts-node/register \ - --require source-map-support/register \ - --require src/test/helpers/mocha-bootstrap.ts \ - --exit \ - scripts/triggers-end-to-end-tests/tests.ts +npx mocha --exit scripts/triggers-end-to-end-tests/tests.ts rm scripts/triggers-end-to-end-tests/functions/package.json From a41686f82876b109901d0269fa0ae7f67728ccd3 Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Wed, 16 Mar 2022 17:24:58 -0700 Subject: [PATCH 0161/1699] Ran generate:auth-api to update schema.ts and apiSpec.js (#4318) --- src/emulator/auth/apiSpec.js | 363 ++++++++++++++++++----------------- src/emulator/auth/schema.ts | 26 +++ 2 files changed, 209 insertions(+), 180 deletions(-) diff --git a/src/emulator/auth/apiSpec.js b/src/emulator/auth/apiSpec.js index fb042263c1a..1f31986b7ce 100644 --- a/src/emulator/auth/apiSpec.js +++ b/src/emulator/auth/apiSpec.js @@ -112,8 +112,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1IssueSamlResponseResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1IssueSamlResponseResponse", }, }, }, @@ -265,8 +264,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SendVerificationCodeResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SendVerificationCodeResponse", }, }, }, @@ -276,8 +274,7 @@ export default { content: { "application/json": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SendVerificationCodeRequest", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SendVerificationCodeRequest", }, }, }, @@ -308,8 +305,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithCustomTokenResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithCustomTokenResponse", }, }, }, @@ -319,8 +315,7 @@ export default { content: { "application/json": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithCustomTokenRequest", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithCustomTokenRequest", }, }, }, @@ -351,8 +346,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithEmailLinkResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithEmailLinkResponse", }, }, }, @@ -393,8 +387,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithGameCenterResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithGameCenterResponse", }, }, }, @@ -404,8 +397,7 @@ export default { content: { "application/json": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithGameCenterRequest", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithGameCenterRequest", }, }, }, @@ -477,8 +469,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithPasswordResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithPasswordResponse", }, }, }, @@ -519,8 +510,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithPhoneNumberResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithPhoneNumberResponse", }, }, }, @@ -530,8 +520,7 @@ export default { content: { "application/json": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithPhoneNumberRequest", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1SignInWithPhoneNumberRequest", }, }, }, @@ -714,8 +703,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1CreateSessionCookieResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1CreateSessionCookieResponse", }, }, }, @@ -856,8 +844,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1BatchDeleteAccountsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1BatchDeleteAccountsResponse", }, }, }, @@ -1251,8 +1238,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1CreateSessionCookieResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1CreateSessionCookieResponse", }, }, }, @@ -1359,8 +1345,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1BatchDeleteAccountsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1BatchDeleteAccountsResponse", }, }, }, @@ -1838,8 +1823,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1GetRecaptchaParamResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1GetRecaptchaParamResponse", }, }, }, @@ -1871,8 +1855,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV1GetSessionCookiePublicKeysResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1GetSessionCookiePublicKeysResponse", }, }, }, @@ -1903,8 +1886,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV2FinalizeMfaEnrollmentResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV2FinalizeMfaEnrollmentResponse", }, }, }, @@ -1914,8 +1896,7 @@ export default { content: { "application/json": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV2FinalizeMfaEnrollmentRequest", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV2FinalizeMfaEnrollmentRequest", }, }, }, @@ -1946,8 +1927,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV2StartMfaEnrollmentResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV2StartMfaEnrollmentResponse", }, }, }, @@ -2027,8 +2007,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitV2FinalizeMfaSignInResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV2FinalizeMfaSignInResponse", }, }, }, @@ -2108,8 +2087,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpsResponse", }, }, }, @@ -2228,8 +2206,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, }, }, @@ -2246,8 +2223,7 @@ export default { }, ], requestBody: { - $ref: - "#/components/requestBodies/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, @@ -2266,8 +2242,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpConfigsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpConfigsResponse", }, }, }, @@ -2344,8 +2319,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, }, }, @@ -2377,8 +2351,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, }, }, @@ -2401,8 +2374,7 @@ export default { }, ], requestBody: { - $ref: - "#/components/requestBodies/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, @@ -2467,8 +2439,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListInboundSamlConfigsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListInboundSamlConfigsResponse", }, }, }, @@ -2647,8 +2618,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListOAuthIdpConfigsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListOAuthIdpConfigsResponse", }, }, }, @@ -3086,8 +3056,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, }, }, @@ -3105,8 +3074,7 @@ export default { }, ], requestBody: { - $ref: - "#/components/requestBodies/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, @@ -3125,8 +3093,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpConfigsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpConfigsResponse", }, }, }, @@ -3167,125 +3134,123 @@ export default { { $ref: "#/components/parameters/upload_protocol" }, ], }, - "/v2/projects/{targetProjectId}/tenants/{tenantId}/defaultSupportedIdpConfigs/{defaultSupportedIdpConfigsId}": { - delete: { - description: - "Delete a default supported Idp configuration for an Identity Toolkit project.", - operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.delete", - responses: { - 200: { - description: "Successful response", - content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, - }, - }, - parameters: [ - { name: "targetProjectId", in: "path", required: true, schema: { type: "string" } }, - { name: "tenantId", in: "path", required: true, schema: { type: "string" } }, - { - name: "defaultSupportedIdpConfigsId", - in: "path", - required: true, - schema: { type: "string" }, + "/v2/projects/{targetProjectId}/tenants/{tenantId}/defaultSupportedIdpConfigs/{defaultSupportedIdpConfigsId}": + { + delete: { + description: + "Delete a default supported Idp configuration for an Identity Toolkit project.", + operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.delete", + responses: { + 200: { + description: "Successful response", + content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, + }, }, - ], - security: [ - { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, - { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, - ], - tags: ["projects"], - }, - get: { - description: - "Retrieve a default supported Idp configuration for an Identity Toolkit project.", - operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.get", - responses: { - 200: { - description: "Successful response", - content: { - "*/*": { - schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + parameters: [ + { name: "targetProjectId", in: "path", required: true, schema: { type: "string" } }, + { name: "tenantId", in: "path", required: true, schema: { type: "string" } }, + { + name: "defaultSupportedIdpConfigsId", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, + { apiKey: [] }, + ], + tags: ["projects"], + }, + get: { + description: + "Retrieve a default supported Idp configuration for an Identity Toolkit project.", + operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.get", + responses: { + 200: { + description: "Successful response", + content: { + "*/*": { + schema: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + }, }, }, }, }, - }, - parameters: [ - { name: "targetProjectId", in: "path", required: true, schema: { type: "string" } }, - { name: "tenantId", in: "path", required: true, schema: { type: "string" } }, - { - name: "defaultSupportedIdpConfigsId", - in: "path", - required: true, - schema: { type: "string" }, - }, - ], - security: [ - { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, - { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, - ], - tags: ["projects"], - }, - patch: { - description: - "Update a default supported Idp configuration for an Identity Toolkit project.", - operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.patch", - responses: { - 200: { - description: "Successful response", - content: { - "*/*": { - schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + parameters: [ + { name: "targetProjectId", in: "path", required: true, schema: { type: "string" } }, + { name: "tenantId", in: "path", required: true, schema: { type: "string" } }, + { + name: "defaultSupportedIdpConfigsId", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, + { apiKey: [] }, + ], + tags: ["projects"], + }, + patch: { + description: + "Update a default supported Idp configuration for an Identity Toolkit project.", + operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.patch", + responses: { + 200: { + description: "Successful response", + content: { + "*/*": { + schema: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + }, }, }, }, }, - }, - parameters: [ - { name: "targetProjectId", in: "path", required: true, schema: { type: "string" } }, - { name: "tenantId", in: "path", required: true, schema: { type: "string" } }, - { - name: "defaultSupportedIdpConfigsId", - in: "path", - required: true, - schema: { type: "string" }, - }, - { - name: "updateMask", - in: "query", - description: - "The update mask applies to the resource. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask", - schema: { type: "string" }, + parameters: [ + { name: "targetProjectId", in: "path", required: true, schema: { type: "string" } }, + { name: "tenantId", in: "path", required: true, schema: { type: "string" } }, + { + name: "defaultSupportedIdpConfigsId", + in: "path", + required: true, + schema: { type: "string" }, + }, + { + name: "updateMask", + in: "query", + description: + "The update mask applies to the resource. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask", + schema: { type: "string" }, + }, + ], + requestBody: { + $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, - ], - requestBody: { - $ref: - "#/components/requestBodies/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, + { apiKey: [] }, + ], + tags: ["projects"], }, - security: [ - { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, - { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + parameters: [ + { $ref: "#/components/parameters/access_token" }, + { $ref: "#/components/parameters/alt" }, + { $ref: "#/components/parameters/callback" }, + { $ref: "#/components/parameters/fields" }, + { $ref: "#/components/parameters/oauth_token" }, + { $ref: "#/components/parameters/prettyPrint" }, + { $ref: "#/components/parameters/quotaUser" }, + { $ref: "#/components/parameters/uploadType" }, + { $ref: "#/components/parameters/upload_protocol" }, ], - tags: ["projects"], }, - parameters: [ - { $ref: "#/components/parameters/access_token" }, - { $ref: "#/components/parameters/alt" }, - { $ref: "#/components/parameters/callback" }, - { $ref: "#/components/parameters/fields" }, - { $ref: "#/components/parameters/oauth_token" }, - { $ref: "#/components/parameters/prettyPrint" }, - { $ref: "#/components/parameters/quotaUser" }, - { $ref: "#/components/parameters/uploadType" }, - { $ref: "#/components/parameters/upload_protocol" }, - ], - }, "/v2/projects/{targetProjectId}/tenants/{tenantId}/inboundSamlConfigs": { post: { description: "Create an inbound SAML configuration for an Identity Toolkit project.", @@ -3331,8 +3296,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListInboundSamlConfigsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListInboundSamlConfigsResponse", }, }, }, @@ -3516,8 +3480,7 @@ export default { content: { "*/*": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListOAuthIdpConfigsResponse", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ListOAuthIdpConfigsResponse", }, }, }, @@ -6050,6 +6013,32 @@ export default { }, type: "object", }, + GoogleCloudIdentitytoolkitAdminV2AllowByDefault: { + description: + "Defines a policy of allowing every region by default and adding disallowed regions to a disallow list.", + properties: { + disallowedRegions: { + description: + "Two letter unicode region codes to disallow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json", + items: { type: "string" }, + type: "array", + }, + }, + type: "object", + }, + GoogleCloudIdentitytoolkitAdminV2AllowlistOnly: { + description: + "Defines a policy of only allowing regions by explicitly adding them to an allowlist.", + properties: { + allowedRegions: { + description: + "Two letter unicode region codes to allow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json", + items: { type: "string" }, + type: "array", + }, + }, + type: "object", + }, GoogleCloudIdentitytoolkitAdminV2Anonymous: { description: "Configuration options related to authenticating an anonymous user.", properties: { @@ -6405,8 +6394,7 @@ export default { defaultSupportedIdpConfigs: { description: "The set of configs.", items: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, type: "array", }, @@ -6695,6 +6683,19 @@ export default { }, type: "object", }, + GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig: { + description: + "Configures the regions where users are allowed to send verification SMS for the project or tenant. This is based on the calling code of the destination phone number.", + properties: { + allowByDefault: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2AllowByDefault", + }, + allowlistOnly: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2AllowlistOnly", + }, + }, + type: "object", + }, GoogleCloudIdentitytoolkitAdminV2SmsTemplate: { description: "The template to use when sending an SMS.", properties: { @@ -6810,6 +6811,9 @@ export default { readOnly: true, type: "string", }, + smsRegionConfig: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig", + }, testPhoneNumbers: { additionalProperties: { type: "string" }, description: @@ -7480,8 +7484,7 @@ export default { content: { "application/json": { schema: { - $ref: - "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig", }, }, }, diff --git a/src/emulator/auth/schema.ts b/src/emulator/auth/schema.ts index d40d54f0603..93d55289947 100644 --- a/src/emulator/auth/schema.ts +++ b/src/emulator/auth/schema.ts @@ -1857,6 +1857,24 @@ export interface components { */ suggestedTimeout?: string; }; + /** + * Defines a policy of allowing every region by default and adding disallowed regions to a disallow list. + */ + GoogleCloudIdentitytoolkitAdminV2AllowByDefault: { + /** + * Two letter unicode region codes to disallow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json + */ + disallowedRegions?: string[]; + }; + /** + * Defines a policy of only allowing regions by explicitly adding them to an allowlist. + */ + GoogleCloudIdentitytoolkitAdminV2AllowlistOnly: { + /** + * Two letter unicode region codes to allow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json + */ + allowedRegions?: string[]; + }; /** * Configuration options related to authenticating an anonymous user. */ @@ -2408,6 +2426,13 @@ export interface components { hashConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2HashConfig"]; phoneNumber?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2PhoneNumber"]; }; + /** + * Configures the regions where users are allowed to send verification SMS for the project or tenant. This is based on the calling code of the destination phone number. + */ + GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig: { + allowByDefault?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2AllowByDefault"]; + allowlistOnly?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2AllowlistOnly"]; + }; /** * The template to use when sending an SMS. */ @@ -2524,6 +2549,7 @@ export interface components { * Output only. Resource name of a tenant. For example: "projects/{project-id}/tenants/{tenant-id}" */ name?: string; + smsRegionConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig"]; /** * A map of pairs that can be used for MFA. The phone number should be in E.164 format (https://www.itu.int/rec/T-REC-E.164/) and a maximum of 10 pairs can be added (error will be thrown once exceeded). */ From 206041a9e225a1cdf0770d8c98d7f9155bf6db26 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 16 Mar 2022 18:19:43 -0700 Subject: [PATCH 0162/1699] Fix bug where callable functions can't be emulated. (#4314) Fixes https://github.com/firebase/firebase-tools/issues/4313 In https://github.com/firebase/firebase-tools/pull/4310, we started to support new type of triggers `callableTrigger` (distinct from `httpTrigger`). Functions Emulator does not support `callableTrigger`. We fix this by converting `callbleTrigger` to `httpTrigger`. This is tech debt - at some point, we should use `endpoint` definitions throughout the emulator (cc @joehan). Also adds integration tests for emulating callable triggers to prevent this kind of breakages in the future. --- CHANGELOG.md | 1 + scripts/integration-helpers/framework.ts | 15 ++++++++ .../functions/index.js | 10 +++++ scripts/triggers-end-to-end-tests/tests.ts | 37 +++++++++++++++++++ src/emulator/functionsEmulatorShared.ts | 3 ++ 5 files changed, 66 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3de49fcfc3..9d977aceaca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Adds support for configuration with multiple storage targets (#4281). +- Fixes bug where callable functions couldn't be emulated (#4314). diff --git a/scripts/integration-helpers/framework.ts b/scripts/integration-helpers/framework.ts index efa478e2861..79e9797392b 100644 --- a/scripts/integration-helpers/framework.ts +++ b/scripts/integration-helpers/framework.ts @@ -263,6 +263,21 @@ export class TriggerEndToEndTest { return fetch(url); } + invokeCallableFunction( + name: string, + body: Record, + zone = FIREBASE_PROJECT_ZONE + ): Promise { + const url = `http://localhost:${this.functionsEmulatorPort}/${[this.project, zone, name].join( + "/" + )}`; + return fetch(url, { + method: "POST", + body: JSON.stringify(body), + headers: { "Content-Type": "application/json" }, + }); + } + writeToRtdb(): Promise { return this.invokeHttpFunction("writeToRtdb"); } diff --git a/scripts/triggers-end-to-end-tests/functions/index.js b/scripts/triggers-end-to-end-tests/functions/index.js index 4338bfd184a..558300d71fb 100644 --- a/scripts/triggers-end-to-end-tests/functions/index.js +++ b/scripts/triggers-end-to-end-tests/functions/index.js @@ -264,6 +264,11 @@ exports.storageMetadataReaction = functions.storage return true; }); +exports.onCall = functions.https.onCall((data) => { + console.log("data", JSON.stringify(data)); + return data; +}); + exports.storagev2archivedreaction = functionsV2.storage.onObjectArchived((cloudevent) => { console.log(STORAGE_FUNCTION_V2_ARCHIVED_LOG); console.log("Object", JSON.stringify(cloudevent.data)); @@ -359,3 +364,8 @@ exports.storagebucketv2metadatareaction = functionsV2.storage.onObjectMetadataUp return true; } ); + +exports.oncallv2 = functionsV2.https.onCall((req) => { + console.log("data", JSON.stringify(req.data)); + return req.data; +}); diff --git a/scripts/triggers-end-to-end-tests/tests.ts b/scripts/triggers-end-to-end-tests/tests.ts index 23a940a3de4..9a70c213f15 100755 --- a/scripts/triggers-end-to-end-tests/tests.ts +++ b/scripts/triggers-end-to-end-tests/tests.ts @@ -428,6 +428,43 @@ describe("storage emulator function triggers", () => { }); }); +describe("onCall function triggers", () => { + let test: TriggerEndToEndTest; + + before(async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + expect(FIREBASE_PROJECT).to.exist.and.not.be.empty; + + const config = readConfig(); + test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, config); + await test.startEmulators(["--only", "functions"]); + }); + + after(async function (this) { + this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + await test.stopEmulators(); + }); + + it("should make a call to v1 callable function", async function (this) { + this.timeout(EMULATOR_TEST_TIMEOUT); + + const response = await test.invokeCallableFunction("onCall", { data: "foobar" }); + expect(response.status).to.equal(200); + const body = await response.json(); + expect(body).to.deep.equal({ result: "foobar" }); + }); + + it("should make a call to v2 callable function", async function (this) { + this.timeout(EMULATOR_TEST_TIMEOUT); + + const response = await test.invokeCallableFunction("oncallv2", { data: "foobar" }); + expect(response.status).to.equal(200); + const body = await response.json(); + expect(body).to.deep.equal({ result: "foobar" }); + }); +}); + describe("import/export end to end", () => { it("should be able to import/export firestore data", async function (this) { this.timeout(2 * TEST_SETUP_TIMEOUT); diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 1f32843bec6..8d4ad621944 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -159,6 +159,9 @@ export function emulatedFunctionsFromEndpoints( // process requires it in this form. Need to work in Firestore emulator for a proper fix... if (backend.isHttpsTriggered(endpoint)) { def.httpsTrigger = endpoint.httpsTrigger; + } else if (backend.isCallableTriggered(endpoint)) { + def.httpsTrigger = {}; + def.labels = { ...def.labels, "deployment-callable": "true" }; } else if (backend.isEventTriggered(endpoint)) { const eventTrigger = endpoint.eventTrigger; if (endpoint.platform === "gcfv1") { From c69ba79a9b755f48d22992d18b81d9e061ebc777 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Wed, 16 Mar 2022 21:58:06 -0400 Subject: [PATCH 0163/1699] Ask for local secret param when collecting user inputs (#4303) * update getParams * Fix update commands * update param asking * Add tests * rename * Rename variables * Fix update bug * Fix tests * PR fixes * mvp * Update askUserForParam.ts * Update paramHelper.ts * refactor writeToManifest * be able to log secrets * pr fixes * write to .secret.local * only write secret if not empty * Fix configure,update,export * fix export * cleanup * add comments fix tests * format * Update manifest.ts * Fix tests * Add tests * PR fixes * Add writeLocalSecrets tests --- src/commands/ext-configure.ts | 11 +- src/commands/ext-export.ts | 9 +- src/commands/ext-install.ts | 4 +- src/commands/ext-update.ts | 4 +- src/deploy/extensions/planner.ts | 23 +++ src/extensions/askUserForParam.ts | 71 ++++++- src/extensions/manifest.ts | 55 ++++- src/test/extensions/askUserForParam.spec.ts | 66 +++++- src/test/extensions/manifest.spec.ts | 210 +++++++++++++++++++- 9 files changed, 415 insertions(+), 38 deletions(-) diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 81dc02fcab4..ebd3d5f0312 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -19,7 +19,7 @@ import * as refs from "../extensions/refs"; import * as manifest from "../extensions/manifest"; import { Options } from "../options"; import { partition } from "../functional"; -import { getBaseParamBindings } from "../extensions/paramHelper"; +import { buildBindingOptionsWithBaseValue, getBaseParamBindings } from "../extensions/paramHelper"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -78,9 +78,9 @@ export default new Command("ext:configure ") }); // Merge with old immutable params. - const newParamValues = { - ...oldParamValues, - ...getBaseParamBindings(mutableParamsBindingOptions), + const newParamOptions = { + ...buildBindingOptionsWithBaseValue(oldParamValues), + ...mutableParamsBindingOptions, }; await manifest.writeToManifest( @@ -88,7 +88,8 @@ export default new Command("ext:configure ") { instanceId, ref: targetRef, - params: newParamValues, + params: newParamOptions, + paramSpecs: extensionVersion.spec.params, }, ], config, diff --git a/src/commands/ext-export.ts b/src/commands/ext-export.ts index 1b58b05f115..425e50b4c9c 100644 --- a/src/commands/ext-export.ts +++ b/src/commands/ext-export.ts @@ -8,6 +8,7 @@ import { } from "../extensions/export"; import { ensureExtensionsApiEnabled } from "../extensions/extensionsHelper"; import * as manifest from "../extensions/manifest"; +import { buildBindingOptionsWithBaseValue } from "../extensions/paramHelper"; import { partition } from "../functional"; import { getProjectNumber } from "../getProjectNumber"; import { logger } from "../logger"; @@ -64,9 +65,15 @@ module.exports = new Command("ext:export") return; } + const manifestSpecs = withRef.map((spec) => ({ + instanceId: spec.instanceId, + ref: spec.ref, + params: buildBindingOptionsWithBaseValue(spec.params), + })); + const existingConfig = manifest.loadConfig(options); await manifest.writeToManifest( - withRef, + manifestSpecs, existingConfig, { nonInteractive: options.nonInteractive, diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 20db914d9d5..b8247a808ab 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -265,7 +265,6 @@ async function installToManifest(options: InstallExtensionOptions): Promise [updateSource]") nonInteractive: options.nonInteractive, instanceId, }); - const newParamBindings = paramHelper.getBaseParamBindings(newParamBindingOptions); await manifest.writeToManifest( [ { instanceId, ref: refs.parse(newExtensionVersion.ref), - params: newParamBindings, + params: newParamBindingOptions, + paramSpecs: newExtensionVersion.spec.params, }, ], config, diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 85ce3a428e1..95918db8805 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -6,7 +6,30 @@ import { FirebaseError } from "../../error"; import { getFirebaseProjectParams, substituteParams } from "../../extensions/extensionsHelper"; import { logger } from "../../logger"; import { readInstanceParam } from "../../extensions/manifest"; +import { ParamBindingOptions } from "../../extensions/paramHelper"; +/** + * Instance spec used by manifest. + * + * Params are passed in ParamBindingOptions so we know the param bindings for + * all environments user has configured. + * + * So far this is only used for writing to the manifest, but in the future + * we want to read manifest into this interface. + */ +export interface ManifestInstanceSpec { + instanceId: string; + params: Record; + ref?: refs.Ref; + paramSpecs?: extensionsApi.Param[]; +} + +// TODO(lihes): Rename this to something like DeploymentInstanceSpec. +/** + * Instance spec used for deploying extensions to firebase project or emulator. + * + * Param bindings are expected to be collapsed from ParamBindingOptions into a Record. + */ export interface InstanceSpec { instanceId: string; ref?: refs.Ref; diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index a9d44d88a88..c9758fed557 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -6,15 +6,25 @@ const { marked } = require("marked"); import { Param, ParamOption, ParamType } from "./extensionsApi"; import * as secretManagerApi from "../gcp/secretManager"; import * as secretsUtils from "./secretsUtils"; -import { logPrefix, substituteParams } from "./extensionsHelper"; +import { confirm, logPrefix, substituteParams } from "./extensionsHelper"; import { convertExtensionOptionToLabeledList, getRandomString, onceWithJoin } from "./utils"; import { logger } from "../logger"; import { promptOnce } from "../prompt"; import * as utils from "../utils"; import { ParamBindingOptions } from "./paramHelper"; +/** + * Location where the secret value is stored. + * + * Visible for testing. + */ +export enum SecretLocation { + CLOUD = 1, + LOCAL, +} + enum SecretUpdateAction { - LEAVE, + LEAVE = 1, SET_NEW, } @@ -109,6 +119,8 @@ export async function askForParam(args: { let valid = false; let response = ""; + let responseForLocal; + let secretLocations: string[] = []; const description = paramSpec.description || ""; const label = paramSpec.label.trim(); logger.info( @@ -150,16 +162,23 @@ export async function askForParam(args: { }, message: "Which options do you want enabled for this parameter? " + - "Press Space to select, then Enter to confirm your choices. " + - "You may select multiple options.", + "Press Space to select, then Enter to confirm your choices. ", choices: convertExtensionOptionToLabeledList(paramSpec.options as ParamOption[]), }); valid = checkResponse(response, paramSpec); break; case ParamType.SECRET: - response = args.reconfiguring - ? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec) - : await promptCreateSecret(args.projectId, args.instanceId, paramSpec); + while (!secretLocations.length) { + secretLocations = await promptSecretLocations(); + } + if (secretLocations.includes(SecretLocation.CLOUD.toString())) { + response = args.reconfiguring + ? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec) + : await promptCreateSecret(args.projectId, args.instanceId, paramSpec); + } + if (secretLocations.includes(SecretLocation.LOCAL.toString())) { + responseForLocal = await promptLocalSecret(args.instanceId, paramSpec); + } valid = true; break; default: @@ -173,7 +192,43 @@ export async function askForParam(args: { valid = checkResponse(response, paramSpec); } } - return { baseValue: response }; + return { baseValue: response, ...(responseForLocal ? { local: responseForLocal } : {}) }; +} + +async function promptSecretLocations(): Promise { + return await promptOnce({ + name: "input", + type: "checkbox", + message: "Where would you like to store your secrets? You must select at least one value", + choices: [ + { + checked: true, + name: "Google Cloud Secret Manager", + // return type of string is not actually enforced, need to manually convert. + value: SecretLocation.CLOUD.toString(), + }, + { + checked: false, + name: "Local file (Only used by Firebase Emulator)", + value: SecretLocation.LOCAL.toString(), + }, + ], + }); +} + +async function promptLocalSecret( + instanceId: string, + paramSpec: Param +): Promise { + utils.logLabeledBullet(logPrefix, "Configure a local secret value for Extensions Emulator"); + const value = await promptOnce({ + name: paramSpec.param, + type: "input", + message: + `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` + + `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`, + }); + return value; } async function promptReconfigureSecret( diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index 7def7ada78b..2b9e3ad6ecf 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -2,13 +2,14 @@ import * as clc from "cli-color"; import * as path from "path"; import * as refs from "./refs"; import { Config } from "../config"; -import { InstanceSpec } from "../deploy/extensions/planner"; +import { InstanceSpec, ManifestInstanceSpec } from "../deploy/extensions/planner"; import { logger } from "../logger"; import { promptOnce } from "../prompt"; -import { readEnvFile } from "./paramHelper"; +import { ParamBindingOptions, readEnvFile } from "./paramHelper"; import { FirebaseError } from "../error"; import * as utils from "../utils"; import { logPrefix } from "./extensionsHelper"; +import { ParamType } from "./extensionsApi"; export const ENV_DIRECTORY = "extensions"; @@ -25,7 +26,7 @@ export const ENV_DIRECTORY = "extensions"; * @param allowOverwrite allows overwriting the entire manifest with the new specs */ export async function writeToManifest( - specs: InstanceSpec[], + specs: ManifestInstanceSpec[], config: Config, options: { nonInteractive: boolean; force: boolean }, allowOverwrite: boolean = false @@ -56,6 +57,48 @@ export async function writeToManifest( writeExtensionsToFirebaseJson(specs, config); await writeEnvFiles(specs, config, options.force); + await writeLocalSecrets(specs, config, options.force); +} + +/** + * Write the secrets in a list of ManifestInstanceSpec into extensions/{instance-id}.secret.local. + * + * Exported for testing. + */ +export async function writeLocalSecrets( + specs: ManifestInstanceSpec[], + config: Config, + force?: boolean +): Promise { + for (const spec of specs) { + if (!spec.paramSpecs) { + continue; + } + + const writeBuffer: Record = {}; + const locallyOverridenSecretParams = spec.paramSpecs.filter( + (p) => p.type === ParamType.SECRET && spec.params[p.param].local + ); + for (const paramSpec of locallyOverridenSecretParams) { + const key = paramSpec.param; + const localValue = spec.params[key].local!; + writeBuffer[key] = localValue; + } + + const content = Object.entries(writeBuffer) + .sort((a, b) => { + return a[0].localeCompare(b[0]); + }) + .map((r) => `${r[0]}=${r[1]}`) + .join("\n"); + if (content) { + await config.askWriteProjectFile( + `extensions/${spec.instanceId}.secret.local`, + content, + force + ); + } + } } /** @@ -104,7 +147,7 @@ export function getInstanceRef(instanceId: string, config: Config): refs.Ref { return refs.parse(ref); } -function writeExtensionsToFirebaseJson(specs: InstanceSpec[], config: Config): void { +function writeExtensionsToFirebaseJson(specs: ManifestInstanceSpec[], config: Config): void { const extensions = config.get("extensions", {}); for (const s of specs) { extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref!); @@ -115,7 +158,7 @@ function writeExtensionsToFirebaseJson(specs: InstanceSpec[], config: Config): v } async function writeEnvFiles( - specs: InstanceSpec[], + specs: ManifestInstanceSpec[], config: Config, force?: boolean ): Promise { @@ -124,7 +167,7 @@ async function writeEnvFiles( .sort((a, b) => { return a[0].localeCompare(b[0]); }) - .map((r) => `${r[0]}=${r[1]}`) + .map((r) => `${r[0]}=${r[1].baseValue}`) .join("\n"); await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force); } diff --git a/src/test/extensions/askUserForParam.spec.ts b/src/test/extensions/askUserForParam.spec.ts index 9f1e953354e..fadf91ad41d 100644 --- a/src/test/extensions/askUserForParam.spec.ts +++ b/src/test/extensions/askUserForParam.spec.ts @@ -6,6 +6,7 @@ import { askForParam, checkResponse, getInquirerDefault, + SecretLocation, } from "../../extensions/askUserForParam"; import * as utils from "../../utils"; import * as prompt from "../../prompt"; @@ -249,34 +250,83 @@ describe("askUserForParam", () => { beforeEach(() => { promptStub = sinon.stub(prompt, "promptOnce"); - promptStub.onCall(0).returns("ABC.123"); - secretExists = sinon.stub(secretManagerApi, "secretExists"); - secretExists.onCall(0).resolves(false); createSecret = sinon.stub(secretManagerApi, "createSecret"); - createSecret.onCall(0).resolves(stubSecret); addVersion = sinon.stub(secretManagerApi, "addVersion"); - addVersion.onCall(0).resolves(stubSecretVersion); - grantRole = sinon.stub(secretsUtils, "grantFirexServiceAgentSecretAdminRole"); + + secretExists.onCall(0).resolves(false); + createSecret.onCall(0).resolves(stubSecret); + addVersion.onCall(0).resolves(stubSecretVersion); grantRole.onCall(0).resolves(undefined); }); afterEach(() => { promptStub.restore(); + secretExists.restore(); + createSecret.restore(); + addVersion.restore(); + grantRole.restore(); }); - it("should keep prompting user until valid input is given", async () => { + it("should return the correct user input for secret stored with Secret Manager", async () => { + promptStub.onCall(0).returns([SecretLocation.CLOUD.toString()]); + promptStub.onCall(1).returns("ABC.123"); + const result = await askForParam({ projectId: "project-id", instanceId: "instance-id", paramSpec: secretSpec, reconfiguring: false, }); - expect(promptStub.calledOnce).to.be.true; + + // prompt for secret storage location, then prompt for secret value + expect(promptStub.calledTwice).to.be.true; + expect(grantRole.calledOnce).to.be.true; + expect(result).to.be.eql({ + baseValue: `projects/${stubSecret.projectId}/secrets/${stubSecret.name}/versions/${stubSecretVersion.versionId}`, + }); + }); + + it("should return the correct user input for secret stored in a local file", async () => { + promptStub.onCall(0).returns([SecretLocation.LOCAL.toString()]); + promptStub.onCall(1).returns("ABC.123"); + + const result = await askForParam({ + projectId: "project-id", + instanceId: "instance-id", + paramSpec: secretSpec, + reconfiguring: false, + }); + // prompt for secret storage location, then prompt for secret value + expect(promptStub.calledTwice).to.be.true; + // Shouldn't make any api calls. + expect(grantRole.calledOnce).to.be.false; + expect(result).to.be.eql({ + baseValue: "", + local: "ABC.123", + }); + }); + + it("should handle cloud & local secret storage at the same time", async () => { + promptStub + .onCall(0) + .returns([SecretLocation.CLOUD.toString(), SecretLocation.LOCAL.toString()]); + promptStub.onCall(1).returns("ABC.123"); + promptStub.onCall(2).returns("LOCAL.ABC.123"); + + const result = await askForParam({ + projectId: "project-id", + instanceId: "instance-id", + paramSpec: secretSpec, + reconfiguring: false, + }); + // prompt for secret storage location, then prompt for cloud secret value, then local + expect(promptStub.calledThrice).to.be.true; expect(grantRole.calledOnce).to.be.true; expect(result).to.be.eql({ baseValue: `projects/${stubSecret.projectId}/secrets/${stubSecret.name}/versions/${stubSecretVersion.versionId}`, + local: "LOCAL.ABC.123", }); }); }); diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts index 6f23b82474a..4a7b6cb41a9 100644 --- a/src/test/extensions/manifest.spec.ts +++ b/src/test/extensions/manifest.spec.ts @@ -8,6 +8,7 @@ import * as refs from "../../extensions/refs"; import { Config } from "../../config"; import * as prompt from "../../prompt"; import { FirebaseError } from "../../error"; +import { ParamType } from "../../extensions/extensionsApi"; /** * Returns a base Config with some extensions data. @@ -112,7 +113,7 @@ describe("manifest", () => { extensionId: "bigquery-export", version: "1.0.0", }, - params: { a: "pikachu", b: "bulbasaur" }, + params: { a: { baseValue: "pikachu" }, b: { baseValue: "bulbasaur" } }, }, { instanceId: "instance-2", @@ -121,7 +122,7 @@ describe("manifest", () => { extensionId: "bigquery-export", version: "2.0.0", }, - params: { a: "eevee", b: "squirtle" }, + params: { a: { baseValue: "eevee" }, b: { baseValue: "squirtle" } }, }, ], generateBaseConfig(), @@ -159,7 +160,7 @@ describe("manifest", () => { extensionId: "bigquery-export", version: "1.0.0", }, - params: { b: "bulbasaur", a: "absol" }, + params: { b: { baseValue: "bulbasaur" }, a: { baseValue: "absol" } }, }, { instanceId: "instance-2", @@ -168,7 +169,7 @@ describe("manifest", () => { extensionId: "bigquery-export", version: "2.0.0", }, - params: { e: "eevee", s: "squirtle" }, + params: { e: { baseValue: "eevee" }, s: { baseValue: "squirtle" } }, }, ], generateBaseConfig(), @@ -209,7 +210,7 @@ describe("manifest", () => { extensionId: "bigquery-export", version: "1.0.0", }, - params: { a: "pikachu", b: "bulbasaur" }, + params: { a: { baseValue: "pikachu" }, b: { baseValue: "bulbasaur" } }, }, { instanceId: "instance-2", @@ -218,7 +219,7 @@ describe("manifest", () => { extensionId: "bigquery-export", version: "2.0.0", }, - params: { a: "eevee", b: "squirtle" }, + params: { a: { baseValue: "eevee" }, b: { baseValue: "squirtle" } }, }, ], generateBaseConfig(), @@ -247,6 +248,203 @@ describe("manifest", () => { }); }); + describe(`${manifest.writeLocalSecrets.name}`, () => { + let askWriteProjectFileStub: sinon.SinonStub; + + beforeEach(() => { + askWriteProjectFileStub = sandbox.stub(Config.prototype, "askWriteProjectFile"); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should write all secret params that have local values", async () => { + await manifest.writeLocalSecrets( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { + a: { baseValue: "base", local: "pikachu" }, + b: { baseValue: "base", local: "bulbasaur" }, + }, + paramSpecs: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.SECRET, + }, + ], + }, + { + instanceId: "instance-2", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "2.0.0", + }, + params: { + a: { baseValue: "base", local: "eevee" }, + b: { baseValue: "base", local: "squirtle" }, + }, + paramSpecs: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.SECRET, + }, + ], + }, + ], + generateBaseConfig(), + true + ); + + expect(askWriteProjectFileStub).to.have.been.calledTwice; + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-1.secret.local", + `a=pikachu\nb=bulbasaur`, + true + ); + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-2.secret.local", + `a=eevee\nb=squirtle`, + true + ); + }); + + it("should write only secret with local values", async () => { + await manifest.writeLocalSecrets( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { + a: { baseValue: "base", local: "pikachu" }, + b: { baseValue: "base" }, + }, + paramSpecs: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.SECRET, + }, + ], + }, + ], + generateBaseConfig(), + true + ); + + expect(askWriteProjectFileStub).to.have.been.calledOnce; + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-1.secret.local", + `a=pikachu`, + true + ); + }); + + it("should write only local values that are ParamType.SECRET", async () => { + await manifest.writeLocalSecrets( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { + a: { baseValue: "base", local: "pikachu" }, + b: { baseValue: "base", local: "bulbasaur" }, + }, + paramSpecs: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, + ], + generateBaseConfig(), + true + ); + + expect(askWriteProjectFileStub).to.have.been.calledOnce; + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-1.secret.local", + `a=pikachu`, + true + ); + }); + + it("should not write the file if there's no matching params", async () => { + await manifest.writeLocalSecrets( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { + // No local values + a: { baseValue: "base" }, + b: { baseValue: "base" }, + }, + paramSpecs: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, + ], + generateBaseConfig(), + true + ); + + expect(askWriteProjectFileStub).to.not.have.been.called; + }); + }); + describe("readParams", () => { let readEnvFileStub: sinon.SinonStub; const testProjectDir = "test"; From fa8a448c743677d9e75898d07722f667cd57f6b6 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 17 Mar 2022 08:51:25 -0700 Subject: [PATCH 0164/1699] remove unused prepareUpload (#4316) --- src/prepareUpload.js | 55 -------------------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 src/prepareUpload.js diff --git a/src/prepareUpload.js b/src/prepareUpload.js deleted file mode 100644 index 18cefe6a796..00000000000 --- a/src/prepareUpload.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; - -var fs = require("fs"); -var path = require("path"); - -var tar = require("tar"); -var tmp = require("tmp"); - -var { listFiles } = require("./listFiles"); -var { FirebaseError } = require("./error"); -var fsutils = require("./fsutils"); - -module.exports = function (options) { - var hostingConfig = options.config.get("hosting"); - var publicDir = options.config.path(hostingConfig.public); - var indexPath = path.join(publicDir, "index.html"); - - var tmpFile = tmp.fileSync({ - prefix: "firebase-upload-", - postfix: ".tar.gz", - }); - var manifest = listFiles(publicDir, hostingConfig.ignore); - - return tar - .c( - { - gzip: true, - file: tmpFile.name, - cwd: publicDir, - prefix: "public", - follow: true, - noDirRecurse: true, - portable: true, - }, - manifest.slice(0) - ) - .then(function () { - var stats = fs.statSync(tmpFile.name); - return { - file: tmpFile.name, - stream: fs.createReadStream(tmpFile.name), - manifest: manifest, - foundIndex: fsutils.fileExistsSync(indexPath), - size: stats.size, - }; - }) - .catch(function (err) { - return Promise.reject( - new FirebaseError("There was an issue preparing Hosting files for upload.", { - original: err, - exit: 2, - }) - ); - }); -}; From f4dfb5c8a1b534f9e1782e908f736910e697a568 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 17 Mar 2022 09:43:04 -0700 Subject: [PATCH 0165/1699] Resolve latest to a real version number (#4311) * Resolve latest to a real version number * undo shrinkwrap changes * removing shrinkwrap changes --- scripts/emulator-tests/functionsEmulator.spec.ts | 2 +- src/deploy/extensions/planner.ts | 12 +++++++++--- src/emulator/extensionsEmulator.ts | 5 ++++- src/test/deploy/extensions/planner.spec.ts | 8 ++++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index 3996083f264..943dcc46ca7 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -786,7 +786,7 @@ describe("FunctionsEmulator-Hub", () => { .then((res) => { expect(res.body.var).to.eql("localhost:9090"); }); - }); + }).timeout(5000); it("should set FIREBASE_AUTH_EMULATOR_HOST when the emulator is running", async () => { emulatorRegistryStub.withArgs(Emulators.AUTH).returns({ diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 95918db8805..a0d81dc42d4 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -152,11 +152,17 @@ export async function want(args: { * @param version a semver or semver range */ export async function resolveVersion(ref: refs.Ref): Promise { - if (!ref.version || ref.version === "latest") { - return "latest"; - } const extensionRef = refs.toExtensionRef(ref); const versions = await extensionsApi.listExtensionVersions(extensionRef); + if (versions.length === 0) { + throw new FirebaseError(`No versions found for ${extensionRef}`); + } + if (!ref.version || ref.version === "latest") { + return versions + .map((ev) => ev.spec.version) + .sort(semver.compare) + .pop()!; + } const maxSatisfying = semver.maxSatisfying( versions.map((ev) => ev.spec.version), ref.version diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index b25bab8fd17..f4cf1d1a2a6 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -110,7 +110,10 @@ export class ExtensionsEmulator { "./functions/package.json", "./functions/node_modules", ]; - + // If the directory isn't found, no need to check for files or print errors. + if (!fs.existsSync(args.path)) { + return false; + } for (const requiredFile of requiredFiles) { const f = path.join(args.path, requiredFile); if (!fs.existsSync(f)) { diff --git a/src/test/deploy/extensions/planner.spec.ts b/src/test/deploy/extensions/planner.spec.ts index 77a2aaa6dfa..cc7552a8c06 100644 --- a/src/test/deploy/extensions/planner.spec.ts +++ b/src/test/deploy/extensions/planner.spec.ts @@ -52,14 +52,14 @@ describe("Extensions Deployment Planner", () => { err: false, }, { - description: "should allow latest", + description: "should resolve latest to a version", in: "latest", - out: "latest", + out: "0.2.0", err: false, }, { - description: "should default to latest", - out: "latest", + description: "should default to latest version", + out: "0.2.0", err: false, }, { From d498a563265905231dccd1a8a3ec321dc51cca10 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Thu, 17 Mar 2022 12:59:07 -0400 Subject: [PATCH 0166/1699] Minor fixes for ext:* --local (#4319) * Delete secret.local when uninstalling extensions locally * Fix bug where local values are missed when updating * Fix tests --- src/config.ts | 4 ++++ src/extensions/manifest.ts | 14 ++++++++++++-- src/extensions/paramHelper.ts | 8 +++++--- src/test/extensions/manifest.spec.ts | 3 +++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/config.ts b/src/config.ts index 7b00e4d4172..0209dc51b3f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -203,6 +203,10 @@ export class Config { fs.writeFileSync(this.path(p), content, "utf8"); } + projectFileExists(p: string): boolean { + return fs.existsSync(this.path(p)); + } + deleteProjectFile(p: string) { fs.removeSync(this.path(p)); } diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index 2b9e3ad6ecf..19efdff737e 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -117,8 +117,18 @@ export function removeFromManifest(instanceId: string, config: Config) { config.deleteProjectFile(`extensions/${instanceId}.env`); logger.info(`Removed extension instance environment config extensions/${instanceId}.env`); - config.deleteProjectFile(`extensions/${instanceId}.env.local`); - logger.info(`Removed extension instance environment config extensions/${instanceId}.env.local`); + if (config.projectFileExists(`extensions/${instanceId}.env.local`)) { + config.deleteProjectFile(`extensions/${instanceId}.env.local`); + logger.info( + `Removed extension instance local environment config extensions/${instanceId}.env.local` + ); + } + if (config.projectFileExists(`extensions/${instanceId}.secret.local`)) { + config.deleteProjectFile(`extensions/${instanceId}.secret.local`); + logger.info( + `Removed extension instance local secret config extensions/${instanceId}.secret.local` + ); + } // TODO(lihes): Remove all project specific env files. } diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 203c8e4d340..9297c85feea 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -191,6 +191,8 @@ export async function promptForNewParams(args: { projectId: string; instanceId: string; }): Promise<{ [option: string]: ParamBindingOptions }> { + const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams); + const firebaseProjectParams = await getFirebaseProjectParams(args.projectId); const comparer = (param1: extensionsApi.Param, param2: extensionsApi.Param) => { return param1.type === param2.type && param1.param === param2.param; @@ -217,7 +219,7 @@ export async function promptForNewParams(args: { logger.info("The following params will no longer be used:"); paramsDiffDeletions.forEach((param) => { logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`)); - delete args.currentParams[param.param.toUpperCase()]; + delete newParamBindingOptions[param.param.toUpperCase()]; }); } if (paramsDiffAdditions.length) { @@ -229,11 +231,11 @@ export async function promptForNewParams(args: { paramSpec: param, reconfiguring: false, }); - args.currentParams[param.param] = chosenValue.baseValue; + newParamBindingOptions[param.param] = chosenValue; } } - return buildBindingOptionsWithBaseValue(args.currentParams); + return newParamBindingOptions; } function getParamsFromFile(args: { diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts index 4a7b6cb41a9..2ec626235d4 100644 --- a/src/test/extensions/manifest.spec.ts +++ b/src/test/extensions/manifest.spec.ts @@ -68,9 +68,12 @@ describe("manifest", () => { describe(`${manifest.removeFromManifest.name}`, () => { let deleteProjectFileStub: sinon.SinonStub; let writeProjectFileStub: sinon.SinonStub; + let projectFileExistsStub: sinon.SinonStub; beforeEach(() => { deleteProjectFileStub = sandbox.stub(Config.prototype, "deleteProjectFile"); writeProjectFileStub = sandbox.stub(Config.prototype, "writeProjectFile"); + projectFileExistsStub = sandbox.stub(Config.prototype, "projectFileExists"); + projectFileExistsStub.returns(true); }); afterEach(() => { From 40d871aed9a991e0fe97fa17b11a47095b5ce46d Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 17 Mar 2022 10:12:27 -0700 Subject: [PATCH 0167/1699] typescript requireConfig (#4317) * typescript requireConfig * need to throw custom errors * remove await * guess it has to still be a promise... --- src/commands/deploy.js | 2 +- src/commands/experimental-functions-shell.js | 2 +- src/commands/functions-config-export.ts | 2 +- src/commands/functions-shell.js | 2 +- src/commands/hosting-channel-create.ts | 2 +- src/commands/hosting-channel-delete.ts | 2 +- src/commands/hosting-channel-deploy.ts | 2 +- src/commands/hosting-channel-list.ts | 2 +- src/commands/hosting-channel-open.ts | 2 +- src/commands/hosting-sites-delete.ts | 2 +- src/commands/serve.js | 2 +- src/commands/target-apply.ts | 2 +- src/commands/target-clear.ts | 2 +- src/commands/target-remove.ts | 2 +- src/commands/target.ts | 2 +- src/emulator/commandUtils.ts | 2 +- src/requireConfig.js | 15 --------------- src/requireConfig.ts | 11 +++++++++++ 18 files changed, 27 insertions(+), 31 deletions(-) delete mode 100644 src/requireConfig.js create mode 100644 src/requireConfig.ts diff --git a/src/commands/deploy.js b/src/commands/deploy.js index 864672ac911..30603a0c80b 100644 --- a/src/commands/deploy.js +++ b/src/commands/deploy.js @@ -7,7 +7,7 @@ const { checkServiceAccountIam } = require("../deploy/functions/checkIam"); const checkValidTargetFilters = require("../checkValidTargetFilters"); const { Command } = require("../command"); const deploy = require("../deploy"); -const requireConfig = require("../requireConfig"); +const { requireConfig } = require("../requireConfig"); const { filterTargets } = require("../filterTargets"); const { requireHostingSite } = require("../requireHostingSite"); diff --git a/src/commands/experimental-functions-shell.js b/src/commands/experimental-functions-shell.js index 0e655752b22..5b5e3fad60f 100644 --- a/src/commands/experimental-functions-shell.js +++ b/src/commands/experimental-functions-shell.js @@ -3,7 +3,7 @@ var { Command } = require("../command"); var { requirePermissions } = require("../requirePermissions"); var { actionFunction } = require("../functionsShellCommandAction"); -var requireConfig = require("../requireConfig"); +var { requireConfig } = require("../requireConfig"); module.exports = new Command("experimental:functions:shell") .description( diff --git a/src/commands/functions-config-export.ts b/src/commands/functions-config-export.ts index 753cf93cfba..964d218be00 100644 --- a/src/commands/functions-config-export.ts +++ b/src/commands/functions-config-export.ts @@ -12,7 +12,7 @@ import { requirePermissions } from "../requirePermissions"; import { logBullet, logWarning } from "../utils"; import { zip } from "../functional"; import * as configExport from "../functions/runtimeConfigExport"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import type { Options } from "../options"; diff --git a/src/commands/functions-shell.js b/src/commands/functions-shell.js index 9e99820d3a0..5a3193d5f98 100644 --- a/src/commands/functions-shell.js +++ b/src/commands/functions-shell.js @@ -3,7 +3,7 @@ var { Command } = require("../command"); var { requirePermissions } = require("../requirePermissions"); var { actionFunction } = require("../functionsShellCommandAction"); -var requireConfig = require("../requireConfig"); +var { requireConfig } = require("../requireConfig"); var commandUtils = require("../emulator/commandUtils"); module.exports = new Command("functions:shell") diff --git a/src/commands/hosting-channel-create.ts b/src/commands/hosting-channel-create.ts index 9c5fb5f5682..789795407c4 100644 --- a/src/commands/hosting-channel-create.ts +++ b/src/commands/hosting-channel-create.ts @@ -9,7 +9,7 @@ import { promptOnce } from "../prompt"; import { requirePermissions } from "../requirePermissions"; import { needProjectId } from "../projectUtils"; import { logger } from "../logger"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const { marked } = require("marked"); import { requireHostingSite } from "../requireHostingSite"; diff --git a/src/commands/hosting-channel-delete.ts b/src/commands/hosting-channel-delete.ts index d4307c35dd5..c14c8e671e9 100644 --- a/src/commands/hosting-channel-delete.ts +++ b/src/commands/hosting-channel-delete.ts @@ -9,7 +9,7 @@ import { promptOnce } from "../prompt"; import { requireHostingSite } from "../requireHostingSite"; import { requirePermissions } from "../requirePermissions"; import { needProjectId } from "../projectUtils"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import { logger } from "../logger"; export default new Command("hosting:channel:delete ") diff --git a/src/commands/hosting-channel-deploy.ts b/src/commands/hosting-channel-deploy.ts index 46a6ce050bb..c19f79b7157 100644 --- a/src/commands/hosting-channel-deploy.ts +++ b/src/commands/hosting-channel-deploy.ts @@ -16,7 +16,7 @@ import { requirePermissions } from "../requirePermissions"; import * as deploy from "../deploy"; import { needProjectId } from "../projectUtils"; import { logger } from "../logger"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import { DEFAULT_DURATION, calculateChannelExpireTTL } from "../hosting/expireUtils"; import { logLabeledSuccess, datetimeString, logLabeledWarning, consoleUrl } from "../utils"; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires diff --git a/src/commands/hosting-channel-list.ts b/src/commands/hosting-channel-list.ts index 1b522b3cbc7..330c336ca01 100644 --- a/src/commands/hosting-channel-list.ts +++ b/src/commands/hosting-channel-list.ts @@ -6,7 +6,7 @@ import { Command } from "../command"; import { requirePermissions } from "../requirePermissions"; import { needProjectId } from "../projectUtils"; import { logger } from "../logger"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import { datetimeString } from "../utils"; import { requireHostingSite } from "../requireHostingSite"; diff --git a/src/commands/hosting-channel-open.ts b/src/commands/hosting-channel-open.ts index 0ea8293226b..ac70b7b43c7 100644 --- a/src/commands/hosting-channel-open.ts +++ b/src/commands/hosting-channel-open.ts @@ -7,7 +7,7 @@ import { FirebaseError } from "../error"; import { getChannel, listChannels, normalizeName } from "../hosting/api"; import { requirePermissions } from "../requirePermissions"; import { needProjectId } from "../projectUtils"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import { logLabeledBullet } from "../utils"; import { promptOnce } from "../prompt"; import { requireHostingSite } from "../requireHostingSite"; diff --git a/src/commands/hosting-sites-delete.ts b/src/commands/hosting-sites-delete.ts index 0373d6b179e..e779b310b0f 100644 --- a/src/commands/hosting-sites-delete.ts +++ b/src/commands/hosting-sites-delete.ts @@ -6,7 +6,7 @@ import { promptOnce } from "../prompt"; import { FirebaseError } from "../error"; import { requirePermissions } from "../requirePermissions"; import { needProjectId } from "../projectUtils"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import { logger } from "../logger"; const LOG_TAG = "hosting:sites"; diff --git a/src/commands/serve.js b/src/commands/serve.js index f2cfcfd1a0b..64b4ce81eef 100644 --- a/src/commands/serve.js +++ b/src/commands/serve.js @@ -7,7 +7,7 @@ var { Command } = require("../command"); const { logger } = require("../logger"); var utils = require("../utils"); var { requirePermissions } = require("../requirePermissions"); -var requireConfig = require("../requireConfig"); +var { requireConfig } = require("../requireConfig"); var { serve } = require("../serve/index"); var { filterTargets } = require("../filterTargets"); var { needProjectNumber } = require("../projectUtils"); diff --git a/src/commands/target-apply.ts b/src/commands/target-apply.ts index 77487c8a422..9e1c72d5f6f 100644 --- a/src/commands/target-apply.ts +++ b/src/commands/target-apply.ts @@ -2,7 +2,7 @@ import * as clc from "cli-color"; import { Command } from "../command"; import { logger } from "../logger"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import * as utils from "../utils"; import { FirebaseError } from "../error"; diff --git a/src/commands/target-clear.ts b/src/commands/target-clear.ts index f93cb879546..b1a5e1a5dbc 100644 --- a/src/commands/target-clear.ts +++ b/src/commands/target-clear.ts @@ -1,7 +1,7 @@ import * as clc from "cli-color"; import { Command } from "../command"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import * as utils from "../utils"; export default new Command("target:clear ") diff --git a/src/commands/target-remove.ts b/src/commands/target-remove.ts index ee6b1d8a0dd..fbdf747f6ad 100644 --- a/src/commands/target-remove.ts +++ b/src/commands/target-remove.ts @@ -1,7 +1,7 @@ import * as clc from "cli-color"; import { Command } from "../command"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import * as utils from "../utils"; export default new Command("target:remove ") diff --git a/src/commands/target.ts b/src/commands/target.ts index fc129501004..098fc2e8651 100644 --- a/src/commands/target.ts +++ b/src/commands/target.ts @@ -2,7 +2,7 @@ import * as clc from "cli-color"; import { Command } from "../command"; import { logger } from "../logger"; -import * as requireConfig from "../requireConfig"; +import { requireConfig } from "../requireConfig"; import * as utils from "../utils"; interface targetMap { diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index 0ce2c66231c..9aa6c2dd541 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -8,7 +8,7 @@ import { logger } from "../logger"; import * as path from "path"; import { Constants } from "./constants"; import { requireAuth } from "../requireAuth"; -import requireConfig = require("../requireConfig"); +import { requireConfig } from "../requireConfig"; import { Emulators, ALL_SERVICE_EMULATORS } from "./types"; import { FirebaseError } from "../error"; import { EmulatorRegistry } from "./registry"; diff --git a/src/requireConfig.js b/src/requireConfig.js deleted file mode 100644 index d7eda280a7a..00000000000 --- a/src/requireConfig.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict"; - -var { FirebaseError } = require("./error"); - -module.exports = function (options) { - if (options.config) { - return Promise.resolve(); - } - return Promise.reject( - options.configError || - new FirebaseError("Not in a Firebase project directory (could not locate firebase.json)", { - exit: 1, - }) - ); -}; diff --git a/src/requireConfig.ts b/src/requireConfig.ts new file mode 100644 index 00000000000..b30090afd60 --- /dev/null +++ b/src/requireConfig.ts @@ -0,0 +1,11 @@ +import { FirebaseError } from "./error"; +import { Options } from "./options"; + +export async function requireConfig(options: Options): Promise { + await Promise.resolve(); // Allows this function to remain `async`. + if (!options.config) { + throw options.configError + ? options.configError + : new FirebaseError("Not in a Firebase project directory (could not locate firebase.json)"); + } +} From 5d9e63e0e78ecb5d6994a3ba7849de306630cdc5 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 17 Mar 2022 17:30:24 +0000 Subject: [PATCH 0168/1699] 10.4.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e9f69c521ff..2b0225e9e0f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.3.1", + "version": "10.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.3.1", + "version": "10.4.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 98ebf8dafd1..ebb7ee9f3f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.3.1", + "version": "10.4.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From be3c4e8ab19966518ee9f64b009b2ac5ab688ac9 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 17 Mar 2022 17:30:46 +0000 Subject: [PATCH 0169/1699] [firebase-release] Removed change log and reset repo after 10.4.0 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d977aceaca..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Adds support for configuration with multiple storage targets (#4281). -- Fixes bug where callable functions couldn't be emulated (#4314). From 597b2a5b507df8394ace9f19c4d674038ed3806e Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 17 Mar 2022 10:50:15 -0700 Subject: [PATCH 0170/1699] typescriptify some init logic for functions and hosting (#4315) * typescriptify some init logic for functions and hosting * remove extra promise wrapping --- src/init/features/functions/index.ts | 4 +- src/init/features/hosting/index.js | 87 ---------------------------- src/init/features/hosting/index.ts | 75 ++++++++++++++++++++++++ src/init/features/index.js | 15 ----- src/init/features/index.ts | 11 ++++ src/init/index.ts | 58 +++++++++++-------- 6 files changed, 123 insertions(+), 127 deletions(-) delete mode 100644 src/init/features/hosting/index.js create mode 100644 src/init/features/hosting/index.ts delete mode 100644 src/init/features/index.js create mode 100644 src/init/features/index.ts diff --git a/src/init/features/functions/index.ts b/src/init/features/functions/index.ts index 007a9345376..f05e594ae73 100644 --- a/src/init/features/functions/index.ts +++ b/src/init/features/functions/index.ts @@ -7,7 +7,7 @@ import { previews } from "../../../previews"; import { Options } from "../../../options"; import { ensure } from "../../../ensureApiEnabled"; -module.exports = async function (setup: any, config: any, options: Options) { +export async function doSetup(setup: any, config: any, options: Options) { logger.info(); logger.info( "A " + clc.bold("functions") + " directory will be created in your project with sample code" @@ -49,4 +49,4 @@ module.exports = async function (setup: any, config: any, options: Options) { choices, }); return require("./" + language)(setup, config); -}; +} diff --git a/src/init/features/hosting/index.js b/src/init/features/hosting/index.js deleted file mode 100644 index cf6243a7469..00000000000 --- a/src/init/features/hosting/index.js +++ /dev/null @@ -1,87 +0,0 @@ -"use strict"; - -const clc = require("cli-color"); -const fs = require("fs"); - -const { Client } = require("../../../apiv2"); -const { initGitHub } = require("./github"); -const { prompt } = require("../../../prompt"); -const { logger } = require("../../../logger"); - -const INDEX_TEMPLATE = fs.readFileSync( - __dirname + "/../../../../templates/init/hosting/index.html", - "utf8" -); -const MISSING_TEMPLATE = fs.readFileSync( - __dirname + "/../../../../templates/init/hosting/404.html", - "utf8" -); -const DEFAULT_IGNORES = ["firebase.json", "**/.*", "**/node_modules/**"]; - -module.exports = function (setup, config, options) { - setup.hosting = {}; - - logger.info(); - logger.info( - "Your " + - clc.bold("public") + - " directory is the folder (relative to your project directory) that" - ); - logger.info( - "will contain Hosting assets to be uploaded with " + clc.bold("firebase deploy") + ". If you" - ); - logger.info("have a build process for your assets, use your build's output directory."); - logger.info(); - - return prompt(setup.hosting, [ - { - name: "public", - type: "input", - default: "public", - message: "What do you want to use as your public directory?", - }, - { - name: "spa", - type: "confirm", - default: false, - message: "Configure as a single-page app (rewrite all urls to /index.html)?", - }, - { - name: "github", - type: "confirm", - default: false, - message: "Set up automatic builds and deploys with GitHub?", - }, - ]).then(function () { - setup.config.hosting = { - public: setup.hosting.public, - ignore: DEFAULT_IGNORES, - }; - - let next; - if (setup.hosting.spa) { - setup.config.hosting.rewrites = [{ source: "**", destination: "/index.html" }]; - next = Promise.resolve(); - } else { - // SPA doesn't need a 404 page since everything is index.html - next = config.askWriteProjectFile(setup.hosting.public + "/404.html", MISSING_TEMPLATE); - } - - return next - .then(() => { - const c = new Client({ urlPrefix: "https://www.gstatic.com", auth: false }); - return c.get("/firebasejs/releases.json"); - }) - .then((response) => { - return config.askWriteProjectFile( - setup.hosting.public + "/index.html", - INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version) - ); - }) - .then(() => { - if (setup.hosting.github) { - return initGitHub(setup, config, options); - } - }); - }); -}; diff --git a/src/init/features/hosting/index.ts b/src/init/features/hosting/index.ts new file mode 100644 index 00000000000..204797704b8 --- /dev/null +++ b/src/init/features/hosting/index.ts @@ -0,0 +1,75 @@ +import * as clc from "cli-color"; +import * as fs from "fs"; + +import { Client } from "../../../apiv2"; +import { initGitHub } from "./github"; +import { prompt } from "../../../prompt"; +import { logger } from "../../../logger"; +import { Options } from "../../../options"; + +const INDEX_TEMPLATE = fs.readFileSync( + __dirname + "/../../../../templates/init/hosting/index.html", + "utf8" +); +const MISSING_TEMPLATE = fs.readFileSync( + __dirname + "/../../../../templates/init/hosting/404.html", + "utf8" +); +const DEFAULT_IGNORES = ["firebase.json", "**/.*", "**/node_modules/**"]; + +export async function doSetup(setup: any, config: any, options: Options): Promise { + setup.hosting = {}; + + logger.info(); + logger.info( + `Your ${clc.bold("public")} directory is the folder (relative to your project directory) that` + ); + logger.info( + `will contain Hosting assets to be uploaded with ${clc.bold("firebase deploy")}. If you` + ); + logger.info("have a build process for your assets, use your build's output directory."); + logger.info(); + + await prompt(setup.hosting, [ + { + name: "public", + type: "input", + default: "public", + message: "What do you want to use as your public directory?", + }, + { + name: "spa", + type: "confirm", + default: false, + message: "Configure as a single-page app (rewrite all urls to /index.html)?", + }, + { + name: "github", + type: "confirm", + default: false, + message: "Set up automatic builds and deploys with GitHub?", + }, + ]); + + setup.config.hosting = { + public: setup.hosting.public, + ignore: DEFAULT_IGNORES, + }; + + if (setup.hosting.spa) { + setup.config.hosting.rewrites = [{ source: "**", destination: "/index.html" }]; + } else { + // SPA doesn't need a 404 page since everything is index.html + await config.askWriteProjectFile(`${setup.hosting.public}/404.html`, MISSING_TEMPLATE); + } + + const c = new Client({ urlPrefix: "https://www.gstatic.com", auth: false }); + const response = await c.get<{ current: { version: string } }>("/firebasejs/releases.json"); + await config.askWriteProjectFile( + `${setup.hosting.public}/index.html`, + INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version) + ); + if (setup.hosting.github) { + return initGitHub(setup, config, options); + } +} diff --git a/src/init/features/index.js b/src/init/features/index.js deleted file mode 100644 index c490bb0b7e9..00000000000 --- a/src/init/features/index.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict"; - -module.exports = { - account: require("./account").doSetup, - database: require("./database").doSetup, - firestore: require("./firestore").doSetup, - functions: require("./functions"), - hosting: require("./hosting"), - storage: require("./storage").doSetup, - emulators: require("./emulators").doSetup, - // always runs, sets up .firebaserc - project: require("./project").doSetup, - remoteconfig: require("./remoteconfig").doSetup, - "hosting:github": require("./hosting/github").initGitHub, -}; diff --git a/src/init/features/index.ts b/src/init/features/index.ts new file mode 100644 index 00000000000..ef4f70fb4be --- /dev/null +++ b/src/init/features/index.ts @@ -0,0 +1,11 @@ +export { doSetup as account } from "./account"; +export { doSetup as database } from "./database"; +export { doSetup as firestore } from "./firestore"; +export { doSetup as functions } from "./functions"; +export { doSetup as hosting } from "./hosting"; +export { doSetup as storage } from "./storage"; +export { doSetup as emulators } from "./emulators"; +// always runs, sets up .firebaserc +export { doSetup as project } from "./project"; +export { doSetup as remoteconfig } from "./remoteconfig"; +export { initGitHub as hostingGithub } from "./hosting/github"; diff --git a/src/init/index.ts b/src/init/index.ts index 08ae3a9a329..dd19f6da5de 100644 --- a/src/init/index.ts +++ b/src/init/index.ts @@ -1,43 +1,55 @@ -import * as _ from "lodash"; +import { capitalize } from "lodash"; import * as clc from "cli-color"; + +import { FirebaseError } from "../error"; import { logger } from "../logger"; -import * as _features from "./features"; -import * as utils from "../utils"; +import * as features from "./features"; -export interface Indexable { - [key: string]: T; -} -export interface RCFile { - projects: Indexable; -} export interface Setup { - config: Indexable; - rcfile: RCFile; + config: Record; + rcfile: { + projects: Record; + }; features?: string[]; featureArg?: boolean; - project?: Indexable; + project?: Record; projectId?: string; projectLocation?: string; } -// TODO: Convert features/index.js to TypeScript so it exports -// as an indexable type instead of doing this cast. -const features = _features as Indexable; +const featureFns = new Map Promise>([ + ["account", features.account], + ["database", features.database], + ["firestore", features.firestore], + ["functions", features.functions], + ["hosting", features.hosting], + ["storage", features.storage], + ["emulators", features.emulators], + ["project", features.project], // always runs, sets up .firebaserc + ["remoteconfig", features.remoteconfig], + ["hosting:github", features.hostingGithub], +]); export async function init(setup: Setup, config: any, options: any): Promise { - const nextFeature = setup.features ? setup.features.shift() : undefined; + const nextFeature = setup.features?.shift(); if (nextFeature) { - if (!features[nextFeature]) { - return utils.reject( - clc.bold(nextFeature) + - " is not a valid feature. Must be one of " + - _.without(_.keys(features), "project").join(", ") + if (!featureFns.has(nextFeature)) { + const availableFeatures = Object.keys(features) + .filter((f) => f !== "project") + .join(", "); + throw new FirebaseError( + `${clc.bold(nextFeature)} is not a valid feature. Must be one of ${availableFeatures}` ); } - logger.info(clc.bold("\n" + clc.white("=== ") + _.capitalize(nextFeature) + " Setup")); + logger.info(clc.bold(`\n${clc.white("===")} ${capitalize(nextFeature)} Setup`)); - await Promise.resolve(features[nextFeature](setup, config, options)); + const fn = featureFns.get(nextFeature); + if (!fn) { + // We've already checked that the function exists, so this really should never happen. + throw new FirebaseError(`We've lost the function to init ${nextFeature}`, { exit: 2 }); + } + await fn(setup, config, options); return init(setup, config, options); } } From a95694f2fabe541b6340765e93da7b0ec99b68ee Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Thu, 17 Mar 2022 15:39:28 -0400 Subject: [PATCH 0171/1699] Add integration test for multiple storage targets (#4296) * Add integration test for multiple storage targets * Add integration test for multiple storage targets * Address PR feedback --- scripts/integration-helpers/framework.ts | 18 +++ .../multiple-targets/allowAll.rules | 8 + .../multiple-targets/allowNone.rules | 8 + .../multiple-targets/firebase.json | 19 +++ .../multiple-targets/tests.ts | 64 ++++++++ scripts/storage-emulator-integration/run.sh | 2 + scripts/storage-emulator-integration/tests.ts | 146 +++--------------- scripts/storage-emulator-integration/utils.ts | 124 +++++++++++++++ 8 files changed, 263 insertions(+), 126 deletions(-) create mode 100644 scripts/storage-emulator-integration/multiple-targets/allowAll.rules create mode 100644 scripts/storage-emulator-integration/multiple-targets/allowNone.rules create mode 100644 scripts/storage-emulator-integration/multiple-targets/firebase.json create mode 100644 scripts/storage-emulator-integration/multiple-targets/tests.ts create mode 100644 scripts/storage-emulator-integration/utils.ts diff --git a/scripts/integration-helpers/framework.ts b/scripts/integration-helpers/framework.ts index 79e9797392b..da5c2de8736 100644 --- a/scripts/integration-helpers/framework.ts +++ b/scripts/integration-helpers/framework.ts @@ -1,6 +1,7 @@ import fetch, { Response } from "node-fetch"; import { CLIProcess } from "./cli"; +import { Emulators } from "../../src/emulator/types"; const FIREBASE_PROJECT_ZONE = "us-central1"; @@ -256,6 +257,23 @@ export class TriggerEndToEndTest { return this.cliProcess ? this.cliProcess.stop() : Promise.resolve(); } + applyTargets(emulatorType: Emulators, target: string, resource: string): Promise { + const cli = new CLIProcess("default", this.workdir); + const started = cli.start( + "target:apply", + this.project, + [emulatorType, target, resource], + (data: unknown) => { + if (typeof data !== "string" && !Buffer.isBuffer(data)) { + throw new Error(`data is not a string or buffer (${typeof data})`); + } + return data.includes(`Applied ${emulatorType} target`); + } + ); + this.cliProcess = cli; + return started; + } + invokeHttpFunction(name: string, zone = FIREBASE_PROJECT_ZONE): Promise { const url = `http://localhost:${[this.functionsEmulatorPort, this.project, zone, name].join( "/" diff --git a/scripts/storage-emulator-integration/multiple-targets/allowAll.rules b/scripts/storage-emulator-integration/multiple-targets/allowAll.rules new file mode 100644 index 00000000000..a7db6961cad --- /dev/null +++ b/scripts/storage-emulator-integration/multiple-targets/allowAll.rules @@ -0,0 +1,8 @@ +rules_version = '2'; +service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write: if true; + } + } +} diff --git a/scripts/storage-emulator-integration/multiple-targets/allowNone.rules b/scripts/storage-emulator-integration/multiple-targets/allowNone.rules new file mode 100644 index 00000000000..9f33d22cbd7 --- /dev/null +++ b/scripts/storage-emulator-integration/multiple-targets/allowNone.rules @@ -0,0 +1,8 @@ +rules_version = '2'; +service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write: if false; + } + } +} diff --git a/scripts/storage-emulator-integration/multiple-targets/firebase.json b/scripts/storage-emulator-integration/multiple-targets/firebase.json new file mode 100644 index 00000000000..3448de54697 --- /dev/null +++ b/scripts/storage-emulator-integration/multiple-targets/firebase.json @@ -0,0 +1,19 @@ +{ + "storage": [ + { + "target": "allowNone", + "rules": "allowNone.rules" + }, + { + "target": "allowAll", + "rules": "allowAll.rules" + } + ], + "emulators": { + "storage": { + "port": 9199 + } + } + } + + \ No newline at end of file diff --git a/scripts/storage-emulator-integration/multiple-targets/tests.ts b/scripts/storage-emulator-integration/multiple-targets/tests.ts new file mode 100644 index 00000000000..a010d923884 --- /dev/null +++ b/scripts/storage-emulator-integration/multiple-targets/tests.ts @@ -0,0 +1,64 @@ +import supertest = require("supertest"); +import { Emulators } from "../../../src/emulator/types"; +import { TriggerEndToEndTest } from "../../integration-helpers/framework"; +import { + EMULATORS_SHUTDOWN_DELAY_MS, + FIREBASE_EMULATOR_CONFIG, + getStorageEmulatorHost, + readEmulatorConfig, + TEST_SETUP_TIMEOUT, +} from "../utils"; + +const FIREBASE_PROJECT = process.env.FBTOOLS_TARGET_PROJECT || "fake-project-id"; + +describe("Multiple Storage Deploy Targets", () => { + let test: TriggerEndToEndTest; + const allowNoneBucket = `${FIREBASE_PROJECT}.appspot.com`; + const allowAllBucket = `${FIREBASE_PROJECT}-2.appspot.com`; + const emulatorConfig = readEmulatorConfig(FIREBASE_EMULATOR_CONFIG); + const STORAGE_EMULATOR_HOST = getStorageEmulatorHost(emulatorConfig); + + before(async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, emulatorConfig); + await test.applyTargets(Emulators.STORAGE, "allowNone", allowNoneBucket); + await test.applyTargets(Emulators.STORAGE, "allowAll", allowAllBucket); + await test.startEmulators(["--only", Emulators.STORAGE]); + }); + + it("should enforce different rules for different targets", async () => { + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post(`/v0/b/${allowNoneBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`) + .set({ "X-Goog-Upload-Protocol": "resumable", "X-Goog-Upload-Command": "start" }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload, finalize", + }) + .expect(403); + + const otherUploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post(`/v0/b/${allowAllBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`) + .set({ "X-Goog-Upload-Protocol": "resumable", "X-Goog-Upload-Command": "start" }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(otherUploadURL.pathname + otherUploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload, finalize", + }) + .expect(200); + }); + + after(async function (this) { + this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + await test.stopEmulators(); + }); +}); diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index efc1b79bb74..ef6cea68270 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -9,4 +9,6 @@ firebase setup:emulators:storage mocha scripts/storage-emulator-integration/rules/*.test.ts +mocha scripts/storage-emulator-integration/multiple-targets/tests.ts + mocha scripts/storage-emulator-integration/tests.ts diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index ebe5ea56a92..d1fbd5fe5b9 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -6,24 +6,29 @@ import * as path from "path"; import * as http from "http"; import * as https from "https"; import * as puppeteer from "puppeteer"; -import * as request from "request"; -import * as crypto from "crypto"; -import * as os from "os"; import { Bucket, Storage, CopyOptions } from "@google-cloud/storage"; import supertest = require("supertest"); import { IMAGE_FILE_BASE64 } from "../../src/test/emulators/fixtures"; -import { FrameworkOptions, TriggerEndToEndTest } from "../integration-helpers/framework"; +import { TriggerEndToEndTest } from "../integration-helpers/framework"; +import { + createRandomFile, + EMULATORS_SHUTDOWN_DELAY_MS, + getAuthEmulatorHost, + getStorageEmulatorHost, + LARGE_FILE_SIZE, + readEmulatorConfig, + readJson, + readProdAppConfig, + resetStorageEmulator, + SERVICE_ACCOUNT_KEY, + SMALL_FILE_SIZE, + TEST_SETUP_TIMEOUT, + uploadText, +} from "./utils"; const FIREBASE_PROJECT = process.env.FBTOOLS_TARGET_PROJECT || "fake-project-id"; -/* - * Various delays that are needed because this test spawns - * parallel emulator subprocesses. - */ -const TEST_SETUP_TIMEOUT = 60000; -const EMULATORS_SHUTDOWN_DELAY_MS = 5000; - // Flip these flags for options during test debugging // all should be FALSE on commit const TEST_CONFIG = { @@ -44,120 +49,9 @@ const TEST_CONFIG = { keepBrowserOpen: false, }; -// Files contianing the Firebase App Config and Service Account key for -// the app to be used in these tests.This is only applicable if -// TEST_CONFIG.useProductionServers is true -const PROD_APP_CONFIG = "storage-integration-config.json"; -const SERVICE_ACCOUNT_KEY = "service-account-key.json"; - -// Firebase Emulator config, for starting up emulators -const FIREBASE_EMULATOR_CONFIG = "firebase.json"; -const SMALL_FILE_SIZE = 200 * 1024; /* 200 kB */ -const LARGE_FILE_SIZE = 20 * 1024 * 1024; /* 20 MiB */ // Temp directory to store generated files. let tmpDir: string; -/** - * Reads a JSON file in the current directory. - * - * @param filename name of the JSON file to be read. Must be in the current directory. - */ -function readJson(filename: string) { - const fullPath = path.join(__dirname, filename); - if (!fs.existsSync(fullPath)) { - throw new Error(`Can't find file at ${filename}`); - } - const data = fs.readFileSync(fullPath, "utf8"); - return JSON.parse(data); -} - -function readProdAppConfig() { - try { - return readJson(PROD_APP_CONFIG); - } catch (error) { - throw new Error( - `Cannot read the integration config. Please ensure that the file ${PROD_APP_CONFIG} is present in the current directory.` - ); - } -} - -function readEmulatorConfig(): FrameworkOptions { - try { - return readJson(FIREBASE_EMULATOR_CONFIG); - } catch (error) { - throw new Error( - `Cannot read the emulator config. Please ensure that the file ${FIREBASE_EMULATOR_CONFIG} is present in the current directory.` - ); - } -} - -function getAuthEmulatorHost(emulatorConfig: FrameworkOptions) { - const port = emulatorConfig.emulators?.auth?.port; - if (port) { - return `http://localhost:${port}`; - } - throw new Error("Auth emulator config not found or invalid"); -} - -function getStorageEmulatorHost(emulatorConfig: FrameworkOptions) { - const port = emulatorConfig.emulators?.storage?.port; - if (port) { - return `http://localhost:${port}`; - } - throw new Error("Storage emulator config not found or invalid"); -} - -function createRandomFile(filename: string, sizeInBytes: number): string { - if (!tmpDir) { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "storage-files")); - } - const fullPath = path.join(tmpDir, filename); - const bytes = crypto.randomBytes(sizeInBytes); - fs.writeFileSync(fullPath, bytes); - - return fullPath; -} - -/** - * Resets the storage layer of the Storage Emulator. - */ -async function resetStorageEmulator(emulatorHost: string) { - await new Promise((resolve) => { - request.post(`${emulatorHost}/internal/reset`, () => { - resolve(); - }); - }); -} - -async function uploadText( - page: puppeteer.Page, - filename: string, - text: string, - format?: string, - metadata?: firebase.storage.UploadMetadata -): Promise { - return page.evaluate( - async (filename, text, format, metadata) => { - try { - const task = await firebase - .storage() - .ref(filename) - .putString(text, format, JSON.parse(metadata)); - return task.state; - } catch (err) { - if (err instanceof Error) { - throw err.message; - } - throw err; - } - }, - filename, - text, - format ?? "raw", - JSON.stringify(metadata ?? {}) - )!; -} - describe("Storage emulator", () => { let test: TriggerEndToEndTest; let browser: puppeteer.Browser; @@ -209,8 +103,8 @@ describe("Storage emulator", () => { testBucket = admin.storage().bucket(storageBucket); - smallFilePath = createRandomFile("small_file", SMALL_FILE_SIZE); - largeFilePath = createRandomFile("large_file", LARGE_FILE_SIZE); + smallFilePath = createRandomFile("small_file", SMALL_FILE_SIZE, tmpDir); + largeFilePath = createRandomFile("large_file", LARGE_FILE_SIZE, tmpDir); }); beforeEach(async () => { @@ -232,8 +126,8 @@ describe("Storage emulator", () => { it("should replace existing file on upload", async () => { const path = "replace.txt"; - const content1 = createRandomFile("small_content_1", 10); - const content2 = createRandomFile("small_content_2", 10); + const content1 = createRandomFile("small_content_1", 10, tmpDir); + const content2 = createRandomFile("small_content_2", 10, tmpDir); const file = testBucket.file(path); await testBucket.upload(content1, { diff --git a/scripts/storage-emulator-integration/utils.ts b/scripts/storage-emulator-integration/utils.ts new file mode 100644 index 00000000000..a91cf508767 --- /dev/null +++ b/scripts/storage-emulator-integration/utils.ts @@ -0,0 +1,124 @@ +import * as firebase from "firebase"; +import * as fs from "fs"; +import * as path from "path"; +import * as puppeteer from "puppeteer"; +import * as request from "request"; +import * as crypto from "crypto"; +import * as os from "os"; +import { FrameworkOptions } from "../integration-helpers/framework"; + +/* Various delays needed when integration test spawns parallel emulator subprocesses. */ +export const TEST_SETUP_TIMEOUT = 60000; +export const EMULATORS_SHUTDOWN_DELAY_MS = 5000; + +// Files contianing the Firebase App Config and Service Account key for +// the app to be used in these tests.This is only applicable if +// TEST_CONFIG.useProductionServers is true +export const PROD_APP_CONFIG = "storage-integration-config.json"; +export const SERVICE_ACCOUNT_KEY = "service-account-key.json"; + +// Firebase Emulator config, for starting up emulators +export const FIREBASE_EMULATOR_CONFIG = "firebase.json"; +export const SMALL_FILE_SIZE = 200 * 1024; /* 200 kB */ +export const LARGE_FILE_SIZE = 20 * 1024 * 1024; /* 20 MiB */ + +/** + * Reads a JSON file in the current directory. + * + * @param filename name of the JSON file to be read. Must be in the current directory. + */ +export function readJson(filename: string) { + const fullPath = path.join(__dirname, filename); + if (!fs.existsSync(fullPath)) { + throw new Error(`Can't find file at ${filename}`); + } + const data = fs.readFileSync(fullPath, "utf8"); + return JSON.parse(data); +} + +export function readProdAppConfig() { + try { + return readJson(PROD_APP_CONFIG); + } catch (error) { + throw new Error( + `Cannot read the integration config. Please ensure that the file ${PROD_APP_CONFIG} is present in the current directory.` + ); + } +} + +export function readEmulatorConfig(config = FIREBASE_EMULATOR_CONFIG): FrameworkOptions { + try { + return readJson(config); + } catch (error) { + throw new Error( + `Cannot read the emulator config. Please ensure that the file ${config} is present in the current directory.` + ); + } +} + +export function getAuthEmulatorHost(emulatorConfig: FrameworkOptions) { + const port = emulatorConfig.emulators?.auth?.port; + if (port) { + return `http://localhost:${port}`; + } + throw new Error("Auth emulator config not found or invalid"); +} + +export function getStorageEmulatorHost(emulatorConfig: FrameworkOptions) { + const port = emulatorConfig.emulators?.storage?.port; + if (port) { + return `http://localhost:${port}`; + } + throw new Error("Storage emulator config not found or invalid"); +} + +export function createRandomFile(filename: string, sizeInBytes: number, tmpDir?: string): string { + if (!tmpDir) { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "storage-files")); + } + const fullPath = path.join(tmpDir, filename); + const bytes = crypto.randomBytes(sizeInBytes); + fs.writeFileSync(fullPath, bytes); + + return fullPath; +} + +/** + * Resets the storage layer of the Storage Emulator. + */ +export async function resetStorageEmulator(emulatorHost: string) { + await new Promise((resolve) => { + request.post(`${emulatorHost}/internal/reset`, () => { + resolve(); + }); + }); +} + +export async function uploadText( + page: puppeteer.Page, + filename: string, + text: string, + format?: string, + metadata?: firebase.storage.UploadMetadata +): Promise { + return page.evaluate( + async (filename, text, format, metadata) => { + try { + const task = await firebase + .storage() + .ref(filename) + .putString(text, format, JSON.parse(metadata)); + return task.state; + } catch (err) { + if (err instanceof Error) { + throw err.message; + } + throw err; + } + }, + filename, + text, + format ?? "raw", + JSON.stringify(metadata ?? {}) + )!; +} From 089bea2ba739c0e03d858e76119e47144eda342c Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 17 Mar 2022 14:23:29 -0700 Subject: [PATCH 0172/1699] Allow multiple-config form for functions config in firebase.json (#4269) This change extends `functions` config in `firebase.json` so that you can write it in multi-config form. E.g. ```json // This has always been valid { "functions": { "source": "source" } } // This is also valid now { "functions": [ { "source": "source" } ] } ``` Since having multiple configuration for function doesn't make sense (yet), we add runtime checks to make sure that there is exactly 1 functions source if we have an array of function config. I had fun trying to add better types around configs and consolidated config validations to remove redundant config checks throughout the codebase. --- schema/firebase-config.json | 129 +++++++++++++----- src/commands/functions-config-export.ts | 5 +- src/config.ts | 18 ++- src/deploy/functions/args.ts | 2 + src/deploy/functions/deploy.ts | 12 +- src/deploy/functions/prepare.ts | 20 +-- .../functions/prepareFunctionsUpload.ts | 36 ++--- src/deploy/functions/release/index.ts | 2 +- src/emulator/controller.ts | 37 ++--- src/firebaseConfig.ts | 5 +- src/functions/projectConfig.ts | 50 +++++++ src/serve/functions.ts | 13 +- src/test/functions/projectConfig.spec.ts | 85 ++++++++++++ 13 files changed, 301 insertions(+), 113 deletions(-) create mode 100644 src/functions/projectConfig.ts create mode 100644 src/test/functions/projectConfig.spec.ts diff --git a/schema/firebase-config.json b/schema/firebase-config.json index ddf7f59bb18..6d4c51b0d3f 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -326,54 +326,111 @@ "type": "object" }, "functions": { - "additionalProperties": false, - "properties": { - "ignore": { - "items": { - "type": "string" - }, - "type": "array" - }, - "postdeploy": { - "anyOf": [ - { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "ignore": { "items": { "type": "string" }, "type": "array" }, - { + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "runtime": { + "enum": [ + "nodejs10", + "nodejs12", + "nodejs14", + "nodejs16" + ], + "type": "string" + }, + "source": { "type": "string" } - ] + }, + "type": "object" }, - "predeploy": { - "anyOf": [ - { - "items": { + { + "items": { + "additionalProperties": false, + "properties": { + "ignore": { + "items": { + "type": "string" + }, + "type": "array" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "runtime": { + "enum": [ + "nodejs10", + "nodejs12", + "nodejs14", + "nodejs16" + ], "type": "string" }, - "type": "array" + "source": { + "type": "string" + } }, - { - "type": "string" - } - ] - }, - "runtime": { - "enum": [ - "nodejs10", - "nodejs12", - "nodejs14", - "nodejs16" - ], - "type": "string" - }, - "source": { - "type": "string" + "type": "object" + }, + "type": "array" } - }, - "type": "object" + ] }, "hosting": { "anyOf": [ diff --git a/src/commands/functions-config-export.ts b/src/commands/functions-config-export.ts index 964d218be00..571627479dd 100644 --- a/src/commands/functions-config-export.ts +++ b/src/commands/functions-config-export.ts @@ -15,6 +15,7 @@ import * as configExport from "../functions/runtimeConfigExport"; import { requireConfig } from "../requireConfig"; import type { Options } from "../options"; +import { normalizeAndValidate } from "../functions/projectConfig"; const REQUIRED_PERMISSIONS = [ "runtimeconfig.configs.list", @@ -103,6 +104,9 @@ export default new Command("functions:config:export") .before(requireConfig) .before(requireInteractive) .action(async (options: Options) => { + const config = normalizeAndValidate(options.config.src.functions)[0]; + const functionsDir = config.source; + let pInfos = configExport.getProjectInfos(options); checkReservedAliases(pInfos); @@ -145,7 +149,6 @@ export default new Command("functions:config:export") ".env" ] = `${header}# .env file contains environment variables that applies to all projects.\n`; - const functionsDir = options.config.get("functions.source", "."); for (const [filename, content] of Object.entries(filesToWrite)) { await options.config.askWriteProjectFile(path.join(functionsDir, filename), content); } diff --git a/src/config.ts b/src/config.ts index 0209dc51b3f..977ec83b4a6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,13 +76,17 @@ export class Config { } }); - // Auto-detect functions from package.json in directory - if ( - this.projectDir && - !this.get("functions.source") && - fsutils.dirExistsSync(this.path("functions")) - ) { - this.set("functions.source", Config.DEFAULT_FUNCTIONS_SOURCE); + // Inject default functions config and source if missing. + if (this.projectDir && fsutils.dirExistsSync(this.path(Config.DEFAULT_FUNCTIONS_SOURCE))) { + if (Array.isArray(this.get("functions"))) { + if (!this.get("functions.[0].source")) { + this.set("functions.[0].source", Config.DEFAULT_FUNCTIONS_SOURCE); + } + } else { + if (!this.get("functions.source")) { + this.set("functions.source", Config.DEFAULT_FUNCTIONS_SOURCE); + } + } } } diff --git a/src/deploy/functions/args.ts b/src/deploy/functions/args.ts index 16d26071ae4..b09d2ac9b83 100644 --- a/src/deploy/functions/args.ts +++ b/src/deploy/functions/args.ts @@ -1,5 +1,6 @@ import * as backend from "./backend"; import * as gcfV2 from "../../gcp/cloudfunctionsv2"; +import * as projectConfig from "../../functions/projectConfig"; // These types should proably be in a root deploy.ts, but we can only boil the ocean one bit at a time. @@ -18,6 +19,7 @@ export interface Context { filters: string[][]; // Filled in the "prepare" phase. + config?: projectConfig.ValidatedSingle; functionsSourceV1?: string; functionsSourceV2?: string; runtimeConfigEnabled?: boolean; diff --git a/src/deploy/functions/deploy.ts b/src/deploy/functions/deploy.ts index b8b7e45444d..9909879c129 100644 --- a/src/deploy/functions/deploy.ts +++ b/src/deploy/functions/deploy.ts @@ -47,7 +47,7 @@ export async function deploy( options: Options, payload: args.Payload ): Promise { - if (!options.config.src.functions) { + if (!context.config) { return; } @@ -77,16 +77,10 @@ export async function deploy( } await Promise.all(uploads); - utils.assertDefined( - options.config.src.functions.source, - "Error: 'functions.source' is not defined" - ); + const source = context.config.source; if (uploads.length) { logSuccess( - clc.green.bold("functions:") + - " " + - clc.bold(options.config.src.functions.source) + - " folder uploaded successfully" + `${clc.green.bold("functions:")} ${clc.bold(source)} folder uploaded successfully` ); } } catch (err: any) { diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 4e7de48c1c2..9ea291d0542 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -13,13 +13,13 @@ import { functionMatchesAnyGroup, getFilterGroups } from "./functionsDeployHelpe import { logBullet } from "../../utils"; import { getFunctionsConfig, prepareFunctionsUpload } from "./prepareFunctionsUpload"; import { promptForFailurePolicies, promptForMinInstances } from "./prompts"; -import { previews } from "../../previews"; import { needProjectId, needProjectNumber } from "../../projectUtils"; import { track } from "../../track"; import { logger } from "../../logger"; import { ensureTriggerRegions } from "./triggerRegionHelper"; import { ensureServiceAgentRoles } from "./checkIam"; import { FirebaseError } from "../../error"; +import { normalizeAndValidate } from "../../functions/projectConfig"; function hasUserConfig(config: Record): boolean { // "firebase" key is always going to exist in runtime config. @@ -39,10 +39,11 @@ export async function prepare( const projectId = needProjectId(options); const projectNumber = await needProjectNumber(options); - const sourceDirName = options.config.get("functions.source") as string; + context.config = normalizeAndValidate(options.config.src.functions)[0]; + const sourceDirName = context.config.source; if (!sourceDirName) { throw new FirebaseError( - `No functions code detected at default location (./functions), and no functions.source defined in firebase.json` + `No functions code detected at default location (./functions), and no functions source defined in firebase.json` ); } const sourceDir = options.config.path(sourceDirName); @@ -51,7 +52,7 @@ export async function prepare( projectId, sourceDir, projectDir: options.config.projectDir, - runtime: (options.config.get("functions.runtime") as string) || "", + runtime: context.config.runtime || "", }; const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext); logger.debug(`Validating ${runtimeDelegate.name} source`); @@ -127,13 +128,14 @@ export async function prepare( ); } if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) { - context.functionsSourceV1 = await prepareFunctionsUpload(runtimeConfig, options); + context.functionsSourceV1 = await prepareFunctionsUpload( + sourceDir, + context.config, + runtimeConfig + ); } if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { - context.functionsSourceV2 = await prepareFunctionsUpload( - /* runtimeConfig= */ undefined, - options - ); + context.functionsSourceV2 = await prepareFunctionsUpload(sourceDir, context.config); } // Setup environment variables on each function. diff --git a/src/deploy/functions/prepareFunctionsUpload.ts b/src/deploy/functions/prepareFunctionsUpload.ts index f8e32827274..d44c4445ac6 100644 --- a/src/deploy/functions/prepareFunctionsUpload.ts +++ b/src/deploy/functions/prepareFunctionsUpload.ts @@ -13,8 +13,7 @@ import * as functionsConfig from "../../functionsConfig"; import * as utils from "../../utils"; import * as fsAsync from "../../fsAsync"; import * as args from "./args"; -import { Options } from "../../options"; -import { Config } from "../../config"; +import * as projectConfig from "../../functions/projectConfig"; const CONFIG_DEST_FILE = ".runtimeconfig.json"; @@ -55,7 +54,11 @@ async function pipeAsync(from: archiver.Archiver, to: fs.WriteStream) { }); } -async function packageSource(options: Options, sourceDir: string, configValues: any) { +async function packageSource( + sourceDir: string, + config: projectConfig.ValidatedSingle, + runtimeConfig: any +) { const tmpFile = tmp.fileSync({ prefix: "firebase-functions-", postfix: ".zip" }).name; const fileStream = fs.createWriteStream(tmpFile, { flags: "w", @@ -67,7 +70,7 @@ async function packageSource(options: Options, sourceDir: string, configValues: // you're in the public dir when you deploy. // We ignore any CONFIG_DEST_FILE that already exists, and write another one // with current config values into the archive in the "end" handler for reader - const ignore = options.config.src.functions?.ignore || ["node_modules", ".git"]; + const ignore = config.ignore || ["node_modules", ".git"]; ignore.push( "firebase-debug.log", "firebase-debug.*.log", @@ -81,8 +84,8 @@ async function packageSource(options: Options, sourceDir: string, configValues: mode: file.mode, }); }); - if (typeof configValues !== "undefined") { - archive.append(JSON.stringify(configValues, null, 2), { + if (typeof runtimeConfig !== "undefined") { + archive.append(JSON.stringify(runtimeConfig, null, 2), { name: CONFIG_DEST_FILE, mode: 420 /* 0o644 */, }); @@ -99,15 +102,10 @@ async function packageSource(options: Options, sourceDir: string, configValues: ); } - utils.assertDefined(options.config.src.functions); - utils.assertDefined( - options.config.src.functions.source, - "Error: 'functions.source' is not defined" - ); utils.logBullet( clc.cyan.bold("functions:") + " packaged " + - clc.bold(options.config.src.functions.source) + + clc.bold(sourceDir) + " (" + filesize(archive.pointer()) + ") for uploading" @@ -116,15 +114,9 @@ async function packageSource(options: Options, sourceDir: string, configValues: } export async function prepareFunctionsUpload( - runtimeConfig: backend.RuntimeConfigValues | undefined, - options: Options + sourceDir: string, + config: projectConfig.ValidatedSingle, + runtimeConfig?: backend.RuntimeConfigValues ): Promise { - utils.assertDefined(options.config.src.functions); - utils.assertDefined( - options.config.src.functions.source, - "Error: 'functions.source' is not defined" - ); - - const sourceDir = options.config.path(options.config.src.functions.source); - return packageSource(options, sourceDir, runtimeConfig); + return packageSource(sourceDir, config, runtimeConfig); } diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index 3ba4fe226d5..8ef235377b8 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -21,7 +21,7 @@ export async function release( options: Options, payload: args.Payload ): Promise { - if (!options.config.has("functions")) { + if (!context.config) { return; } diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 04932c1ed3b..b047e64b1d4 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -45,6 +45,7 @@ import { Options } from "../options"; import { ParsedTriggerDefinition } from "./functionsEmulatorShared"; import { ExtensionsEmulator } from "./extensionsEmulator"; import { previews } from "../previews"; +import { normalizeAndValidate } from "../functions/projectConfig"; const START_LOGGING_EMULATOR = utils.envOverride( "START_LOGGING_EMULATOR", @@ -241,15 +242,20 @@ export function shouldStart(options: Options, name: Emulators): boolean { } // Don't start the functions emulator if we can't find the source directory - if (name === Emulators.FUNCTIONS && emulatorInTargets && !options.config.src.functions?.source) { - EmulatorLogger.forEmulator(Emulators.FUNCTIONS).logLabeled( - "WARN", - "functions", - `The functions emulator is configured but there is no functions source directory. Have you run ${clc.bold( - "firebase init functions" - )}?` - ); - return false; + if (name === Emulators.FUNCTIONS && emulatorInTargets) { + try { + normalizeAndValidate(options.config.src.functions); + return true; + } catch (err: any) { + EmulatorLogger.forEmulator(Emulators.FUNCTIONS).logLabeled( + "WARN", + "functions", + `The functions emulator is configured but there is no functions source directory. Have you run ${clc.bold( + "firebase init functions" + )}?` + ); + return false; + } } if (name === Emulators.HOSTING && emulatorInTargets && !options.config.get("hosting")) { @@ -326,7 +332,7 @@ interface EmulatorOptions extends Options { extDevEnv?: Record; } -export async function startAll(options: EmulatorOptions, showUI: boolean = true): Promise { +export async function startAll(options: EmulatorOptions, showUI = true): Promise { // Emulators config is specified in firebase.json as: // "emulators": { // "firestore": { @@ -422,15 +428,10 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) const emulatableBackends: EmulatableBackend[] = []; const projectDir = (options.extDevDir || options.config.projectDir) as string; if (shouldStart(options, Emulators.FUNCTIONS)) { + const functionsCfg = normalizeAndValidate(options.config.src.functions)[0]; // Note: ext:dev:emulators:* commands hit this path, not the Emulators.EXTENSIONS path - utils.assertDefined(options.config.src.functions); - utils.assertDefined( - options.config.src.functions.source, - "Error: 'functions.source' is not defined" - ); - utils.assertIsStringOrUndefined(options.extDevDir); - const functionsDir = path.join(projectDir, options.config.src.functions.source); + const functionsDir = path.join(projectDir, functionsCfg.source); emulatableBackends.push({ functionsDir, @@ -442,7 +443,7 @@ export async function startAll(options: EmulatorOptions, showUI: boolean = true) // Ideally, we should handle that case via ExtensionEmulator. predefinedTriggers: options.extDevTriggers as ParsedTriggerDefinition[] | undefined, nodeMajorVersion: parseRuntimeVersion( - options.extDevNodeVersion || options.config.get("functions.runtime") + (options.extDevNodeVersion as string) || functionsCfg.runtime ), }); } diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index b31a6eff622..814997c33f7 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -102,13 +102,14 @@ export type FirestoreConfig = { indexes?: string; } & Deployable; -export type FunctionsConfig = { - // TODO: Add types for "backend" +export type FunctionConfig = { source?: string; ignore?: string[]; runtime?: CloudFunctionRuntimes; } & Deployable; +export type FunctionsConfig = FunctionConfig | FunctionConfig[]; + export type HostingConfig = HostingSingle | HostingMultiple; export type StorageConfig = StorageSingle | StorageMultiple; diff --git a/src/functions/projectConfig.ts b/src/functions/projectConfig.ts new file mode 100644 index 00000000000..00b1b42273f --- /dev/null +++ b/src/functions/projectConfig.ts @@ -0,0 +1,50 @@ +import { FunctionsConfig, FunctionConfig } from "../firebaseConfig"; +import { FirebaseError } from "../error"; + +export type NormalizedConfig = [FunctionConfig, ...FunctionConfig[]]; +export type ValidatedSingle = FunctionConfig & { source: string }; +export type ValidatedConfig = [ValidatedSingle]; + +/** + * Normalize functions config to return functions config in an array form. + */ +export function normalize(config?: FunctionsConfig): NormalizedConfig { + if (!config) { + throw new FirebaseError("No valid functions configuration detected in firebase.json"); + } + + if (Array.isArray(config)) { + if (config.length < 1) { + throw new FirebaseError("Requires at least one functions.source in firebase.json."); + } + // Unfortunately, Typescript can't figure out that config has at least one element. We assert the type manually. + return config as NormalizedConfig; + } + return [config]; +} + +function validateSingle(config: FunctionConfig): ValidatedSingle { + if (!config.source) { + throw new FirebaseError("functions.source must be specified"); + } + return { ...config, source: config.source }; +} + +/** + * Validate functions config. + */ +export function validate(config: NormalizedConfig): ValidatedConfig { + if (config.length > 1) { + throw new FirebaseError("More than one functions.source detected in firebase.json."); + } + return [validateSingle(config[0])]; +} + +/** + * Normalize and validate functions config. + * + * Valid functions config has exactly one config and has all required fields set. + */ +export function normalizeAndValidate(config?: FunctionsConfig): ValidatedConfig { + return validate(normalize(config)); +} diff --git a/src/serve/functions.ts b/src/serve/functions.ts index deedf16dbee..e3562df5fbb 100644 --- a/src/serve/functions.ts +++ b/src/serve/functions.ts @@ -9,6 +9,7 @@ import { parseRuntimeVersion } from "../emulator/functionsEmulatorUtils"; import { needProjectId } from "../projectUtils"; import { getProjectDefaultAccount } from "../auth"; import { Options } from "../options"; +import * as projectConfig from "../functions/projectConfig"; import * as utils from "../utils"; // TODO(samstern): It would be better to convert this to an EmulatorServer @@ -25,15 +26,11 @@ export class FunctionsServer { async start(options: Options, partialArgs: Partial): Promise { const projectId = needProjectId(options); - utils.assertDefined(options.config.src.functions); - utils.assertDefined( - options.config.src.functions.source, - "Error: 'functions.source' is not defined" - ); + const config = projectConfig.normalizeAndValidate(options.config.src.functions)[0]; - const functionsDir = path.join(options.config.projectDir, options.config.src.functions.source); + const functionsDir = path.join(options.config.projectDir, config.source); const account = getProjectDefaultAccount(options.config.projectDir); - const nodeMajorVersion = parseRuntimeVersion(options.config.get("functions.runtime")); + const nodeMajorVersion = parseRuntimeVersion(config.runtime); this.backend = { functionsDir, nodeMajorVersion, @@ -64,7 +61,7 @@ export class FunctionsServer { utils.assertIsNumber(options.port); const targets = options.targets as string[] | undefined; const port = options.port; - const hostingRunning = targets && targets.indexOf("hosting") >= 0; + const hostingRunning = targets && targets.includes("hosting"); if (hostingRunning) { args.port = port + 1; } else { diff --git a/src/test/functions/projectConfig.spec.ts b/src/test/functions/projectConfig.spec.ts new file mode 100644 index 00000000000..58676ec1cb2 --- /dev/null +++ b/src/test/functions/projectConfig.spec.ts @@ -0,0 +1,85 @@ +import { expect } from "chai"; + +import * as projectConfig from "../../functions/projectConfig"; +import { FirebaseError } from "../../error"; + +const TEST_CONFIG = { source: "foo" }; + +describe("projectConfig", () => { + describe("normalize", () => { + it("normalizes singleton config", () => { + expect(projectConfig.normalize(TEST_CONFIG)).to.deep.equal([TEST_CONFIG]); + }); + + it("normalizes array config", () => { + expect(projectConfig.normalize([TEST_CONFIG])).to.deep.equal([TEST_CONFIG]); + }); + + it("throws error if given empty config", () => { + expect(() => projectConfig.normalize([])).to.throw(FirebaseError); + }); + }); + + describe("validate", () => { + it("passes validation for simple config", () => { + expect(projectConfig.validate([TEST_CONFIG])).to.deep.equal([TEST_CONFIG]); + }); + + it("fails validation given more than one config", () => { + expect(() => + projectConfig.validate([TEST_CONFIG, { ...TEST_CONFIG, source: "bar" }]) + ).to.throw(FirebaseError); + }); + + it("fails validation given config w/o source", () => { + expect(() => projectConfig.validate([{ runtime: "nodejs10" }])).to.throw( + FirebaseError, + /functions.source must be specified/ + ); + }); + + it("fails validation given config w/ empty source", () => { + expect(() => projectConfig.validate([{ source: "" }])).to.throw( + FirebaseError, + /functions.source must be specified/ + ); + }); + }); + + describe("normalizeAndValidate", () => { + it("returns normalized config for singleton config", () => { + expect(projectConfig.normalizeAndValidate(TEST_CONFIG)).to.deep.equal([TEST_CONFIG]); + }); + + it("returns normalized config for multi-resource config", () => { + expect(projectConfig.normalizeAndValidate([TEST_CONFIG])).to.deep.equal([TEST_CONFIG]); + }); + + it("fails validation given singleton config w/o source", () => { + expect(() => projectConfig.normalizeAndValidate({ runtime: "nodejs10" })).to.throw( + FirebaseError, + /functions.source must be specified/ + ); + }); + + it("fails validation given singleton config w empty source", () => { + expect(() => projectConfig.normalizeAndValidate({ source: "" })).to.throw( + FirebaseError, + /functions.source must be specified/ + ); + }); + + it("fails validation given multi-resource config w/o source", () => { + expect(() => projectConfig.normalizeAndValidate([{ runtime: "nodejs10" }])).to.throw( + FirebaseError, + /functions.source must be specified/ + ); + }); + + it("fails validation given more than one config", () => { + expect(() => + projectConfig.normalizeAndValidate([TEST_CONFIG, { ...TEST_CONFIG, source: "bar" }]) + ).to.throw(FirebaseError, /More than one functions.source detected/); + }); + }); +}); From ae958a3681479012cb5b9b98f505616f4497d6f3 Mon Sep 17 00:00:00 2001 From: abhis3 Date: Fri, 18 Mar 2022 15:29:17 -0700 Subject: [PATCH 0173/1699] Bump Storage Rules Runtime to v1.0.2 (#4334) * Bump Storage Rules Runtime to v1.0.2 --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..c2624d96c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Upgrade Storage Rules Runtime to v1.0.2 diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index eda2fd7c294..d8941e60b47 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -51,14 +51,14 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe }, }, storage: { - downloadPath: path.join(CACHE_DIR, "cloud-storage-rules-runtime-v1.0.1.jar"), - version: "1.0.1", + downloadPath: path.join(CACHE_DIR, "cloud-storage-rules-runtime-v1.0.2.jar"), + version: "1.0.2", opts: { cacheDir: CACHE_DIR, remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-storage-rules-runtime-v1.0.1.jar", - expectedSize: 32729999, - expectedChecksum: "1a441f5e16c17aa7a27db71c9c9186d5", + "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-storage-rules-runtime-v1.0.2.jar", + expectedSize: 35704306, + expectedChecksum: "0dd3e17939610fc3dbdf53fb24cfda86", namePrefix: "cloud-storage-rules-emulator", }, }, From 8b8b32358a283780dca19784a93236206c206b66 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Fri, 18 Mar 2022 16:00:18 -0700 Subject: [PATCH 0174/1699] limit hosting upload concurrency to 10 (#4338) * limit hosting upload concurrency to 10 * add changelog --- CHANGELOG.md | 4 +++- src/deploy/hosting/deploy.ts | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2624d96c20..f823e817e93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ -- Upgrade Storage Rules Runtime to v1.0.2 +- Upgrade Storage Rules Runtime to v1.0.2. +- Lowers the number of concurrent uploads the CLI will attempt to Firebase Hosting. +- Adds support for an environment variable `FIREBASE_HOSTING_UPLOAD_CONCURRENCY` to specify custom levels of Hosting upload concurrency. diff --git a/src/deploy/hosting/deploy.ts b/src/deploy/hosting/deploy.ts index 1fd499d31b2..5f782d11447 100644 --- a/src/deploy/hosting/deploy.ts +++ b/src/deploy/hosting/deploy.ts @@ -3,7 +3,7 @@ import { detectProjectRoot } from "../../detectProjectRoot"; import { listFiles } from "../../listFiles"; import { logger } from "../../logger"; import * as track from "../../track"; -import { logLabeledBullet, logLabeledSuccess } from "../../utils"; +import { envOverride, logLabeledBullet, logLabeledSuccess } from "../../utils"; import { HostingDeploy } from "./hostingDeploy"; import * as clc from "cli-color"; @@ -67,12 +67,23 @@ export async function deploy( `found ${files.length} files in ${clc.bold(deploy.config.public)}` ); + let concurrency = 10; + const envConcurrency = envOverride("FIREBASE_HOSTING_UPLOAD_CONCURRENCY", ""); + if (envConcurrency) { + const c = parseInt(envConcurrency, 10); + if (!isNaN(c) && c > 0) { + concurrency = c; + } + } + + logger.debug(`[hosting] uploading with ${concurrency} concurrency`); const uploader = new Uploader({ version: deploy.version, files: files, public: publicDir, cwd: options.cwd, projectRoot: detectProjectRoot(options), + uploadConcurrency: concurrency, }); const progressInterval = setInterval( From 869fdd54592fac88e2afba8f554c8b380e1ad0c7 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Fri, 18 Mar 2022 16:16:49 -0700 Subject: [PATCH 0175/1699] rewrite accountExport to typescript & improve error handling (#4323) * rewrite accountExporter and fix error logic * add changelog * fix test incorrectly returning a nextPageToken * clean up some typescript --- CHANGELOG.md | 1 + src/accountExporter.js | 205 ----------------------------- src/accountExporter.ts | 213 +++++++++++++++++++++++++++++++ src/test/accountExporter.spec.ts | 6 +- 4 files changed, 215 insertions(+), 210 deletions(-) delete mode 100644 src/accountExporter.js create mode 100644 src/accountExporter.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f823e817e93..10a837cc059 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Upgrade Storage Rules Runtime to v1.0.2. - Lowers the number of concurrent uploads the CLI will attempt to Firebase Hosting. - Adds support for an environment variable `FIREBASE_HOSTING_UPLOAD_CONCURRENCY` to specify custom levels of Hosting upload concurrency. +- Fixes error handling in `auth:export` when API calls would fail. diff --git a/src/accountExporter.js b/src/accountExporter.js deleted file mode 100644 index 548e0ab19e1..00000000000 --- a/src/accountExporter.js +++ /dev/null @@ -1,205 +0,0 @@ -"use strict"; - -var os = require("os"); -var path = require("path"); -var _ = require("lodash"); - -const { Client } = require("./apiv2"); -const { googleOrigin } = require("./api"); -var { FirebaseError } = require("./error"); -var utils = require("./utils"); - -const apiClient = new Client({ - urlPrefix: googleOrigin, -}); - -// TODO: support for MFA at runtime was added in PR #3173, but this exporter currently ignores `mfaInfo` and loses the data on export. -var EXPORTED_JSON_KEYS = [ - "localId", - "email", - "emailVerified", - "passwordHash", - "salt", - "displayName", - "photoUrl", - "lastLoginAt", - "createdAt", - "phoneNumber", - "disabled", - "customAttributes", -]; -var EXPORTED_JSON_KEYS_RENAMING = { - lastLoginAt: "lastSignedInAt", -}; -var EXPORTED_PROVIDER_USER_INFO_KEYS = ["providerId", "rawId", "email", "displayName", "photoUrl"]; -var PROVIDER_ID_INDEX_MAP = { - "google.com": 7, - "facebook.com": 11, - "twitter.com": 15, - "github.com": 19, -}; - -var _escapeComma = function (str) { - if (str.indexOf(",") !== -1) { - // Encapsulate the string with quotes if it contains a comma. - return `"${str}"`; - } - return str; -}; - -var _convertToNormalBase64 = function (data) { - return data.replace(/_/g, "/").replace(/-/g, "+"); -}; - -var _addProviderUserInfo = function (providerInfo, arr, startPos) { - arr[startPos] = providerInfo.rawId; - arr[startPos + 1] = providerInfo.email || ""; - arr[startPos + 2] = _escapeComma(providerInfo.displayName || ""); - arr[startPos + 3] = providerInfo.photoUrl || ""; -}; - -var _transUserToArray = function (user) { - var arr = Array(27).fill(""); - arr[0] = user.localId; - arr[1] = user.email || ""; - arr[2] = user.emailVerified || false; - arr[3] = _convertToNormalBase64(user.passwordHash || ""); - arr[4] = _convertToNormalBase64(user.salt || ""); - arr[5] = _escapeComma(user.displayName || ""); - arr[6] = user.photoUrl || ""; - for (var i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) { - var providerInfo = user.providerUserInfo[i]; - if (providerInfo && PROVIDER_ID_INDEX_MAP[providerInfo.providerId]) { - _addProviderUserInfo(providerInfo, arr, PROVIDER_ID_INDEX_MAP[providerInfo.providerId]); - } - } - arr[23] = user.createdAt; - arr[24] = user.lastLoginAt; - arr[25] = user.phoneNumber; - arr[26] = user.disabled; - arr[27] = user.customAttributes; - return arr; -}; - -var _transUserJson = function (user) { - var newUser = {}; - _.each(_.pick(user, EXPORTED_JSON_KEYS), function (value, key) { - var newKey = EXPORTED_JSON_KEYS_RENAMING[key] || key; - newUser[newKey] = value; - }); - if (newUser.passwordHash) { - newUser.passwordHash = _convertToNormalBase64(newUser.passwordHash); - } - if (newUser.salt) { - newUser.salt = _convertToNormalBase64(newUser.salt); - } - if (user.providerUserInfo) { - newUser.providerUserInfo = []; - user.providerUserInfo.forEach(function (providerInfo) { - if (!_.includes(Object.keys(PROVIDER_ID_INDEX_MAP), providerInfo.providerId)) { - return; - } - newUser.providerUserInfo.push(_.pick(providerInfo, EXPORTED_PROVIDER_USER_INFO_KEYS)); - }); - } - return newUser; -}; - -var validateOptions = function (options, fileName) { - var exportOptions = {}; - if (fileName === undefined) { - throw new FirebaseError("Must specify data file", { exit: 1 }); - } - var extName = path.extname(fileName.toLowerCase()); - if (extName === ".csv") { - exportOptions.format = "csv"; - } else if (extName === ".json") { - exportOptions.format = "json"; - } else if (options.format) { - var format = options.format.toLowerCase(); - if (format === "csv" || format === "json") { - exportOptions.format = format; - } else { - throw new FirebaseError("Unsupported data file format, should be csv or json", { exit: 1 }); - } - } else { - throw new FirebaseError( - "Please specify data file format in file name, or use `format` parameter", - { - exit: 1, - } - ); - } - return exportOptions; -}; - -var _createWriteUsersToFile = function () { - var jsonSep = ""; - return function (userList, format, writeStream) { - userList.map(function (user) { - if (user.passwordHash && user.version !== 0) { - // Password isn't hashed by default Scrypt. - delete user.passwordHash; - delete user.salt; - } - if (format === "csv") { - writeStream.write(_transUserToArray(user).join(",") + "," + os.EOL, "utf8"); - } else { - writeStream.write(jsonSep + JSON.stringify(_transUserJson(user), null, 2), "utf8"); - jsonSep = "," + os.EOL; - } - }); - }; -}; - -var serialExportUsers = function (projectId, options) { - if (!options.writeUsersToFile) { - options.writeUsersToFile = _createWriteUsersToFile(); - } - var postBody = { - targetProjectId: projectId, - maxResults: options.batchSize, - }; - if (options.nextPageToken) { - postBody.nextPageToken = options.nextPageToken; - } - if (!options.timeoutRetryCount) { - options.timeoutRetryCount = 0; - } - return apiClient - .post("/identitytoolkit/v3/relyingparty/downloadAccount", postBody, { - skipLog: { resBody: true }, // This contains a lot of PII - don't log it. - }) - .then(function (ret) { - options.timeoutRetryCount = 0; - var userList = ret.body.users; - if (userList && userList.length > 0) { - options.writeUsersToFile(userList, options.format, options.writeStream); - utils.logSuccess("Exported " + userList.length + " account(s) successfully."); - // The identitytoolkit API do not return a nextPageToken value - // consistently when the last page is reached - if (!ret.body.nextPageToken) { - return; - } - options.nextPageToken = ret.body.nextPageToken; - return serialExportUsers(projectId, options); - } - }) - .catch((err) => { - // Calling again in case of error timedout so that script won't exit - if (err.original.code === "ETIMEDOUT") { - options.timeoutRetryCount++; - if (options.timeoutRetryCount > 5) { - return err; - } - return serialExportUsers(projectId, options); - } - }); -}; - -var accountExporter = { - validateOptions: validateOptions, - serialExportUsers: serialExportUsers, -}; - -module.exports = accountExporter; diff --git a/src/accountExporter.ts b/src/accountExporter.ts new file mode 100644 index 00000000000..13f681737a5 --- /dev/null +++ b/src/accountExporter.ts @@ -0,0 +1,213 @@ +import { Writable } from "stream"; +import * as _ from "lodash"; +import * as os from "os"; +import * as path from "path"; + +import { Client } from "./apiv2"; +import { FirebaseError } from "./error"; +import { googleOrigin } from "./api"; +import * as utils from "./utils"; + +const apiClient = new Client({ + urlPrefix: googleOrigin, +}); + +// TODO: support for MFA at runtime was added in PR #3173, but this exporter currently ignores `mfaInfo` and loses the data on export. +const EXPORTED_JSON_KEYS = [ + "localId", + "email", + "emailVerified", + "passwordHash", + "salt", + "displayName", + "photoUrl", + "lastLoginAt", + "createdAt", + "phoneNumber", + "disabled", + "customAttributes", +]; +const EXPORTED_JSON_KEYS_RENAMING: Record = { + lastLoginAt: "lastSignedInAt", +}; +const EXPORTED_PROVIDER_USER_INFO_KEYS = [ + "providerId", + "rawId", + "email", + "displayName", + "photoUrl", +]; +const PROVIDER_ID_INDEX_MAP = new Map([ + ["google.com", 7], + ["facebook.com", 11], + ["twitter.com", 15], + ["github.com", 19], +]); + +function escapeComma(str: string): string { + if (str.includes(",")) { + // Encapsulate the string with quotes if it contains a comma. + return `"${str}"`; + } + return str; +} + +function convertToNormalBase64(data: string): string { + return data.replace(/_/g, "/").replace(/-/g, "+"); +} + +function addProviderUserInfo(providerInfo: any, arr: any[], startPos: number): void { + arr[startPos] = providerInfo.rawId; + arr[startPos + 1] = providerInfo.email || ""; + arr[startPos + 2] = escapeComma(providerInfo.displayName || ""); + arr[startPos + 3] = providerInfo.photoUrl || ""; +} + +function transUserToArray(user: any): any[] { + const arr = Array(27).fill(""); + arr[0] = user.localId; + arr[1] = user.email || ""; + arr[2] = user.emailVerified || false; + arr[3] = convertToNormalBase64(user.passwordHash || ""); + arr[4] = convertToNormalBase64(user.salt || ""); + arr[5] = escapeComma(user.displayName || ""); + arr[6] = user.photoUrl || ""; + for (let i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) { + const providerInfo = user.providerUserInfo[i]; + if (providerInfo) { + const providerIndex = PROVIDER_ID_INDEX_MAP.get(providerInfo.providerId); + if (providerIndex) { + addProviderUserInfo(providerInfo, arr, providerIndex); + } + } + } + arr[23] = user.createdAt; + arr[24] = user.lastLoginAt; + arr[25] = user.phoneNumber; + arr[26] = user.disabled; + arr[27] = user.customAttributes; + return arr; +} + +function transUserJson(user: any): any { + const newUser: any = {}; + _.each(_.pick(user, EXPORTED_JSON_KEYS), (value, key) => { + const newKey = EXPORTED_JSON_KEYS_RENAMING[key] || key; + newUser[newKey] = value; + }); + if (newUser.passwordHash) { + newUser.passwordHash = convertToNormalBase64(newUser.passwordHash); + } + if (newUser.salt) { + newUser.salt = convertToNormalBase64(newUser.salt); + } + if (user.providerUserInfo) { + newUser.providerUserInfo = []; + for (const providerInfo of user.providerUserInfo) { + if (PROVIDER_ID_INDEX_MAP.has(providerInfo.providerId)) { + newUser.providerUserInfo.push(_.pick(providerInfo, EXPORTED_PROVIDER_USER_INFO_KEYS)); + } + } + } + return newUser; +} + +export function validateOptions(options: any, fileName: string): any { + const exportOptions: any = {}; + if (fileName === undefined) { + throw new FirebaseError("Must specify data file"); + } + const extName = path.extname(fileName.toLowerCase()); + if (extName === ".csv") { + exportOptions.format = "csv"; + } else if (extName === ".json") { + exportOptions.format = "json"; + } else if (options.format) { + const format = options.format.toLowerCase(); + if (format === "csv" || format === "json") { + exportOptions.format = format; + } else { + throw new FirebaseError("Unsupported data file format, should be csv or json"); + } + } else { + throw new FirebaseError( + "Please specify data file format in file name, or use `format` parameter" + ); + } + return exportOptions; +} + +function createWriteUsersToFile(): ( + userList: any[], + format: "csv" | "json", + writeStream: Writable +) => void { + let jsonSep = ""; + return (userList: any[], format: "csv" | "json", writeStream: Writable) => { + userList.map((user) => { + if (user.passwordHash && user.version !== 0) { + // Password isn't hashed by default Scrypt. + delete user.passwordHash; + delete user.salt; + } + if (format === "csv") { + writeStream.write(transUserToArray(user).join(",") + "," + os.EOL, "utf8"); + } else { + writeStream.write(jsonSep + JSON.stringify(transUserJson(user), null, 2), "utf8"); + jsonSep = "," + os.EOL; + } + }); + }; +} + +export async function serialExportUsers(projectId: string, options: any): Promise { + if (!options.writeUsersToFile) { + options.writeUsersToFile = createWriteUsersToFile(); + } + const postBody: any = { + targetProjectId: projectId, + maxResults: options.batchSize, + }; + if (options.nextPageToken) { + postBody.nextPageToken = options.nextPageToken; + } + if (!options.timeoutRetryCount) { + options.timeoutRetryCount = 0; + } + try { + const ret = await apiClient.post( + "/identitytoolkit/v3/relyingparty/downloadAccount", + postBody, + { + skipLog: { resBody: true }, // This contains a lot of PII - don't log it. + } + ); + options.timeoutRetryCount = 0; + const userList = ret.body.users; + if (userList && userList.length > 0) { + options.writeUsersToFile(userList, options.format, options.writeStream); + utils.logSuccess("Exported " + userList.length + " account(s) successfully."); + // The identitytoolkit API do not return a nextPageToken value + // consistently when the last page is reached + if (!ret.body.nextPageToken) { + return; + } + options.nextPageToken = ret.body.nextPageToken; + return serialExportUsers(projectId, options); + } + } catch (err: any) { + // Calling again in case of error timedout so that script won't exit + if (err.original?.code === "ETIMEDOUT") { + options.timeoutRetryCount++; + if (options.timeoutRetryCount > 5) { + return err; + } + return serialExportUsers(projectId, options); + } + if (err instanceof FirebaseError) { + throw err; + } else { + throw new FirebaseError(`Failed to export accounts: ${err}`, { original: err }); + } + } +} diff --git a/src/test/accountExporter.spec.ts b/src/test/accountExporter.spec.ts index 6592cd4b884..1d89e73e5fa 100644 --- a/src/test/accountExporter.spec.ts +++ b/src/test/accountExporter.spec.ts @@ -4,12 +4,9 @@ import * as nock from "nock"; import * as os from "os"; import * as sinon from "sinon"; -import * as accountExporter from "../accountExporter"; +import { validateOptions, serialExportUsers } from "../accountExporter"; describe("accountExporter", () => { - const validateOptions = accountExporter.validateOptions; - const serialExportUsers = accountExporter.serialExportUsers; - describe("validateOptions", () => { it("should reject when no format provided", () => { expect(() => validateOptions({}, "output_file")).to.throw; @@ -231,7 +228,6 @@ describe("accountExporter", () => { }) .reply(200, { users: userList.slice(0, 3), - nextPageToken: "3", }); await serialExportUsers("test-project-id", { format: "JSON", From fe1c8c325050d20c69859d95dfa9db42389e8cc7 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Fri, 18 Mar 2022 18:07:46 -0700 Subject: [PATCH 0176/1699] revert hosting upload concurrency to 200 (#4339) --- CHANGELOG.md | 3 +-- src/deploy/hosting/deploy.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10a837cc059..08cb1762427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,3 @@ - Upgrade Storage Rules Runtime to v1.0.2. -- Lowers the number of concurrent uploads the CLI will attempt to Firebase Hosting. -- Adds support for an environment variable `FIREBASE_HOSTING_UPLOAD_CONCURRENCY` to specify custom levels of Hosting upload concurrency. +- Adds support for an environment variable `FIREBASE_HOSTING_UPLOAD_CONCURRENCY` to specify custom levels of Hosting upload concurrency (defaults to 200). - Fixes error handling in `auth:export` when API calls would fail. diff --git a/src/deploy/hosting/deploy.ts b/src/deploy/hosting/deploy.ts index 5f782d11447..d422a15f617 100644 --- a/src/deploy/hosting/deploy.ts +++ b/src/deploy/hosting/deploy.ts @@ -67,7 +67,7 @@ export async function deploy( `found ${files.length} files in ${clc.bold(deploy.config.public)}` ); - let concurrency = 10; + let concurrency = 200; const envConcurrency = envOverride("FIREBASE_HOSTING_UPLOAD_CONCURRENCY", ""); if (envConcurrency) { const c = parseInt(envConcurrency, 10); From 58d860d9c5286d6fa9f6c0f864129c907601ea7d Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Sat, 19 Mar 2022 02:30:47 +0000 Subject: [PATCH 0177/1699] Ensure callable functions have invoker correctly set to "public" (#4335) Implemented fix for issue introduced in `v10.3.0` where callable functions were not having their `invoker` property correctly set to `"public"` that would in turn cause access issues with the deployed functions (as detailed in #4327). Will also fix issues such as #3965, that are presumably cause by version mismatches/upgrades introducing this issue. Fixes issue by adding specific handling for callable functions and always setting the `invoker` to `"public"`. Also fixed issue with `taskQueueTrigger` endpoints calling wrong method/passing wrong args when trying to set invoker. --- CHANGELOG.md | 1 + src/deploy/functions/release/fabricator.ts | 14 +- .../functions/release/fabricator.spec.ts | 315 +++++++++++++----- 3 files changed, 250 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08cb1762427..aaa4126c554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Upgrade Storage Rules Runtime to v1.0.2. - Adds support for an environment variable `FIREBASE_HOSTING_UPLOAD_CONCURRENCY` to specify custom levels of Hosting upload concurrency (defaults to 200). - Fixes error handling in `auth:export` when API calls would fail. +- Fixes bug where new callable functions were not publicly accessible. (#4327) diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 42f9e583efa..ac7b84e8677 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -233,6 +233,13 @@ export class Fabricator { }) .catch(rethrowAs(endpoint, "set invoker")); } + } else if (backend.isCallableTriggered(endpoint)) { + // Callable functions should always be public + await this.executor + .run(async () => { + await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), ["public"]); + }) + .catch(rethrowAs(endpoint, "set invoker")); } else if (backend.isTaskQueueTriggered(endpoint)) { // Like HTTPS triggers, taskQueueTriggers have an invoker, but unlike HTTPS they don't default // public. @@ -297,6 +304,11 @@ export class Fabricator { .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker)) .catch(rethrowAs(endpoint, "set invoker")); } + } else if (backend.isCallableTriggered(endpoint)) { + // Callable functions should always be public + await this.executor + .run(() => run.setInvokerCreate(endpoint.project, serviceName, ["public"])) + .catch(rethrowAs(endpoint, "set invoker")); } else if (backend.isTaskQueueTriggered(endpoint)) { // Like HTTPS triggers, taskQueueTriggers have an invoker, but unlike HTTPS they don't default // public. @@ -304,7 +316,7 @@ export class Fabricator { if (invoker && !invoker.includes("private")) { await this.executor .run(async () => { - await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker); + await run.setInvokerCreate(endpoint.project, serviceName, invoker); }) .catch(rethrowAs(endpoint, "set invoker")); } diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 502f4618013..6ea04040709 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -137,71 +137,151 @@ describe("Fabricator", () => { ).to.be.rejectedWith(reporter.DeploymentError, "set invoker"); }); - it("enforces SECURE_ALWAYS HTTPS policies", async () => { - gcf.createFunction.resolves({ name: "op", type: "create", done: false }); - poller.pollOperation.resolves(); - gcf.setInvokerCreate.resolves(); - const ep = endpoint(); + describe("httpsTrigger", () => { + it("enforces SECURE_ALWAYS HTTPS policies", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint(); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.createFunction).to.have.been.calledWithMatch({ + httpsTrigger: { + securityLevel: "SECURE_ALWAYS", + }, + }); + }); - await fab.createV1Function(ep, new scraper.SourceTokenScraper()); - expect(gcf.createFunction).to.have.been.calledWithMatch({ - httpsTrigger: { - securityLevel: "SECURE_ALWAYS", - }, + it("sets public invoker by default", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint(); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.setInvokerCreate).to.have.been.calledWith(ep.project, backend.functionName(ep), [ + "public", + ]); }); - }); - it("sets invoker by default", async () => { - gcf.createFunction.resolves({ name: "op", type: "create", done: false }); - poller.pollOperation.resolves(); - gcf.setInvokerCreate.resolves(); - const ep = endpoint(); + it("sets explicit invoker", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint({ + httpsTrigger: { + invoker: ["custom@"], + }, + }); - await fab.createV1Function(ep, new scraper.SourceTokenScraper()); - expect(gcf.setInvokerCreate).to.have.been.calledWith(ep.project, backend.functionName(ep), [ - "public", - ]); + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.setInvokerCreate).to.have.been.calledWith(ep.project, backend.functionName(ep), [ + "custom@", + ]); + }); + + it("doesn't set private invoker on create", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint({ + httpsTrigger: { + invoker: ["private"], + }, + }); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.setInvokerCreate).to.not.have.been.called; + }); }); - it("sets explicit invoker", async () => { - gcf.createFunction.resolves({ name: "op", type: "create", done: false }); - poller.pollOperation.resolves(); - gcf.setInvokerCreate.resolves(); - const ep = endpoint({ - httpsTrigger: { - invoker: ["custom@"], - }, + describe("callableTrigger", () => { + it("enforces SECURE_ALWAYS HTTPS policies", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint({ callableTrigger: {} }); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.createFunction).to.have.been.calledWithMatch({ + httpsTrigger: { + securityLevel: "SECURE_ALWAYS", + }, + }); }); - await fab.createV1Function(ep, new scraper.SourceTokenScraper()); - expect(gcf.setInvokerCreate).to.have.been.calledWith(ep.project, backend.functionName(ep), [ - "custom@", - ]); + it("always sets invoker to public", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint({ callableTrigger: {} }); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.setInvokerCreate).to.have.been.calledWith(ep.project, backend.functionName(ep), [ + "public", + ]); + }); }); - it("doesn't set private invoker on create", async () => { - gcf.createFunction.resolves({ name: "op", type: "create", done: false }); - poller.pollOperation.resolves(); - gcf.setInvokerCreate.resolves(); - const ep = endpoint({ - httpsTrigger: { - invoker: ["private"], - }, + describe("taskQueueTrigger", () => { + it("enforces SECURE_ALWAYS HTTPS policies", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint({ taskQueueTrigger: {} }); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.createFunction).to.have.been.calledWithMatch({ + httpsTrigger: { + securityLevel: "SECURE_ALWAYS", + }, + }); }); - await fab.createV1Function(ep, new scraper.SourceTokenScraper()); - expect(gcf.setInvokerCreate).to.not.have.been.called; + it("doesn't set invoker by default", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint({ taskQueueTrigger: {} }); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.setInvokerCreate).to.not.have.been.called; + }); + + it("sets explicit invoker", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint({ + httpsTrigger: { + invoker: ["custom@"], + }, + }); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + expect(gcf.setInvokerCreate).to.have.been.calledWith(ep.project, backend.functionName(ep), [ + "custom@", + ]); + }); }); it("doesn't set invoker on non-http functions", async () => { gcf.createFunction.resolves({ name: "op", type: "create", done: false }); poller.pollOperation.resolves(); gcf.setInvokerCreate.resolves(); - const ep = endpoint({ + const ep0 = endpoint({ scheduleTrigger: {}, }); + const ep1 = endpoint({ + eventTrigger: { + eventType: "some.event", + eventFilters: [{ attribute: "resource", value: "some-resource" }], + retry: false, + }, + }); - await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + await fab.createV1Function(ep0, new scraper.SourceTokenScraper()); + await fab.createV1Function(ep1, new scraper.SourceTokenScraper()); expect(gcf.setInvokerCreate).to.not.have.been.called; }); }); @@ -241,14 +321,23 @@ describe("Fabricator", () => { gcf.updateFunction.resolves({ name: "op", type: "create", done: false }); poller.pollOperation.resolves(); gcf.setInvokerUpdate.resolves(); - const ep = endpoint({ + const ep0 = endpoint({ httpsTrigger: { invoker: ["custom@"], }, }); + const ep1 = endpoint({ + taskQueueTrigger: { + invoker: ["custom@"], + }, + }); - await fab.updateV1Function(ep, new scraper.SourceTokenScraper()); - expect(gcf.setInvokerUpdate).to.have.been.calledWith(ep.project, backend.functionName(ep), [ + await fab.updateV1Function(ep0, new scraper.SourceTokenScraper()); + await fab.updateV1Function(ep1, new scraper.SourceTokenScraper()); + expect(gcf.setInvokerUpdate).to.have.been.calledWith(ep0.project, backend.functionName(ep0), [ + "custom@", + ]); + expect(gcf.setInvokerUpdate).to.have.been.calledWith(ep1.project, backend.functionName(ep1), [ "custom@", ]); }); @@ -298,7 +387,7 @@ describe("Fabricator", () => { setConcurrency.resolves(); }); - it("handles topiocs that already exist", async () => { + it("handles topics that already exist", async () => { pubsub.createTopic.callsFake(() => { const err = new Error("Already exists"); (err as any).status = 409; @@ -381,14 +470,13 @@ describe("Fabricator", () => { ); }); - it("sets invoker and concurrency by default for large functions", async () => { + it("sets concurrency by default for large functions", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2", availableMemoryMb: 2048 }); await fab.createV2Function(ep); - expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["public"]); expect(setConcurrency).to.have.been.calledWith(ep, "service", 80); }); @@ -403,54 +491,106 @@ describe("Fabricator", () => { expect(setConcurrency).to.not.have.been.called; }); - it("sets explicit invoker", async () => { + it("sets explicit concurrency", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); const ep = endpoint( - { - httpsTrigger: { - invoker: ["custom@"], - }, - }, - { platform: "gcfv2" } + { httpsTrigger: {} }, + { platform: "gcfv2", availableMemoryMb: 2048, concurrency: 2 } ); await fab.createV2Function(ep); - expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["custom@"]); + expect(setConcurrency).to.have.been.calledWith(ep, "service", 2); }); - it("doesn't set private invoker on create", async () => { - gcfv2.createFunction.resolves({ name: "op", done: false }); - poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); - run.setInvokerCreate.resolves(); - const ep = endpoint({ httpsTrigger: { invoker: ["private"] } }, { platform: "gcfv2" }); + describe("httpsTrigger", () => { + it("sets invoker to public by default", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2" }); - await fab.createV2Function(ep); - expect(gcf.setInvokerCreate).to.not.have.been.called; + await fab.createV2Function(ep); + expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["public"]); + }); + + it("sets explicit invoker", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint( + { + httpsTrigger: { + invoker: ["custom@"], + }, + }, + { platform: "gcfv2" } + ); + + await fab.createV2Function(ep); + expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["custom@"]); + }); + + it("doesn't set private invoker on create", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint({ httpsTrigger: { invoker: ["private"] } }, { platform: "gcfv2" }); + + await fab.createV2Function(ep); + expect(run.setInvokerCreate).to.not.have.been.called; + }); }); - it("doesn't set invoker on non-http functions", async () => { - gcfv2.createFunction.resolves({ name: "op", done: false }); - poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); - run.setInvokerCreate.resolves(); - const ep = endpoint({ scheduleTrigger: {} }, { platform: "gcfv2" }); + describe("callableTrigger", () => { + it("always sets invoker to public", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint({ callableTrigger: {} }, { platform: "gcfv2" }); - await fab.createV2Function(ep); - expect(run.setInvokerCreate).to.not.have.been.called; + await fab.createV2Function(ep); + expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["public"]); + }); }); - it("sets explicit concurrency", async () => { + describe("taskQueueTrigger", () => { + it("doesn't set invoker by default", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint({ taskQueueTrigger: {} }, { platform: "gcfv2" }); + + await fab.createV2Function(ep); + expect(run.setInvokerCreate).to.not.have.been.called; + }); + + it("sets explicit invoker", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint( + { + taskQueueTrigger: { + invoker: ["custom@"], + }, + }, + { platform: "gcfv2" } + ); + await fab.createV2Function(ep); + expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["custom@"]); + }); + }); + + it("doesn't set invoker on non-http functions", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); - const ep = endpoint( - { httpsTrigger: {} }, - { platform: "gcfv2", availableMemoryMb: 2048, concurrency: 2 } - ); + const ep = endpoint({ scheduleTrigger: {} }, { platform: "gcfv2" }); await fab.createV2Function(ep); - expect(setConcurrency).to.have.been.calledWith(ep, "service", 2); + expect(run.setInvokerCreate).to.not.have.been.called; }); }); @@ -478,7 +618,7 @@ describe("Fabricator", () => { ); }); - it("sets explicit invoker", async () => { + it("sets explicit invoker on httpsTrigger", async () => { gcfv2.updateFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerUpdate.resolves(); @@ -495,6 +635,23 @@ describe("Fabricator", () => { expect(run.setInvokerUpdate).to.have.been.calledWith(ep.project, "service", ["custom@"]); }); + it("sets explicit invoker on taskQueueTrigger", async () => { + gcfv2.updateFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerUpdate.resolves(); + const ep = endpoint( + { + taskQueueTrigger: { + invoker: ["custom@"], + }, + }, + { platform: "gcfv2" } + ); + + await fab.updateV2Function(ep); + expect(run.setInvokerUpdate).to.have.been.calledWith(ep.project, "service", ["custom@"]); + }); + it("does not set invoker by default", async () => { gcfv2.updateFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); From 59a38134c5ddb0c50daddcbc2775f0c964104749 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Mon, 21 Mar 2022 14:22:25 -0400 Subject: [PATCH 0178/1699] [ext:* --local] Fix bug where optional secrets where no longer optional (#4344) * Update askUserForParam.ts * pr fixes --- src/extensions/askUserForParam.ts | 42 ++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index c9758fed557..fa0eb9c440c 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -168,9 +168,10 @@ export async function askForParam(args: { valid = checkResponse(response, paramSpec); break; case ParamType.SECRET: - while (!secretLocations.length) { - secretLocations = await promptSecretLocations(); - } + do { + secretLocations = await promptSecretLocations(paramSpec); + } while (!isValidSecretLocations(secretLocations, paramSpec)); + if (secretLocations.includes(SecretLocation.CLOUD.toString())) { response = args.reconfiguring ? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec) @@ -195,14 +196,43 @@ export async function askForParam(args: { return { baseValue: response, ...(responseForLocal ? { local: responseForLocal } : {}) }; } -async function promptSecretLocations(): Promise { +function isValidSecretLocations(secretLocations: string[], paramSpec: Param): boolean { + if (paramSpec.required) { + return !!secretLocations.length; + } + return true; +} + +async function promptSecretLocations(paramSpec: Param): Promise { + if (paramSpec.required) { + return await promptOnce({ + name: "input", + type: "checkbox", + message: "Where would you like to store your secrets? You must select at least one value", + choices: [ + { + checked: true, + name: "Google Cloud Secret Manager", + // return type of string is not actually enforced, need to manually convert. + value: SecretLocation.CLOUD.toString(), + }, + { + checked: false, + name: "Local file (Only used by Firebase Emulator)", + value: SecretLocation.LOCAL.toString(), + }, + ], + }); + } return await promptOnce({ name: "input", type: "checkbox", - message: "Where would you like to store your secrets? You must select at least one value", + message: + "Where would you like to store your secrets? " + + "If you don't want to set this optional secret, leave both options unselected to skip it", choices: [ { - checked: true, + checked: false, name: "Google Cloud Secret Manager", // return type of string is not actually enforced, need to manually convert. value: SecretLocation.CLOUD.toString(), From db6dbe3dd7451827002d6fb7c1941d5abfa1a39d Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 21 Mar 2022 20:51:30 +0000 Subject: [PATCH 0179/1699] 10.4.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2b0225e9e0f..2fcb916aea5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.4.0", + "version": "10.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.4.0", + "version": "10.4.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index ebb7ee9f3f5..4541d902db9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.4.0", + "version": "10.4.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From c3931e3c2a09125dd0abd00b58a91a29c5cd36c6 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 21 Mar 2022 20:51:54 +0000 Subject: [PATCH 0180/1699] [firebase-release] Removed change log and reset repo after 10.4.1 release --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa4126c554..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +0,0 @@ -- Upgrade Storage Rules Runtime to v1.0.2. -- Adds support for an environment variable `FIREBASE_HOSTING_UPLOAD_CONCURRENCY` to specify custom levels of Hosting upload concurrency (defaults to 200). -- Fixes error handling in `auth:export` when API calls would fail. -- Fixes bug where new callable functions were not publicly accessible. (#4327) From daa604a78cc648abae814350020aa4becb5ce207 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 21 Mar 2022 16:04:48 -0700 Subject: [PATCH 0181/1699] Substitute params into backendInfo, and add unit tests (#4328) * Substitute params into backendInfo, and add unit tests * small pr fixes * replacing some code i accidentally removed in a merge --- src/emulator/functionsEmulator.ts | 26 +-- src/emulator/functionsEmulatorShared.ts | 40 ++++- .../emulators/functionsEmulatorShared.spec.ts | 161 +++++++++++++++++- 3 files changed, 208 insertions(+), 19 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index cae2fb31c2a..542ca76b3af 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -42,6 +42,7 @@ import { emulatedFunctionsFromEndpoints, emulatedFunctionsByRegion, getSecretLocalPath, + toBackendInfo, } from "./functionsEmulatorShared"; import { EmulatorRegistry } from "./registry"; import { EmulatorLogger, Verbosity } from "./emulatorLogger"; @@ -833,27 +834,18 @@ export class FunctionsEmulator implements EmulatorInstance { } getBackendInfo(): BackendInfo[] { - const cf3Triggers = Object.values(this.triggers) - .filter((t) => !t.backend.extensionInstanceId) - .map((t) => t.def); + const cf3Triggers = this.getCF3Triggers(); return this.args.emulatableBackends.map((e: EmulatableBackend) => { - const envWithSecrets = Object.assign({}, e.env); - for (const s of e.secretEnv) { - envWithSecrets[s.key] = backend.secretVersionName(s); - } - - return { - directory: e.functionsDir, - env: envWithSecrets, - extensionInstanceId: e.extensionInstanceId, // Present on all extensions - extension: e.extension, // Only present on published extensions - extensionVersion: e.extensionVersion, // Only present on published extensions - extensionSpec: e.extensionSpec, // Only present on local extensions - functionTriggers: e.predefinedTriggers ?? cf3Triggers, // If we don't have predefinedTriggers, this is the CF3 backend. - }; + return toBackendInfo(e, cf3Triggers); }); } + getCF3Triggers(): ParsedTriggerDefinition[] { + return Object.values(this.triggers) + .filter((t) => !t.backend.extensionInstanceId) + .map((t) => t.def); + } + addTriggerRecord( def: EmulatedTriggerDefinition, opts: { diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 8d4ad621944..a281f0a8774 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -7,10 +7,12 @@ import * as fs from "fs"; import * as backend from "../deploy/functions/backend"; import { Constants } from "./constants"; -import { EmulatableBackend, InvokeRuntimeOpts } from "./functionsEmulator"; +import { BackendInfo, EmulatableBackend, InvokeRuntimeOpts } from "./functionsEmulator"; import { copyIfPresent } from "../gcp/proto"; import { logger } from "../logger"; import { ENV_DIRECTORY } from "../extensions/manifest"; +import { substituteParams } from "../extensions/extensionsHelper"; +import { ExtensionSpec, ExtensionVersion } from "../extensions/extensionsApi"; export type SignatureType = "http" | "event" | "cloudevent"; @@ -391,3 +393,39 @@ export function getSecretLocalPath(backend: EmulatableBackend, projectDir: strin : backend.functionsDir; return path.join(secretDirectory, secretsFile); } + +/** + * toBackendInfo transforms an EmulatableBackend into its correspondign API type, BackendInfo + * @param e the emulatableBackend to transform + * @param cf3Triggers a list of CF3 triggers. If e does not include predefinedTriggers, these will be used instead. + */ +export function toBackendInfo( + e: EmulatableBackend, + cf3Triggers: ParsedTriggerDefinition[] +): BackendInfo { + const envWithSecrets = Object.assign({}, e.env); + for (const s of e.secretEnv) { + envWithSecrets[s.key] = backend.secretVersionName(s); + } + let extensionVersion = e.extensionVersion; + if (extensionVersion) { + extensionVersion = substituteParams(extensionVersion, e.env); + } + let extensionSpec = e.extensionSpec; + if (extensionSpec) { + extensionSpec = substituteParams(extensionSpec, e.env); + } + + // Parse and stringify to get rid of undefined values + return JSON.parse( + JSON.stringify({ + directory: e.functionsDir, + env: envWithSecrets, + extensionInstanceId: e.extensionInstanceId, // Present on all extensions + extension: e.extension, // Only present on published extensions + extensionVersion: extensionVersion, // Only present on published extensions + extensionSpec: extensionSpec, // Only present on local extensions + functionTriggers: e.predefinedTriggers ?? cf3Triggers, // If we don't have predefinedTriggers, this is the CF3 backend. + }) + ); +} diff --git a/src/test/emulators/functionsEmulatorShared.spec.ts b/src/test/emulators/functionsEmulatorShared.spec.ts index 72f37faa8fe..6d9b4df64b0 100644 --- a/src/test/emulators/functionsEmulatorShared.spec.ts +++ b/src/test/emulators/functionsEmulatorShared.spec.ts @@ -1,6 +1,13 @@ import { expect } from "chai"; -import { EmulatableBackend } from "../../emulator/functionsEmulator"; +import { BackendInfo, EmulatableBackend } from "../../emulator/functionsEmulator"; import * as functionsEmulatorShared from "../../emulator/functionsEmulatorShared"; +import { + Extension, + ExtensionSpec, + ExtensionVersion, + RegistryLaunchStage, + Visibility, +} from "../../extensions/extensionsApi"; const baseDef = { platform: "gcfv1" as const, @@ -126,4 +133,156 @@ describe("FunctionsEmulatorShared", () => { }); } }); + + describe(`${functionsEmulatorShared.toBackendInfo.name}`, () => { + const testCF3Triggers: functionsEmulatorShared.ParsedTriggerDefinition[] = [ + { + entryPoint: "cf3", + platform: "gcfv1", + name: "cf3-trigger", + }, + ]; + const testExtTriggers: functionsEmulatorShared.ParsedTriggerDefinition[] = [ + { + entryPoint: "ext", + platform: "gcfv1", + name: "ext-trigger", + }, + ]; + const testSpec: ExtensionSpec = { + name: "my-extension", + version: "0.1.0", + resources: [], + sourceUrl: "test.com", + params: [], + postinstallContent: "Should subsitute ${param:KEY}", + }; + const testSubbedSpec: ExtensionSpec = { + name: "my-extension", + version: "0.1.0", + resources: [], + sourceUrl: "test.com", + params: [], + postinstallContent: "Should subsitute value", + }; + const testExtension: Extension = { + name: "my-extension", + ref: "pubby/my-extensions", + createTime: "", + visibility: Visibility.PUBLIC, + registryLaunchStage: RegistryLaunchStage.BETA, + }; + const testExtensionVersion = (spec: ExtensionSpec): ExtensionVersion => { + return { + name: "my-extension", + ref: "pubby/my-extensions@0.1.0", + state: "PUBLISHED", + spec, + hash: "abc123", + sourceDownloadUri: "test.com", + }; + }; + + const tests: { + desc: string; + in: EmulatableBackend; + expected: BackendInfo; + }[] = [ + { + desc: "should transform a published Extension backend", + in: { + functionsDir: "test", + env: { + KEY: "value", + }, + secretEnv: [], + predefinedTriggers: testExtTriggers, + extension: testExtension, + extensionVersion: testExtensionVersion(testSpec), + extensionInstanceId: "my-instance", + }, + expected: { + directory: "test", + env: { + KEY: "value", + }, + functionTriggers: testExtTriggers, + extension: testExtension, + extensionVersion: testExtensionVersion(testSubbedSpec), + extensionInstanceId: "my-instance", + }, + }, + { + desc: "should transform a local Extension backend", + in: { + functionsDir: "test", + env: { + KEY: "value", + }, + secretEnv: [], + predefinedTriggers: testExtTriggers, + extensionSpec: testSpec, + extensionInstanceId: "my-local-instance", + }, + expected: { + directory: "test", + env: { + KEY: "value", + }, + functionTriggers: testExtTriggers, + extensionSpec: testSubbedSpec, + extensionInstanceId: "my-local-instance", + }, + }, + { + desc: "should transform a CF3 backend", + in: { + functionsDir: "test", + env: { + KEY: "value", + }, + secretEnv: [], + }, + expected: { + directory: "test", + env: { + KEY: "value", + }, + functionTriggers: testCF3Triggers, + }, + }, + { + desc: "should add secretEnvVar into env", + in: { + functionsDir: "test", + env: { + KEY: "value", + }, + secretEnv: [ + { + key: "secret", + secret: "asecret", + projectId: "test", + }, + ], + }, + expected: { + directory: "test", + env: { + KEY: "value", + secret: "projects/test/secrets/asecret/versions/latest", + }, + functionTriggers: testCF3Triggers, + }, + }, + ]; + + for (const tc of tests) { + it(tc.desc, () => { + expect(functionsEmulatorShared.toBackendInfo(tc.in, testCF3Triggers)).to.deep.equal( + tc.expected + ); + }); + } + }); }); From 60795bd473ea6d7e1ac0c0526f72e45b6eb585bc Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Tue, 22 Mar 2022 11:48:13 -0700 Subject: [PATCH 0182/1699] Adds required IAM bindings for EventArc events (#4324) * adding required bindings for pubsub & compute service agents * adding tests * adding eventarc service agent bindings * adding noop for services, check for existing v2 functions, and more tests * addressing comments --- src/deploy/functions/checkIam.ts | 91 ++++- .../functions/services/firebaseAlerts.ts | 30 -- src/deploy/functions/services/index.ts | 8 +- src/test/deploy/functions/checkIam.spec.ts | 352 +++++++++++++++++- .../functions/services/firebaseAlerts.spec.ts | 64 ---- 5 files changed, 447 insertions(+), 98 deletions(-) diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index c423a999477..c02a904883c 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -14,6 +14,10 @@ import { getIamPolicy, setIamPolicy } from "../../gcp/resourceManager"; import { Service, serviceForEndpoint } from "./services"; const PERMISSION = "cloudfunctions.functions.setIamPolicy"; +export const SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator"; +export const RUN_INVOKER_ROLE = "roles/run.invoker"; +export const EVENTARC_EVENT_RECEIVER_ROLE = "roles/eventarc.eventReceiver"; +export const EVENTARC_SERVICE_AGENT_ROLE = "roles/eventarc.serviceAgent"; /** * Checks to see if the authenticated account has `iam.serviceAccounts.actAs` permissions @@ -118,6 +122,83 @@ function reduceEventsToServices(services: Array, endpoint: backend.Endp return services; } +/** + * Returns the IAM bindings that grants the role to the service account + * @param existingPolicy the project level IAM policy + * @param serviceAccount the IAM service account + * @param role the role you want to grant + * @returns + */ +export function obtainBinding( + existingPolicy: iam.Policy, + serviceAccount: string, + role: string +): iam.Binding { + let binding = existingPolicy.bindings.find((b) => b.role === role); + if (!binding) { + binding = { + role, + members: [], + }; + } + if (!binding.members.find((m) => m === serviceAccount)) { + binding.members.push(serviceAccount); + } + return binding; +} + +/** + * Finds the required project level IAM bindings for the Pub/Sub service agent. + * If the user enabled Pub/Sub on or before April 8, 2021, then we must enable the token creator role. + * @param projectNumber project number + * @param existingPolicy the project level IAM policy + */ +export function obtainPubSubServiceAgentBindings( + projectNumber: string, + existingPolicy: iam.Policy +): iam.Binding[] { + const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`; + return [obtainBinding(existingPolicy, pubsubServiceAgent, SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE)]; +} + +/** + * Finds the required project level IAM bindings for the default compute service agent. + * Before a user creates an EventArc trigger, this agent must be granted the invoker and event receiver roles. + * @param projectNumber project number + * @param existingPolicy the project level IAM policy + */ +export function obtainDefaultComputeServiceAgentBindings( + projectNumber: string, + existingPolicy: iam.Policy +): iam.Binding[] { + const defaultComputeServiceAgent = `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`; + const invokerBinding = obtainBinding( + existingPolicy, + defaultComputeServiceAgent, + RUN_INVOKER_ROLE + ); + const eventReceiverBinding = obtainBinding( + existingPolicy, + defaultComputeServiceAgent, + EVENTARC_EVENT_RECEIVER_ROLE + ); + return [invokerBinding, eventReceiverBinding]; +} + +/** + * Finds the required project level IAM bindings for the eventarc service agent. + * If a user enables eventarc for the first time, this grant can take a while to propagate and deployment will fail. + * @param projectNumber project number + * @param existingPolicy the project level IAM policy + */ +export function obtainEventarcServiceAgentBindings( + projectNumber: string, + existingPolicy: iam.Policy +): iam.Binding[] { + const eventarcServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`; + return [obtainBinding(existingPolicy, eventarcServiceAgent, EVENTARC_SERVICE_AGENT_ROLE)]; +} + /** Helper to merge all required bindings into the IAM policy */ export function mergeBindings(policy: iam.Policy, allRequiredBindings: iam.Binding[][]) { for (const requiredBindings of allRequiredBindings) { @@ -143,7 +224,7 @@ export function mergeBindings(policy: iam.Policy, allRequiredBindings: iam.Bindi /** * Checks and sets the roles for specific resource service agents - * @param projectId project identifier + * @param projectNumber project number * @param want backend that we want to deploy * @param have backend that we have currently deployed */ @@ -181,6 +262,14 @@ export async function ensureServiceAgentRoles( findRequiredBindings.push(service.requiredProjectBindings!(projectNumber, policy)) ); const allRequiredBindings = await Promise.all(findRequiredBindings); + if (haveServices.length === 0) { + allRequiredBindings.push(obtainPubSubServiceAgentBindings(projectNumber, policy)); + allRequiredBindings.push(obtainDefaultComputeServiceAgentBindings(projectNumber, policy)); + allRequiredBindings.push(obtainEventarcServiceAgentBindings(projectNumber, policy)); + } + if (!allRequiredBindings.find((bindings) => bindings.length > 0)) { + return; + } mergeBindings(policy, allRequiredBindings); // set the updated policy try { diff --git a/src/deploy/functions/services/firebaseAlerts.ts b/src/deploy/functions/services/firebaseAlerts.ts index ed6c2f53370..e84787050a2 100644 --- a/src/deploy/functions/services/firebaseAlerts.ts +++ b/src/deploy/functions/services/firebaseAlerts.ts @@ -1,36 +1,6 @@ import * as backend from "../backend"; -import * as iam from "../../../gcp/iam"; -import { getProjectNumber } from "../../../getProjectNumber"; import { FirebaseError } from "../../../error"; -export const SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator"; - -/** - * Finds the required project level IAM bindings for the Pub/Sub service agent - * If the user enabled Pub/Sub on or before April 8, 2021, then we must enable the token creator role - * @param projectId project identifier - * @param existingPolicy the project level IAM policy - */ -export function obtainFirebaseAlertsBindings( - projectNumber: string, - existingPolicy: iam.Policy -): Promise> { - const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`; - let pubsubBinding = existingPolicy.bindings.find( - (b) => b.role === SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE - ); - if (!pubsubBinding) { - pubsubBinding = { - role: SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: [], - }; - } - if (!pubsubBinding.members.find((m) => m === pubsubServiceAgent)) { - pubsubBinding.members.push(pubsubServiceAgent); - } - return Promise.resolve([pubsubBinding]); -} - /** * Sets a Firebase Alerts event trigger's region to 'global' since the service is global * @param endpoint the storage endpoint diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 9e51f9de0e5..7fe35c95619 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -2,10 +2,14 @@ import * as backend from "../backend"; import * as iam from "../../../gcp/iam"; import * as v2events from "../../../functions/events/v2"; import { obtainStorageBindings, ensureStorageTriggerRegion } from "./storage"; -import { obtainFirebaseAlertsBindings, ensureFirebaseAlertsTriggerRegion } from "./firebaseAlerts"; +import { ensureFirebaseAlertsTriggerRegion } from "./firebaseAlerts"; +/** A standard void No Op */ const noop = (): Promise => Promise.resolve(); +/** A No Op that's useful for Services that don't have specific bindings but should still try to set default bindings */ +const noopProjectBindings = (): Promise> => Promise.resolve([]); + /** A service interface for the underlying GCP event services */ export interface Service { readonly name: string; @@ -43,7 +47,7 @@ export const StorageService: Service = { export const FirebaseAlertsService: Service = { name: "firebasealerts", api: "logging.googleapis.com", - requiredProjectBindings: obtainFirebaseAlertsBindings, + requiredProjectBindings: noopProjectBindings, ensureTriggerRegion: ensureFirebaseAlertsTriggerRegion, }; diff --git a/src/test/deploy/functions/checkIam.spec.ts b/src/test/deploy/functions/checkIam.spec.ts index c7f1de3baea..c81b74bbee8 100644 --- a/src/test/deploy/functions/checkIam.spec.ts +++ b/src/test/deploy/functions/checkIam.spec.ts @@ -23,6 +23,12 @@ const SPEC = { runtime: "nodejs14", }; +const iamPolicy = { + etag: "etag", + version: 3, + bindings: [BINDING], +}; + describe("checkIam", () => { let storageStub: sinon.SinonStub; let getIamStub: sinon.SinonStub; @@ -44,6 +50,161 @@ describe("checkIam", () => { sinon.verifyAndRestore(); }); + const iamPolicy = { + etag: "etag", + version: 3, + bindings: [ + { + role: "some/role", + members: ["someuser"], + }, + ], + }; + + describe("obtainBinding", () => { + it("should assign the service account the role if they don't exist in the policy", () => { + const policy = { ...iamPolicy }; + const serviceAccount = "myServiceAccount"; + const role = "role/myrole"; + + const bindings = checkIam.obtainBinding(policy, serviceAccount, role); + + expect(bindings).to.deep.equal({ + role, + members: [serviceAccount], + }); + }); + + it("should append the service account as a member of the role when the role exists in the policy", () => { + const policy = { ...iamPolicy }; + const serviceAccount = "myServiceAccount"; + const role = "role/myrole"; + policy.bindings = [ + { + role, + members: ["someuser"], + }, + ]; + + const bindings = checkIam.obtainBinding(policy, serviceAccount, role); + + expect(bindings).to.deep.equal({ + role, + members: ["someuser", serviceAccount], + }); + }); + + it("should not add the role or service account if the policy already has the binding", () => { + const policy = { ...iamPolicy }; + const serviceAccount = "myServiceAccount"; + const role = "role/myrole"; + policy.bindings = [ + { + role, + members: [serviceAccount], + }, + ]; + + const bindings = checkIam.obtainBinding(policy, serviceAccount, role); + + expect(bindings).to.deep.equal({ + role, + members: [serviceAccount], + }); + }); + }); + + describe("obtainPubSubServiceAgentBindings", () => { + it("should add the binding", () => { + const policy = { ...iamPolicy }; + + const bindings = checkIam.obtainPubSubServiceAgentBindings(projectNumber, policy); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [`serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`], + }); + }); + + it("should add the service agent as a member", () => { + const policy = { ...iamPolicy }; + policy.bindings = [ + { + role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: ["someuser"], + }, + ]; + + const bindings = checkIam.obtainPubSubServiceAgentBindings(projectNumber, policy); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [ + "someuser", + `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`, + ], + }); + }); + + it("should do nothing if we have the binding", () => { + const policy = { ...iamPolicy }; + policy.bindings = [ + { + role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`, + ], + }, + ]; + + const bindings = checkIam.obtainPubSubServiceAgentBindings(projectNumber, policy); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [`serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`], + }); + }); + }); + + describe("obtainDefaultComputeServiceAgentBindings", () => { + it("should add both bindings", () => { + const policy = { ...iamPolicy }; + + const bindings = checkIam.obtainDefaultComputeServiceAgentBindings(projectNumber, policy); + + expect(bindings.length).to.equal(2); + expect(bindings).to.include.deep.members([ + { + role: checkIam.RUN_INVOKER_ROLE, + members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`], + }, + { + role: checkIam.EVENTARC_EVENT_RECEIVER_ROLE, + members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`], + }, + ]); + }); + }); + + describe("obtainEventarcServiceAgentBindings", () => { + it("should add the binding", () => { + const policy = { ...iamPolicy }; + + const bindings = checkIam.obtainEventarcServiceAgentBindings(projectNumber, policy); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: checkIam.EVENTARC_SERVICE_AGENT_ROLE, + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`, + ], + }); + }); + }); + describe("mergeBindings", () => { it("should skip empty or duplicate bindings", () => { const policy = { @@ -209,7 +370,7 @@ describe("checkIam", () => { expect(setIamStub).to.not.have.been.called; }); - it("should add the binding with the service agent", async () => { + it("should add the pubsub publisher role and all default bindings for a new v2 storage function without v2 deployed functions", async () => { const newIamPolicy = { etag: "etag", version: 3, @@ -219,6 +380,26 @@ describe("checkIam", () => { role: "roles/pubsub.publisher", members: [`serviceAccount:${STORAGE_RES.email_address}`], }, + { + role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`, + ], + }, + { + role: checkIam.RUN_INVOKER_ROLE, + members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`], + }, + { + role: checkIam.EVENTARC_EVENT_RECEIVER_ROLE, + members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`], + }, + { + role: checkIam.EVENTARC_SERVICE_AGENT_ROLE, + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`, + ], + }, ], }; storageStub.resolves(STORAGE_RES); @@ -253,4 +434,173 @@ describe("checkIam", () => { expect(setIamStub).to.have.been.calledWith(projectNumber, newIamPolicy, "bindings"); }); }); + + it("should add the pubsub publisher role for a new v2 storage function with v2 deployed functions", async () => { + const newIamPolicy = { + etag: "etag", + version: 3, + bindings: [ + BINDING, + { + role: "roles/pubsub.publisher", + members: [`serviceAccount:${STORAGE_RES.email_address}`], + }, + ], + }; + storageStub.resolves(STORAGE_RES); + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [BINDING], + }); + setIamStub.resolves(newIamPolicy); + const wantFn: backend.Endpoint = { + id: "wantFn", + entryPoint: "wantFn", + platform: "gcfv2", + eventTrigger: { + eventType: "google.cloud.storage.object.v1.finalized", + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], + retry: false, + }, + ...SPEC, + }; + const haveFn: backend.Endpoint = { + id: "haveFn", + entryPoint: "haveFn", + platform: "gcfv2", + eventTrigger: { + eventType: "google.firebase.firebasealerts.alerts.v1.published", + eventFilters: [ + { + attribute: "alerttype", + value: "crashlytics.newFatalIssue", + }, + ], + retry: false, + }, + ...SPEC, + }; + + await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.of(haveFn)); + + expect(storageStub).to.have.been.calledOnce; + expect(getIamStub).to.have.been.calledOnce; + expect(setIamStub).to.have.been.calledOnce; + expect(setIamStub).to.have.been.calledWith(projectNumber, newIamPolicy, "bindings"); + }); + + it("should add the default bindings for a new v2 alerts function without v2 deployed functions", async () => { + const newIamPolicy = { + etag: "etag", + version: 3, + bindings: [ + BINDING, + { + role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`, + ], + }, + { + role: checkIam.RUN_INVOKER_ROLE, + members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`], + }, + { + role: checkIam.EVENTARC_EVENT_RECEIVER_ROLE, + members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`], + }, + { + role: checkIam.EVENTARC_SERVICE_AGENT_ROLE, + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`, + ], + }, + ], + }; + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [BINDING], + }); + setIamStub.resolves(newIamPolicy); + const wantFn: backend.Endpoint = { + id: "wantFn", + entryPoint: "wantFn", + platform: "gcfv2", + eventTrigger: { + eventType: "google.firebase.firebasealerts.alerts.v1.published", + eventFilters: [ + { + attribute: "alerttype", + value: "crashlytics.newFatalIssue", + }, + ], + retry: false, + }, + ...SPEC, + }; + + await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.empty()); + + expect(getIamStub).to.have.been.calledOnce; + expect(setIamStub).to.have.been.calledOnce; + expect(setIamStub).to.have.been.calledWith(projectNumber, newIamPolicy, "bindings"); + }); + + it("should not add bindings for a new v2 alerts function with v2 deployed functions", async () => { + const newIamPolicy = { + etag: "etag", + version: 3, + bindings: [BINDING], + }; + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [BINDING], + }); + setIamStub.resolves(newIamPolicy); + const wantFn: backend.Endpoint = { + id: "wantFn", + entryPoint: "wantFn", + platform: "gcfv2", + eventTrigger: { + eventType: "google.firebase.firebasealerts.alerts.v1.published", + eventFilters: [ + { + attribute: "alerttype", + value: "crashlytics.newFatalIssue", + }, + ], + retry: false, + }, + ...SPEC, + }; + const haveFn: backend.Endpoint = { + id: "haveFn", + entryPoint: "haveFn", + platform: "gcfv2", + eventTrigger: { + eventType: "google.cloud.storage.object.v1.finalized", + eventFilters: [ + { + attribute: "bucket", + value: "my-bucket", + }, + ], + retry: false, + }, + ...SPEC, + }; + + await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.of(haveFn)); + + expect(getIamStub).to.have.been.calledOnce; + expect(setIamStub).to.not.have.been.called; + }); }); diff --git a/src/test/deploy/functions/services/firebaseAlerts.spec.ts b/src/test/deploy/functions/services/firebaseAlerts.spec.ts index 105c889d304..7178c64ae67 100644 --- a/src/test/deploy/functions/services/firebaseAlerts.spec.ts +++ b/src/test/deploy/functions/services/firebaseAlerts.spec.ts @@ -18,70 +18,6 @@ const endpoint: Endpoint = { runtime: "nodejs16", }; -describe("obtainFirebaseAlertsBindings", () => { - const iamPolicy = { - etag: "etag", - version: 3, - bindings: [ - { - role: "some/role", - members: ["someuser"], - }, - ], - }; - - it("should add the binding", async () => { - const policy = { ...iamPolicy }; - - const bindings = await firebaseAlerts.obtainFirebaseAlertsBindings(projectNumber, policy); - - expect(bindings.length).to.equal(1); - expect(bindings[0]).to.deep.equal({ - role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: ["serviceAccount:service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com"], - }); - }); - - it("should add the service agent as a member", async () => { - const policy = { ...iamPolicy }; - policy.bindings = [ - { - role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: ["someuser"], - }, - ]; - - const bindings = await firebaseAlerts.obtainFirebaseAlertsBindings(projectNumber, policy); - - expect(bindings.length).to.equal(1); - expect(bindings[0]).to.deep.equal({ - role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: [ - "someuser", - "serviceAccount:service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com", - ], - }); - }); - - it("should do nothing if we have the binding", async () => { - const policy = { ...iamPolicy }; - policy.bindings = [ - { - role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: ["serviceAccount:service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com"], - }, - ]; - - const bindings = await firebaseAlerts.obtainFirebaseAlertsBindings(projectNumber, policy); - - expect(bindings.length).to.equal(1); - expect(bindings[0]).to.deep.equal({ - role: firebaseAlerts.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: ["serviceAccount:service-123456789@gcp-sa-pubsub.iam.gserviceaccount.com"], - }); - }); -}); - describe("ensureFirebaseAlertsTriggerRegion", () => { it("should set the trigger location to global", async () => { const ep = { ...endpoint }; From 76e4c2417913bbafe727827de49840aaaa4e4c56 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Tue, 22 Mar 2022 17:24:50 -0400 Subject: [PATCH 0183/1699] Allow ext:* --local commands to run without a project ID (#4345) * no longer required project ID for --local * fix pipe that needs project ID * format * pr fixes Co-authored-by: joehan --- src/commands/ext-configure.ts | 13 +++++--- src/commands/ext-install.ts | 15 +++++---- src/commands/ext-update.ts | 8 ++--- src/extensions/askUserForParam.ts | 37 +++++++++++---------- src/extensions/extensionsHelper.ts | 17 +++++++--- src/extensions/paramHelper.ts | 21 +++++------- src/requirePermissions.ts | 7 ++-- src/test/extensions/askUserForParam.spec.ts | 8 ++++- 8 files changed, 74 insertions(+), 52 deletions(-) diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index ebd3d5f0312..3fdca966cfb 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -8,7 +8,7 @@ import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; import { FirebaseError } from "../error"; -import { needProjectId } from "../projectUtils"; +import { needProjectId, getProjectId } from "../projectUtils"; import * as extensionsApi from "../extensions/extensionsApi"; import { logPrefix, diagnoseAndFixProject } from "../extensions/extensionsHelper"; import * as paramHelper from "../extensions/paramHelper"; @@ -40,7 +40,7 @@ export default new Command("ext:configure ") .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) .action(async (instanceId: string, options: Options) => { - const projectId = needProjectId(options); + const projectId = getProjectId(options); if (options.local) { if (options.nonInteractive) { @@ -109,7 +109,10 @@ export default new Command("ext:configure ") try { let existingInstance: extensionsApi.ExtensionInstance; try { - existingInstance = await extensionsApi.getInstance(projectId, instanceId); + existingInstance = await extensionsApi.getInstance( + needProjectId({ projectId }), + instanceId + ); } catch (err: any) { if (err.status === 404) { return utils.reject( @@ -152,7 +155,7 @@ export default new Command("ext:configure ") spinner.start(); const res = await extensionsApi.configureInstance({ - projectId, + projectId: needProjectId({ projectId }), instanceId, params: paramBindings, }); @@ -162,7 +165,7 @@ export default new Command("ext:configure ") logPrefix, marked( `You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl( - projectId, + needProjectId({ projectId }), `/extensions/instances/${instanceId}?tab=config` )}` ) diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index b8247a808ab..6b89a5c7a9a 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -12,7 +12,7 @@ import { checkBillingEnabled } from "../gcp/cloudbilling"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; import { FirebaseError } from "../error"; -import { needProjectId } from "../projectUtils"; +import { getProjectId, needProjectId } from "../projectUtils"; import * as extensionsApi from "../extensions/extensionsApi"; import * as secretsUtils from "../extensions/secretsUtils"; import * as provisioningHelper from "../extensions/provisioningHelper"; @@ -67,7 +67,7 @@ export default new Command("ext:install [extensionName]") .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) .action(async (extensionName: string, options: Options) => { - const projectId = needProjectId(options); + const projectId = getProjectId(options); const paramsEnvPath = (options.params ?? "") as string; let learnMore = false; if (!extensionName) { @@ -103,7 +103,7 @@ export default new Command("ext:install [extensionName]") "Installing a local source locally is not supported yet, please use ext:dev:emulator commands" ); } - source = await infoInstallBySource(projectId, extensionName); + source = await infoInstallBySource(needProjectId({ projectId }), extensionName); } else { void track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); extVersion = await infoInstallByReference(extensionName, options.interactive); @@ -167,7 +167,7 @@ export default new Command("ext:install [extensionName]") try { return installExtension({ paramsEnvPath, - projectId, + projectId: projectId, extensionName, source, extVersion, @@ -228,7 +228,7 @@ async function infoInstallByReference( interface InstallExtensionOptions { paramsEnvPath?: string; - projectId: string; + projectId?: string; extensionName: string; source?: extensionsApi.ExtensionSource; extVersion?: extensionsApi.ExtensionVersion; @@ -293,8 +293,9 @@ async function installToManifest(options: InstallExtensionOptions): Promise { - const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } = - options; + const { extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } = options; + const projectId = needProjectId({ projectId: options.projectId }); + const spec = source?.spec || extVersion?.spec; if (!spec) { throw new FirebaseError( diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 2bb1d0b536f..2bb0cafeb59 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -35,13 +35,12 @@ import { inferUpdateSource, } from "../extensions/updateHelper"; import * as refs from "../extensions/refs"; -import { needProjectId } from "../projectUtils"; +import { getProjectId, needProjectId } from "../projectUtils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; import { previews } from "../previews"; import * as manifest from "../extensions/manifest"; import { Options } from "../options"; -import { logger } from ".."; marked.setOptions({ renderer: new TerminalRenderer(), @@ -70,9 +69,9 @@ export default new Command("ext:update [updateSource]") "save the update to firebase.json rather than directly update an existing Extension instance on a Firebase project" ) .action(async (instanceId: string, updateSource: string, options: Options) => { - const projectId = needProjectId(options); - if (options.local) { + const projectId = getProjectId(options); + const config = manifest.loadConfig(options); const oldRef = manifest.getInstanceRef(instanceId, config); const oldExtensionVersion = await extensionsApi.getExtensionVersion( @@ -156,6 +155,7 @@ export default new Command("ext:update [updateSource]") const spinner = ora(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`); try { + const projectId = needProjectId(options); let existingInstance: extensionsApi.ExtensionInstance; try { existingInstance = await extensionsApi.getInstance(projectId, instanceId); diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index fa0eb9c440c..e186d192fe5 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -6,12 +6,13 @@ const { marked } = require("marked"); import { Param, ParamOption, ParamType } from "./extensionsApi"; import * as secretManagerApi from "../gcp/secretManager"; import * as secretsUtils from "./secretsUtils"; -import { confirm, logPrefix, substituteParams } from "./extensionsHelper"; +import { logPrefix, substituteParams } from "./extensionsHelper"; import { convertExtensionOptionToLabeledList, getRandomString, onceWithJoin } from "./utils"; import { logger } from "../logger"; import { promptOnce } from "../prompt"; import * as utils from "../utils"; import { ParamBindingOptions } from "./paramHelper"; +import { needProjectId } from "../projectUtils"; /** * Location where the secret value is stored. @@ -78,28 +79,28 @@ export function checkResponse(response: string, spec: Param): boolean { * @param firebaseProjectParams Autopopulated Firebase project-specific params * @return Promisified map of env vars to values. */ -export async function ask( - projectId: string, - instanceId: string, - paramSpecs: Param[], - firebaseProjectParams: { [key: string]: string }, - reconfiguring: boolean -): Promise<{ [key: string]: ParamBindingOptions }> { - if (_.isEmpty(paramSpecs)) { +export async function ask(args: { + projectId: string | undefined; + instanceId: string; + paramSpecs: Param[]; + firebaseProjectParams: { [key: string]: string }; + reconfiguring: boolean; +}): Promise<{ [key: string]: ParamBindingOptions }> { + if (_.isEmpty(args.paramSpecs)) { logger.debug("No params were specified for this extension."); return {}; } utils.logLabeledBullet(logPrefix, "answer the questions below to configure your extension:"); - const substituted = substituteParams(paramSpecs, firebaseProjectParams); + const substituted = substituteParams(args.paramSpecs, args.firebaseProjectParams); const result: { [key: string]: ParamBindingOptions } = {}; const promises = _.map(substituted, (paramSpec: Param) => { return async () => { result[paramSpec.param] = await askForParam({ - projectId, - instanceId, - paramSpec, - reconfiguring, + projectId: args.projectId, + instanceId: args.instanceId, + paramSpec: paramSpec, + reconfiguring: args.reconfiguring, }); }; }); @@ -110,7 +111,7 @@ export async function ask( } export async function askForParam(args: { - projectId: string; + projectId?: string; instanceId: string; paramSpec: Param; reconfiguring: boolean; @@ -173,9 +174,11 @@ export async function askForParam(args: { } while (!isValidSecretLocations(secretLocations, paramSpec)); if (secretLocations.includes(SecretLocation.CLOUD.toString())) { + // TODO(lihes): evaluate the UX of this error message. + const projectId = needProjectId({ projectId: args.projectId }); response = args.reconfiguring - ? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec) - : await promptCreateSecret(args.projectId, args.instanceId, paramSpec); + ? await promptReconfigureSecret(projectId, args.instanceId, paramSpec) + : await promptCreateSecret(projectId, args.instanceId, paramSpec); } if (secretLocations.includes(SecretLocation.LOCAL.toString())) { responseForLocal = await promptLocalSecret(args.instanceId, paramSpec); diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index facadca36b5..cabfe0428c8 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -21,7 +21,7 @@ import { diagnose } from "./diagnose"; import { checkResponse } from "./askUserForParam"; import { ensure } from "../ensureApiEnabled"; import { deleteObject, uploadObject } from "../gcp/storage"; -import { needProjectId } from "../projectUtils"; +import { getProjectId } from "../projectUtils"; import { createSource, ExtensionSource, @@ -108,9 +108,12 @@ export function getDBInstanceFromURL(databaseUrl = ""): string { * Gets Firebase project specific param values. */ export async function getFirebaseProjectParams( - projectId: string, + projectId: string | undefined, emulatorMode: boolean = false ): Promise> { + if (!projectId) { + return {}; + } const body = emulatorMode ? await getProjectAdminSdkConfigOrCached(projectId) : await getFirebaseConfig({ project: projectId }); @@ -366,7 +369,10 @@ export async function promptForValidInstanceId(instanceId: string): Promise { - const projectId = needProjectId(options); + const projectId = getProjectId(options); + if (!projectId) { + return; + } return await ensure( projectId, "firebaseextensions.googleapis.com", @@ -759,7 +765,10 @@ export async function confirm(args: { } export async function diagnoseAndFixProject(options: any): Promise { - const projectId = needProjectId(options); + const projectId = getProjectId(options); + if (!projectId) { + return; + } const ok = await diagnose(projectId); if (!ok) { throw new FirebaseError("Unable to proceed until all issues are resolved."); diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 9297c85feea..a2c46ed374c 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -95,7 +95,7 @@ export function getParamsWithCurrentValuesAsDefaults( * @throws FirebaseError if an invalid env file is passed in */ export async function getParams(args: { - projectId: string; + projectId?: string; instanceId: string; paramSpecs: extensionsApi.Param[]; nonInteractive?: boolean; @@ -117,19 +117,18 @@ export async function getParams(args: { ); } else if (args.paramsEnvPath) { params = getParamsFromFile({ - projectId: args.projectId, paramSpecs: args.paramSpecs, paramsEnvPath: args.paramsEnvPath, }); } else { const firebaseProjectParams = await getFirebaseProjectParams(args.projectId); - params = await askUserForParam.ask( - args.projectId, - args.instanceId, - args.paramSpecs, + params = await askUserForParam.ask({ + projectId: args.projectId, + instanceId: args.instanceId, + paramSpecs: args.paramSpecs, firebaseProjectParams, - !!args.reconfiguring - ); + reconfiguring: !!args.reconfiguring, + }); } void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params)); return params; @@ -139,7 +138,7 @@ export async function getParamsForUpdate(args: { spec: extensionsApi.ExtensionSpec; newSpec: extensionsApi.ExtensionSpec; currentParams: { [option: string]: string }; - projectId: string; + projectId?: string; paramsEnvPath?: string; nonInteractive?: boolean; instanceId: string; @@ -159,7 +158,6 @@ export async function getParamsForUpdate(args: { ); } else if (args.paramsEnvPath) { params = getParamsFromFile({ - projectId: args.projectId, paramSpecs: args.newSpec.params, paramsEnvPath: args.paramsEnvPath, }); @@ -188,7 +186,7 @@ export async function promptForNewParams(args: { spec: extensionsApi.ExtensionSpec; newSpec: extensionsApi.ExtensionSpec; currentParams: { [option: string]: string }; - projectId: string; + projectId?: string; instanceId: string; }): Promise<{ [option: string]: ParamBindingOptions }> { const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams); @@ -239,7 +237,6 @@ export async function promptForNewParams(args: { } function getParamsFromFile(args: { - projectId: string; paramSpecs: extensionsApi.Param[]; paramsEnvPath: string; }): Record { diff --git a/src/requirePermissions.ts b/src/requirePermissions.ts index 8c52c440ed4..1e17fec506b 100644 --- a/src/requirePermissions.ts +++ b/src/requirePermissions.ts @@ -1,5 +1,5 @@ import { bold } from "cli-color"; -import { needProjectId } from "./projectUtils"; +import { getProjectId } from "./projectUtils"; import { requireAuth } from "./requireAuth"; import { logger } from "./logger"; import { FirebaseError } from "./error"; @@ -16,7 +16,10 @@ const BASE_PERMISSIONS = ["firebase.projects.get"]; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export async function requirePermissions(options: any, permissions: string[] = []): Promise { - const projectId = needProjectId(options); + const projectId = getProjectId(options); + if (!projectId) { + return; + } const requiredPermissions = BASE_PERMISSIONS.concat(permissions).sort(); await requireAuth(options); diff --git a/src/test/extensions/askUserForParam.spec.ts b/src/test/extensions/askUserForParam.spec.ts index fadf91ad41d..b2078a46a1c 100644 --- a/src/test/extensions/askUserForParam.spec.ts +++ b/src/test/extensions/askUserForParam.spec.ts @@ -349,7 +349,13 @@ describe("askUserForParam", () => { it("should call substituteParams with the right parameters", async () => { const spec = [testSpec]; const firebaseProjectVars = { PROJECT_ID: "my-project" }; - await ask("project-id", "instance-id", spec, firebaseProjectVars, false); + await ask({ + projectId: "project-id", + instanceId: "instance-id", + paramSpecs: spec, + firebaseProjectParams: firebaseProjectVars, + reconfiguring: false, + }); expect(subVarSpy.calledWith(spec, firebaseProjectVars)).to.be.true; }); }); From 311d1c8e85574aab14988ea46a3a6274e3448aa8 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 22 Mar 2022 15:25:39 -0700 Subject: [PATCH 0184/1699] Only validate function name (#4352) * Only validate function name * Add changelog entry * Update CHANGELOG.md Co-authored-by: Daniel Lee * PR fixes Co-authored-by: Daniel Lee --- CHANGELOG.md | 1 + src/emulator/functionsEmulator.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..ae4aed4f6f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes an issue where some valid Cloud Functions for Firebase names would not pass validation in the Functions emulator (#4352). diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 542ca76b3af..1b06cfbffde 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -551,7 +551,9 @@ export class FunctionsEmulator implements EmulatorInstance { for (const definition of toSetup) { // Skip function with invalid id. try { - functionIdsAreValid([definition]); + // Note - in the emulator, functionId = {region}-{functionName}, but in prod, functionId=functionName. + // To match prod behavior, only validate functionName + functionIdsAreValid([{ ...definition, id: definition.name }]); } catch (e: any) { this.logger.logLabeled( "WARN", From 06090456c7696d783e6c97e4bb0b190d2189b11a Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 22 Mar 2022 22:43:46 +0000 Subject: [PATCH 0185/1699] 10.4.2 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2fcb916aea5..83599146185 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.4.1", + "version": "10.4.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.4.1", + "version": "10.4.2", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 4541d902db9..71514e3bf12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.4.1", + "version": "10.4.2", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 93f8a6f6482039be62d506e616413d5861782b51 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 22 Mar 2022 22:44:11 +0000 Subject: [PATCH 0186/1699] [firebase-release] Removed change log and reset repo after 10.4.2 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae4aed4f6f6..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes an issue where some valid Cloud Functions for Firebase names would not pass validation in the Functions emulator (#4352). From fac3f61e7ca485111880fcba5cabe14a036d7bd5 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Wed, 23 Mar 2022 10:22:36 -0400 Subject: [PATCH 0187/1699] Remove skipAuth param from StorageLayer methods (#4277) * multipart and firebase.ts integration * update gcloud.ts * refactor persistence out into StorageEmulator * fix lint and method references * fix tests * upload.ts * update callers of UploadService and tests * more tests * more tests * md -> metadata * address pr comments * lint * fix tests * fix tests * stash * more integration tests, fix upload bug * more fixing * fix remaining * lint * metadata * deleteobject * list * get * fix tests * lint * address pr comments * lint * restructure * cleanup * fix merge * remove skipAuth param from StorageLayer * rename to adminStorageLayer to make StorageLayer type explicit * fix imports * change to use passthrough logic * share state across storagelayers * fix tests * fix import * address pr comments * lint * revert * pr comments * more cleanup * fix tests * remove only * move internal tests to new file * internals test file * remove skipauth param --- .../internals.test.ts | 154 ++++++++++++++++++ scripts/storage-emulator-integration/run.sh | 2 + .../storage.rules | 2 +- scripts/storage-emulator-integration/tests.ts | 53 +++--- src/emulator/shared/request.ts | 18 ++ src/emulator/storage/apis/firebase.ts | 17 +- src/emulator/storage/apis/gcloud.ts | 109 +++++-------- src/emulator/storage/files.ts | 114 +++++-------- src/emulator/storage/index.ts | 48 ++++-- src/emulator/storage/rules/utils.ts | 32 +++- src/test/emulators/storage/files.spec.ts | 9 +- 11 files changed, 356 insertions(+), 202 deletions(-) create mode 100644 scripts/storage-emulator-integration/internals.test.ts create mode 100644 src/emulator/shared/request.ts diff --git a/scripts/storage-emulator-integration/internals.test.ts b/scripts/storage-emulator-integration/internals.test.ts new file mode 100644 index 00000000000..e1507692415 --- /dev/null +++ b/scripts/storage-emulator-integration/internals.test.ts @@ -0,0 +1,154 @@ +import * as puppeteer from "puppeteer"; +import { expect } from "chai"; +import * as admin from "firebase-admin"; +import { Bucket } from "@google-cloud/storage"; +import * as http from "http"; +import * as firebase from "firebase"; +import { TriggerEndToEndTest } from "../integration-helpers/framework"; +import * as fs from "fs"; +import * as path from "path"; +import { + createRandomFile, + EMULATORS_SHUTDOWN_DELAY_MS, + getAuthEmulatorHost, + getStorageEmulatorHost, + readEmulatorConfig, + readJson, + resetStorageEmulator, + SERVICE_ACCOUNT_KEY, + SMALL_FILE_SIZE, + TEST_SETUP_TIMEOUT, + uploadText, +} from "./utils"; + +const FIREBASE_PROJECT = process.env.FBTOOLS_TARGET_PROJECT || "fake-project-id"; + +const EMULATOR_CONFIG = readEmulatorConfig(); +const STORAGE_EMULATOR_HOST = getStorageEmulatorHost(EMULATOR_CONFIG); +const AUTH_EMULATOR_HOST = getAuthEmulatorHost(EMULATOR_CONFIG); + +// Emulators accept fake app configs. This is sufficient for testing against the emulator. +const FAKE_APP_CONFIG = { + apiKey: "fake-api-key", + projectId: `${FIREBASE_PROJECT}`, + authDomain: `${FIREBASE_PROJECT}.firebaseapp.com`, + storageBucket: `${FIREBASE_PROJECT}.appspot.com`, + appId: "fake-app-id", +}; + +const storageBucket = FAKE_APP_CONFIG.storageBucket; + +describe("Emulator Internals", function (this) { + // Temp directory to store generated files. + let tmpDir: string; + let test: TriggerEndToEndTest; + // eslint-disable-next-line @typescript-eslint/no-invalid-this + this.timeout(TEST_SETUP_TIMEOUT); + + let browser: puppeteer.Browser; + let page: puppeteer.Page; + let testGcsBucket: Bucket; + + before(async () => { + this.timeout(TEST_SETUP_TIMEOUT); + + // Start emulators + process.env.STORAGE_EMULATOR_HOST = STORAGE_EMULATOR_HOST; + test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, EMULATOR_CONFIG); + await test.startEmulators(["--only", "auth,storage"]); + + // Initialize GCS SDK + const adminCredential = fs.existsSync(path.join(__dirname, SERVICE_ACCOUNT_KEY)) + ? admin.credential.cert(readJson(SERVICE_ACCOUNT_KEY)) + : admin.credential.applicationDefault(); + admin.initializeApp({ credential: adminCredential }); + + testGcsBucket = admin.storage().bucket(storageBucket); + }); + + const initFirebaseSdkPage = async () => { + const browser = await puppeteer.launch({ + devtools: true, + headless: true, + }); + const page = await browser.newPage(); + await page.goto("https://example.com", { waitUntil: "networkidle2" }); + + await page.addScriptTag({ + url: "https://www.gstatic.com/firebasejs/7.24.0/firebase-app.js", + }); + await page.addScriptTag({ + url: "https://www.gstatic.com/firebasejs/7.24.0/firebase-auth.js", + }); + await page.addScriptTag({ + url: "https://storage.googleapis.com/fir-tools-builds/firebase-storage.js", + }); + + await page.evaluate( + (appConfig, emulatorHost) => { + firebase.initializeApp(appConfig); + const auth = firebase.auth(); + auth.useEmulator(emulatorHost); + (window as any).auth = auth; + }, + FAKE_APP_CONFIG, + AUTH_EMULATOR_HOST + ); + + await page.evaluate((hostAndPort) => { + const [host, port] = hostAndPort.split(":") as string[]; + (firebase.storage() as any).useEmulator(host, port); + }, STORAGE_EMULATOR_HOST.replace(/^(https?:|)\/\//, "")); + return { browser, page }; + }; + + beforeEach(async () => { + await resetStorageEmulator(STORAGE_EMULATOR_HOST); + ({ browser, page } = await initFirebaseSdkPage()); + }); + + this.afterEach(async () => { + await page.close(); + await browser.close(); + }); + + after(async () => { + this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + + if (tmpDir) { + fs.rmdirSync(tmpDir, { recursive: true }); + } + delete process.env.STORAGE_EMULATOR_HOST; + await test.stopEmulators(); + }); + + it("gcloud API persisted files should be accessible via Firebase SDK", async () => { + const smallFilePath = createRandomFile("testFile", SMALL_FILE_SIZE); + await testGcsBucket.upload(smallFilePath, { destination: "public/testFile" }); + + const downloadUrl = await page.evaluate(async () => { + return firebase.storage().ref("public/testFile").getDownloadURL(); + }); + const requestClient = http; + const data = await new Promise((resolve, reject) => { + requestClient.get(downloadUrl, (response) => { + const bufs: any = []; + response + .on("data", (chunk) => bufs.push(chunk)) + .on("end", () => resolve(Buffer.concat(bufs))) + .on("close", resolve) + .on("error", reject); + }); + }); + + expect(data).to.deep.equal(fs.readFileSync(smallFilePath)); + }); + + it("Firebase SDK persisted files should be accessible via gcloud API", async () => { + const fileContent = "some-file-content"; + await uploadText(page, "public/testFile", fileContent); + + const [downloadedFileContent] = await testGcsBucket.file("public/testFile").download(); + expect(downloadedFileContent).to.deep.equal(Buffer.from(fileContent)); + }); +}); diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index ef6cea68270..d6e82eeea46 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -12,3 +12,5 @@ mocha scripts/storage-emulator-integration/rules/*.test.ts mocha scripts/storage-emulator-integration/multiple-targets/tests.ts mocha scripts/storage-emulator-integration/tests.ts + +mocha scripts/storage-emulator-integration/*.test.ts diff --git a/scripts/storage-emulator-integration/storage.rules b/scripts/storage-emulator-integration/storage.rules index 3c1a8c64c06..460409cac2f 100644 --- a/scripts/storage-emulator-integration/storage.rules +++ b/scripts/storage-emulator-integration/storage.rules @@ -31,7 +31,7 @@ service firebase.storage { } match /public/{allPaths=**} { - allow read; + allow read, write; } } } diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index d1fbd5fe5b9..d73f6ce84ea 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -54,8 +54,6 @@ let tmpDir: string; describe("Storage emulator", () => { let test: TriggerEndToEndTest; - let browser: puppeteer.Browser; - let page: puppeteer.Page; let smallFilePath: string; let largeFilePath: string; @@ -1111,18 +1109,20 @@ describe("Storage emulator", () => { describe("Firebase Endpoints", () => { let storage: Storage; + let browser: puppeteer.Browser; + let page: puppeteer.Page; const filename = "testing/storage_ref/image.png"; before(async function (this) { this.timeout(TEST_SETUP_TIMEOUT); - if (!TEST_CONFIG.useProductionServers) { - test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, emulatorConfig); - await test.startEmulators(["--only", "auth,storage"]); - } else { + if (TEST_CONFIG.useProductionServers) { process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(__dirname, SERVICE_ACCOUNT_KEY); storage = new Storage(); + } else { + test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, emulatorConfig); + await test.startEmulators(["--only", "auth,storage"]); } browser = await puppeteer.launch({ @@ -1143,7 +1143,6 @@ describe("Storage emulator", () => { await page.addScriptTag({ url: "https://www.gstatic.com/firebasejs/7.24.0/firebase-auth.js", }); - // url: "https://storage.googleapis.com/fir-tools-builds/firebase-storage-new.js", await page.addScriptTag({ url: TEST_CONFIG.useProductionServers ? "https://www.gstatic.com/firebasejs/7.24.0/firebase-storage.js" @@ -1174,14 +1173,29 @@ describe("Storage emulator", () => { } }); + afterEach(async () => { + await page.close(); + }); + + after(async function (this) { + this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + + await browser.close(); + if (TEST_CONFIG.useProductionServers) { + delete process.env.GOOGLE_APPLICATION_CREDENTIALS; + } else { + await test.stopEmulators(); + } + }); + describe(".ref()", () => { beforeEach(async function (this) { this.timeout(TEST_SETUP_TIMEOUT); - if (!TEST_CONFIG.useProductionServers) { - await resetStorageEmulator(STORAGE_EMULATOR_HOST); - } else { + if (TEST_CONFIG.useProductionServers) { await storage.bucket(storageBucket).deleteFiles(); + } else { + await resetStorageEmulator(STORAGE_EMULATOR_HOST); } await page.evaluate( @@ -1708,11 +1722,7 @@ describe("Storage emulator", () => { emulatorSpecificDescribe("Non-SDK Endpoints", () => { beforeEach(async () => { - if (!TEST_CONFIG.useProductionServers) { - await resetStorageEmulator(STORAGE_EMULATOR_HOST); - } else { - await storage.bucket(storageBucket).deleteFiles(); - } + await resetStorageEmulator(STORAGE_EMULATOR_HOST); await page.evaluate( (IMAGE_FILE_BASE64, filename) => { @@ -1982,18 +1992,5 @@ describe("Storage emulator", () => { }); }); }); - - after(async function (this) { - this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); - - if (!TEST_CONFIG.keepBrowserOpen) { - await browser.close(); - } - if (!TEST_CONFIG.useProductionServers) { - await test.stopEmulators(); - } else { - delete process.env.GOOGLE_APPLICATION_CREDENTIALS; - } - }); }); }); diff --git a/src/emulator/shared/request.ts b/src/emulator/shared/request.ts new file mode 100644 index 00000000000..75a905dc148 --- /dev/null +++ b/src/emulator/shared/request.ts @@ -0,0 +1,18 @@ +import { Request } from "express"; + +/** Returns the body of a {@link Request} as a {@link Buffer}. */ +export async function reqBodyToBuffer(req: Request): Promise { + if (req.body instanceof Buffer) { + return Buffer.from(req.body); + } + const bufs: Buffer[] = []; + req.on("data", (data) => { + bufs.push(data); + }); + await new Promise((resolve) => { + req.on("end", () => { + resolve(); + }); + }); + return Buffer.concat(bufs); +} diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 85dc33837a1..97a75ef7657 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -9,6 +9,7 @@ import { parseObjectUploadMultipartRequest } from "../multipart"; import { NotFoundError, ForbiddenError } from "../errors"; import { NotCancellableError, Upload, UploadNotActiveError } from "../upload"; import { ListResponse } from "../list"; +import { reqBodyToBuffer } from "../../shared/request"; /** * @param emulator @@ -169,22 +170,6 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return res.json(response); }); - const reqBodyToBuffer = async (req: Request): Promise => { - if (req.body instanceof Buffer) { - return Buffer.from(req.body); - } - const bufs: Buffer[] = []; - req.on("data", (data) => { - bufs.push(data); - }); - await new Promise((resolve) => { - req.on("end", () => { - resolve(); - }); - }); - return Buffer.concat(bufs); - }; - const handleUpload = async (req: Request, res: Response) => { if (!req.query.name) { res.sendStatus(400); diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index fdbb01f3cd2..5112e4275e8 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -16,26 +16,24 @@ import type { Request, Response } from "express"; import { parseObjectUploadMultipartRequest } from "../multipart"; import { Upload, UploadNotActiveError } from "../upload"; import { ForbiddenError, NotFoundError } from "../errors"; +import { reqBodyToBuffer } from "../../shared/request"; -/** - * @param emulator - * @param storage - */ export function createCloudEndpoints(emulator: StorageEmulator): Router { // eslint-disable-next-line new-cap const gcloudStorageAPI = Router(); - const { storageLayer, uploadService } = emulator; + // Use Admin StorageLayer to ensure Firebase Rules validation is skipped. + const { adminStorageLayer, uploadService } = emulator; // Automatically create a bucket for any route which uses a bucket gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => { - storageLayer.createBucket(req.params[0]); + adminStorageLayer.createBucket(req.params[0]); next(); }); gcloudStorageAPI.get("/b", async (req, res) => { res.json({ kind: "storage#buckets", - items: await storageLayer.listBuckets(), + items: await adminStorageLayer.listBuckets(), }); }); @@ -44,19 +42,16 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { async (req, res) => { let getObjectResponse: GetObjectResponse; try { - getObjectResponse = await storageLayer.handleGetObject( - { - bucketId: req.params.bucketId, - decodedObjectId: req.params.objectId, - }, - /* skipAuth = */ true - ); + getObjectResponse = await adminStorageLayer.handleGetObject({ + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }); } catch (err) { if (err instanceof NotFoundError) { return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { - throw new Error("Request failed unexpectedly due to Firebase Rules."); + return res.sendStatus(403); } throw err; } @@ -71,20 +66,17 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", async (req, res) => { let updatedMetadata: StoredFileMetadata; try { - updatedMetadata = await storageLayer.handleUpdateObjectMetadata( - { - bucketId: req.params.bucketId, - decodedObjectId: req.params.objectId, - metadata: req.body as IncomingMetadata, - }, - /* skipAuth = */ true - ); + updatedMetadata = await adminStorageLayer.handleUpdateObjectMetadata({ + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + metadata: req.body as IncomingMetadata, + }); } catch (err) { if (err instanceof NotFoundError) { return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { - throw new Error("Request failed unexpectedly due to Firebase Rules."); + return res.sendStatus(403); } throw err; } @@ -101,7 +93,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined; const prefix = req.query.prefix ? req.query.prefix.toString() : ""; - const listResult = storageLayer.listItems( + const listResult = adminStorageLayer.listItems( req.params.bucketId, prefix, delimiter, @@ -114,41 +106,22 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { try { - await storageLayer.handleDeleteObject( - { - bucketId: req.params.bucketId, - decodedObjectId: req.params.objectId, - }, - /* skipAuth = */ true - ); + await adminStorageLayer.handleDeleteObject({ + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }); } catch (err) { if (err instanceof NotFoundError) { return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { - throw new Error("Request failed unexpectedly due to Firebase Rules."); + return res.sendStatus(403); } throw err; } return res.sendStatus(204); }); - const reqBodyToBuffer = async (req: Request): Promise => { - if (req.body instanceof Buffer) { - return Buffer.from(req.body); - } - const bufs: Buffer[] = []; - req.on("data", (data) => { - bufs.push(data); - }); - await new Promise((resolve) => { - req.on("end", () => { - resolve(); - }); - }); - return Buffer.concat(bufs); - }; - gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => { if (!req.query.upload_id) { res.sendStatus(400); @@ -171,10 +144,10 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { let metadata: StoredFileMetadata; try { - metadata = await storageLayer.handleUploadObject(upload, /* skipAuth = */ true); + metadata = await adminStorageLayer.handleUploadObject(upload); } catch (err) { if (err instanceof ForbiddenError) { - throw new Error("Request failed unexpectedly due to Firebase Rules."); + return res.sendStatus(403); } throw err; } @@ -189,19 +162,16 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { ); let getObjectResponse: GetObjectResponse; try { - getObjectResponse = await storageLayer.handleGetObject( - { - bucketId: req.params.bucketId, - decodedObjectId: req.params.objectId, - }, - /* skipAuth = */ true - ); + getObjectResponse = await adminStorageLayer.handleGetObject({ + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }); } catch (err) { if (err instanceof NotFoundError) { return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { - throw new Error("Request failed unexpectedly due to Firebase Rules."); + return res.sendStatus(403); } throw err; } @@ -282,10 +252,10 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { }); let metadata: StoredFileMetadata; try { - metadata = await storageLayer.handleUploadObject(upload, /* skipAuth = */ true); + metadata = await adminStorageLayer.handleUploadObject(upload); } catch (err) { if (err instanceof ForbiddenError) { - throw new Error("Request failed unexpectedly due to Firebase Rules."); + return res.sendStatus(403); } throw err; } @@ -296,19 +266,16 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => { let getObjectResponse: GetObjectResponse; try { - getObjectResponse = await storageLayer.handleGetObject( - { - bucketId: req.params.bucketId, - decodedObjectId: req.params.objectId, - }, - /* skipAuth = */ true - ); + getObjectResponse = await adminStorageLayer.handleGetObject({ + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }); } catch (err) { if (err instanceof NotFoundError) { return sendObjectNotFound(req, res); } if (err instanceof ForbiddenError) { - throw new Error("Request failed unexpectedly due to Firebase Rules."); + return res.sendStatus(403); } throw err; } @@ -318,7 +285,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { gcloudStorageAPI.post( "/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", (req, res, next) => { - const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId); + const md = adminStorageLayer.getMetadata(req.params.bucketId, req.params.objectId); if (!md) { return sendObjectNotFound(req, res); @@ -329,7 +296,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return next(); } - const metadata = storageLayer.copyFile( + const metadata = adminStorageLayer.copyFile( md, req.params.destBucketId, req.params.destObjectId, diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index a44ee9b5957..c81d831a41c 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -18,7 +18,7 @@ import { getProjectAdminSdkConfigOrCached, } from "../adminSdkConfig"; import { RulesetOperationMethod } from "./rules/types"; -import { AdminCredentialValidator, RulesValidator } from "./rules/utils"; +import { AdminCredentialValidator, FirebaseRulesValidator } from "./rules/utils"; import { Persistence } from "./persistence"; import { Upload, UploadStatus } from "./upload"; @@ -105,24 +105,15 @@ export type DeleteDownloadTokenRequest = { }; export class StorageLayer { - private _files!: Map; - private _buckets!: Map; - private _cloudFunctions: StorageCloudFunctions; - constructor( private _projectId: string, - private _rulesValidator: RulesValidator, + private _files: Map, + private _buckets: Map, + private _rulesValidator: FirebaseRulesValidator, private _adminCredsValidator: AdminCredentialValidator, - private _persistence: Persistence - ) { - this.reset(); - this._cloudFunctions = new StorageCloudFunctions(this._projectId); - } - - public reset(): void { - this._files = new Map(); - this._buckets = new Map(); - } + private _persistence: Persistence, + private _cloudFunctions: StorageCloudFunctions + ) {} createBucket(id: string): void { if (!this._buckets.has(id)) { @@ -147,17 +138,14 @@ export class StorageLayer { * @throws {NotFoundError} if object does not exist * @throws {ForbiddenError} if request is unauthorized */ - public async handleGetObject( - request: GetObjectRequest, - skipAuth = false - ): Promise { + public async handleGetObject(request: GetObjectRequest): Promise { const metadata = this.getMetadata(request.bucketId, request.decodedObjectId); // If a valid download token is present, skip Firebase Rules auth. Mainly used by the js sdk. const hasValidDownloadToken = (metadata?.downloadTokens || []).includes( request.downloadToken ?? "" ); - let authorized = skipAuth || hasValidDownloadToken; + let authorized = hasValidDownloadToken; if (!authorized) { authorized = await this._rulesValidator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), @@ -208,17 +196,15 @@ export class StorageLayer { * @throws {ForbiddenError} if the request is not authorized. * @throws {NotFoundError} if the object does not exist. */ - public async handleDeleteObject(request: DeleteObjectRequest, skipAuth = false): Promise { + public async handleDeleteObject(request: DeleteObjectRequest): Promise { const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId); - const authorized = - skipAuth || - (await this._rulesValidator.validate( - ["b", request.bucketId, "o", request.decodedObjectId].join("/"), - request.bucketId, - RulesetOperationMethod.DELETE, - { before: storedMetadata?.asRulesResource() }, - request.authorization - )); + const authorized = await this._rulesValidator.validate( + ["b", request.bucketId, "o", request.decodedObjectId].join("/"), + request.bucketId, + RulesetOperationMethod.DELETE, + { before: storedMetadata?.asRulesResource() }, + request.authorization + ); if (!authorized) { throw new ForbiddenError(); } @@ -260,23 +246,20 @@ export class StorageLayer { * @throws {NotFoundError} if the object does not exist. */ public async handleUpdateObjectMetadata( - request: UpdateObjectMetadataRequest, - skipAuth = false + request: UpdateObjectMetadataRequest ): Promise { const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId); - const authorized = - skipAuth || - (await this._rulesValidator.validate( - ["b", request.bucketId, "o", request.decodedObjectId].join("/"), - request.bucketId, - RulesetOperationMethod.UPDATE, - { - before: storedMetadata?.asRulesResource(), - after: storedMetadata?.asRulesResource(request.metadata), - }, - request.authorization - )); + const authorized = await this._rulesValidator.validate( + ["b", request.bucketId, "o", request.decodedObjectId].join("/"), + request.bucketId, + RulesetOperationMethod.UPDATE, + { + before: storedMetadata?.asRulesResource(), + after: storedMetadata?.asRulesResource(request.metadata), + }, + request.authorization + ); if (!authorized) { throw new ForbiddenError(); } @@ -291,10 +274,8 @@ export class StorageLayer { /** * Last step in uploading a file. Validates the request and persists the staging * object to its permanent location on disk. - * TODO(tonyjhuang): Inject a Rules evaluator into StorageLayer to avoid needing skipAuth param - * @throws {ForbiddenError} if the request is not authorized. */ - public async handleUploadObject(upload: Upload, skipAuth = false): Promise { + public async handleUploadObject(upload: Upload): Promise { if (upload.status !== UploadStatus.FINISHED) { throw new Error(`Unexpected upload status encountered: ${upload.status}.`); } @@ -314,15 +295,13 @@ export class StorageLayer { this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size) ); - const authorized = - skipAuth || - (await this._rulesValidator.validate( - ["b", upload.bucketId, "o", upload.objectId].join("/"), - upload.bucketId, - RulesetOperationMethod.CREATE, - { after: metadata?.asRulesResource() }, - upload.authorization - )); + const authorized = await this._rulesValidator.validate( + ["b", upload.bucketId, "o", upload.objectId].join("/"), + upload.bucketId, + RulesetOperationMethod.CREATE, + { after: metadata?.asRulesResource() }, + upload.authorization + ); if (!authorized) { this._persistence.deleteFile(upload.path); throw new ForbiddenError(); @@ -395,19 +374,14 @@ export class StorageLayer { * Lists all files and prefixes (folders) at a path. * @throws {ForbiddenError} if the request is not authorized. */ - public async handleListObjects( - request: ListObjectsRequest, - skipAuth = false - ): Promise { - const authorized = - skipAuth || - (await this._rulesValidator.validate( - ["b", request.bucketId, "o", request.prefix].join("/"), - request.bucketId, - RulesetOperationMethod.LIST, - {}, - request.authorization - )); + public async handleListObjects(request: ListObjectsRequest): Promise { + const authorized = await this._rulesValidator.validate( + ["b", request.bucketId, "o", request.prefix].join("/"), + request.bucketId, + RulesetOperationMethod.LIST, + {}, + request.authorization + ); if (!authorized) { throw new ForbiddenError(); } diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index b98d0724384..f86f002a9a0 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -3,15 +3,22 @@ import * as utils from "../../utils"; import { Constants } from "../constants"; import { EmulatorInfo, EmulatorInstance, Emulators } from "../types"; import { createApp } from "./server"; -import { StorageLayer } from "./files"; +import { StorageLayer, StoredFile } from "./files"; import { EmulatorLogger } from "../emulatorLogger"; import { createStorageRulesManager, StorageRulesManager } from "./rules/manager"; import { StorageRulesRuntime } from "./rules/runtime"; import { SourceFile } from "./rules/types"; import express = require("express"); -import { getAdminCredentialValidator, getRulesValidator } from "./rules/utils"; +import { + getAdminCredentialValidator, + getAdminOnlyFirebaseRulesValidator, + getFirebaseRulesValidator, + FirebaseRulesValidator, +} from "./rules/utils"; import { Persistence } from "./persistence"; import { UploadService } from "./upload"; +import { CloudStorageBucketMetadata } from "./metadata"; +import { StorageCloudFunctions } from "./cloudFunctions"; export type RulesConfig = { resource: string; @@ -36,27 +43,47 @@ export class StorageEmulator implements EmulatorInstance { private _logger = EmulatorLogger.forEmulator(Emulators.STORAGE); private _rulesRuntime: StorageRulesRuntime; private _rulesManager: StorageRulesManager; + private _files: Map = new Map(); + private _buckets: Map = new Map(); + private _cloudFunctions: StorageCloudFunctions; private _persistence: Persistence; - private _storageLayer: StorageLayer; private _uploadService: UploadService; + private _storageLayer: StorageLayer; + /** StorageLayer that validates requests solely based on admin credentials. */ + private _adminStorageLayer: StorageLayer; constructor(private args: StorageEmulatorArgs) { this._rulesRuntime = new StorageRulesRuntime(); this._rulesManager = createStorageRulesManager(this.args.rules, this._rulesRuntime); + this._cloudFunctions = new StorageCloudFunctions(args.projectId); this._persistence = new Persistence(this.getPersistenceTmpDir()); - this._storageLayer = new StorageLayer( - args.projectId, - getRulesValidator((resource: string) => this._rulesManager.getRuleset(resource)), - getAdminCredentialValidator(), - this._persistence - ); this._uploadService = new UploadService(this._persistence); + + const createStorageLayer = (rulesValidator: FirebaseRulesValidator): StorageLayer => { + return new StorageLayer( + args.projectId, + this._files, + this._buckets, + rulesValidator, + getAdminCredentialValidator(), + this._persistence, + this._cloudFunctions + ); + }; + this._storageLayer = createStorageLayer( + getFirebaseRulesValidator((resource: string) => this._rulesManager.getRuleset(resource)) + ); + this._adminStorageLayer = createStorageLayer(getAdminOnlyFirebaseRulesValidator()); } get storageLayer(): StorageLayer { return this._storageLayer; } + get adminStorageLayer(): StorageLayer { + return this._adminStorageLayer; + } + get uploadService(): UploadService { return this._uploadService; } @@ -70,7 +97,8 @@ export class StorageEmulator implements EmulatorInstance { } reset(): void { - this._storageLayer.reset(); + this._files.clear(); + this._buckets.clear(); this._persistence.reset(this.getPersistenceTmpDir()); this._uploadService.reset(); } diff --git a/src/emulator/storage/rules/utils.ts b/src/emulator/storage/rules/utils.ts index e6b9d3b4b50..b1b644161ab 100644 --- a/src/emulator/storage/rules/utils.ts +++ b/src/emulator/storage/rules/utils.ts @@ -11,7 +11,7 @@ export type RulesVariableOverrides = { }; /** Authorizes storage requests via Firebase Rules rulesets. */ -export interface RulesValidator { +export interface FirebaseRulesValidator { validate( path: string, bucketId: string, @@ -32,7 +32,9 @@ export type RulesetProvider = (resource: string) => StorageRulesetInstance | und /** * Returns a validator that pulls a Ruleset from a {@link RulesetProvider} on each run. */ -export function getRulesValidator(rulesetProvider: RulesetProvider): RulesValidator { +export function getFirebaseRulesValidator( + rulesetProvider: RulesetProvider +): FirebaseRulesValidator { return { validate: async ( path: string, @@ -52,7 +54,31 @@ export function getRulesValidator(rulesetProvider: RulesetProvider): RulesValida }; } -/** Returns a validator for admin credentials. */ +/** + * Returns a Firebase Rules validator returns true iff a valid OAuth (admin) credential + * is available. This validator does *not* check Firebase Rules directly. + */ +export function getAdminOnlyFirebaseRulesValidator(): FirebaseRulesValidator { + return { + validate: ( + _path: string, + _bucketId: string, + _method: RulesetOperationMethod, + _variableOverrides: RulesVariableOverrides, + _authorization?: string + ) => { + // TODO(tonyjhuang): This should check for valid admin credentials some day. + // Unfortunately today, there's no easy way to set up the GCS SDK to pass + // "Bearer owner" along with requests so this is a placeholder. + return Promise.resolve(true); + }, + }; +} + +/** + * Returns a validator for OAuth (admin) credentials. This typically takes the shape of + * "Authorization: Bearer owner" headers. + */ export function getAdminCredentialValidator(): AdminCredentialValidator { return { validate: isValidAdminCredentials }; } diff --git a/src/test/emulators/storage/files.spec.ts b/src/test/emulators/storage/files.spec.ts index 9d513211624..b10c0408537 100644 --- a/src/test/emulators/storage/files.spec.ts +++ b/src/test/emulators/storage/files.spec.ts @@ -6,7 +6,7 @@ import { StorageCloudFunctions } from "../../../emulator/storage/cloudFunctions" import { StorageLayer } from "../../../emulator/storage/files"; import { ForbiddenError, NotFoundError } from "../../../emulator/storage/errors"; import { Persistence } from "../../../emulator/storage/persistence"; -import { RulesValidator } from "../../../emulator/storage/rules/utils"; +import { FirebaseRulesValidator } from "../../../emulator/storage/rules/utils"; import { Upload, UploadService, UploadStatus, UploadType } from "../../../emulator/storage/upload"; const ALWAYS_TRUE_RULES_VALIDATOR = { @@ -135,12 +135,15 @@ describe("files", () => { }); }); - const getStorageLayer = (rulesValidator: RulesValidator) => + const getStorageLayer = (rulesValidator: FirebaseRulesValidator) => new StorageLayer( "project", + new Map(), + new Map(), rulesValidator, ALWAYS_TRUE_ADMIN_CREDENTIAL_VALIDATOR, - _persistence + _persistence, + new StorageCloudFunctions("project") ); const getPersistenceTmpDir = () => `${tmpdir()}/firebase/storage/blobs`; From 7c41d7310ea576281bbaeda5087e9922f9aeccd1 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Wed, 23 Mar 2022 14:46:18 -0400 Subject: [PATCH 0188/1699] Refactor gcloud to use same list method in StorageLayer (#4349) * multipart and firebase.ts integration * update gcloud.ts * refactor persistence out into StorageEmulator * fix lint and method references * fix tests * upload.ts * update callers of UploadService and tests * more tests * more tests * md -> metadata * address pr comments * lint * fix tests * fix tests * stash * more integration tests, fix upload bug * more fixing * fix remaining * lint * metadata * deleteobject * list * get * fix tests * lint * address pr comments * lint * restructure * cleanup * fix merge * remove skipAuth param from StorageLayer * rename to adminStorageLayer to make StorageLayer type explicit * fix imports * change to use passthrough logic * share state across storagelayers * fix tests * fix import * address pr comments * lint * revert * pr comments * more cleanup * fix tests * remove only * move internal tests to new file * internals test file * refactor gcloud to use same list method in StorageLayer * fix imports * delete object * lint * handlecreate * skipauth * remove list.ts * ensure empty array * lint * test * fix test --- scripts/storage-emulator-integration/tests.ts | 15 ++++ src/emulator/storage/apis/firebase.ts | 29 ++++--- src/emulator/storage/apis/gcloud.ts | 55 +++++++------ src/emulator/storage/files.ts | 79 +++++++------------ src/emulator/storage/list.ts | 20 ----- src/test/emulators/storage/files.spec.ts | 14 ++-- 6 files changed, 97 insertions(+), 115 deletions(-) delete mode 100644 src/emulator/storage/list.ts diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index d73f6ce84ea..efd5333e71d 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -1438,6 +1438,21 @@ describe("Storage emulator", () => { items: ["file.jpg"], }); }); + + it("zero element list array should still be present in response", async () => { + const listResult = await page.evaluate(async () => { + const list = await firebase.storage().ref("/list").listAll(); + return { + prefixes: list.prefixes.map((prefix) => prefix.name), + items: list.items.map((item) => item.name), + }; + }); + + expect(listResult).to.deep.equal({ + prefixes: [], + items: [], + }); + }); }); describe("#list()", () => { diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 97a75ef7657..4b33f3c03a7 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -8,8 +8,8 @@ import { EmulatorRegistry } from "../../registry"; import { parseObjectUploadMultipartRequest } from "../multipart"; import { NotFoundError, ForbiddenError } from "../errors"; import { NotCancellableError, Upload, UploadNotActiveError } from "../upload"; -import { ListResponse } from "../list"; import { reqBodyToBuffer } from "../../shared/request"; +import { ListObjectsResponse } from "../files"; /** * @param emulator @@ -89,7 +89,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { let data: Buffer; try { // Both object data and metadata get can use the same handler since they share auth logic. - ({ metadata, data } = await storageLayer.handleGetObject({ + ({ metadata, data } = await storageLayer.getObject({ bucketId: req.params.bucketId, decodedObjectId: decodeURIComponent(req.params.objectId), authorization: req.header("authorization"), @@ -146,9 +146,9 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { // list object handler firebaseStorageAPI.get("/b/:bucketId/o", async (req, res) => { const maxResults = req.query.maxResults?.toString(); - let response: ListResponse; + let listResponse: ListObjectsResponse; try { - response = await storageLayer.handleListObjects({ + listResponse = await storageLayer.listObjects({ bucketId: req.params.bucketId, prefix: req.query.prefix ? req.query.prefix.toString() : "", delimiter: req.query.delimiter ? req.query.delimiter.toString() : "", @@ -167,7 +167,14 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } throw err; } - return res.json(response); + return res.status(200).json({ + nextPageToken: listResponse.nextPageToken, + prefixes: listResponse.prefixes ?? [], + items: + listResponse.items?.map((item) => { + return { name: item.name, bucket: item.bucket }; + }) ?? [], + }); }); const handleUpload = async (req: Request, res: Response) => { @@ -213,7 +220,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { }); let metadata: StoredFileMetadata; try { - metadata = await storageLayer.handleUploadObject(upload); + metadata = await storageLayer.uploadObject(upload); } catch (err) { if (err instanceof ForbiddenError) { return res.status(403).json({ @@ -325,7 +332,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } let metadata: StoredFileMetadata; try { - metadata = await storageLayer.handleUploadObject(upload); + metadata = await storageLayer.uploadObject(upload); } catch (err) { if (err instanceof ForbiddenError) { return res.status(403).json({ @@ -358,7 +365,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return res.sendStatus(400); } try { - metadata = storageLayer.handleCreateDownloadToken({ + metadata = storageLayer.createDownloadToken({ bucketId, decodedObjectId, authorization, @@ -380,7 +387,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } else { // delete download token try { - metadata = storageLayer.handleDeleteDownloadToken({ + metadata = storageLayer.deleteDownloadToken({ bucketId, decodedObjectId, token: req.query["delete_token"]?.toString() ?? "", @@ -415,7 +422,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { const handleMetadataUpdate = async (req: Request, res: Response) => { let metadata: StoredFileMetadata; try { - metadata = await storageLayer.handleUpdateObjectMetadata({ + metadata = await storageLayer.updateObjectMetadata({ bucketId: req.params.bucketId, decodedObjectId: decodeURIComponent(req.params.objectId), metadata: req.body as IncomingMetadata, @@ -453,7 +460,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { try { - await storageLayer.handleDeleteObject({ + await storageLayer.deleteObject({ bucketId: req.params.bucketId, decodedObjectId: decodeURIComponent(req.params.objectId), authorization: req.header("authorization"), diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index 5112e4275e8..4e3480bd596 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -10,7 +10,7 @@ import { import { EmulatorRegistry } from "../../registry"; import { StorageEmulator } from "../index"; import { EmulatorLogger } from "../../emulatorLogger"; -import { GetObjectResponse } from "../files"; +import { GetObjectResponse, ListObjectsResponse } from "../files"; import { crc32cToString } from "../crc"; import type { Request, Response } from "express"; import { parseObjectUploadMultipartRequest } from "../multipart"; @@ -42,7 +42,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { async (req, res) => { let getObjectResponse: GetObjectResponse; try { - getObjectResponse = await adminStorageLayer.handleGetObject({ + getObjectResponse = await adminStorageLayer.getObject({ bucketId: req.params.bucketId, decodedObjectId: req.params.objectId, }); @@ -66,7 +66,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", async (req, res) => { let updatedMetadata: StoredFileMetadata; try { - updatedMetadata = await adminStorageLayer.handleUpdateObjectMetadata({ + updatedMetadata = await adminStorageLayer.updateObjectMetadata({ bucketId: req.params.bucketId, decodedObjectId: req.params.objectId, metadata: req.body as IncomingMetadata, @@ -83,30 +83,35 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return res.json(new CloudStorageObjectMetadata(updatedMetadata)); }); - gcloudStorageAPI.get("/b/:bucketId/o", (req, res) => { + gcloudStorageAPI.get("/b/:bucketId/o", async (req, res) => { + let listResponse: ListObjectsResponse; // TODO validate that all query params are single strings and are not repeated. - let maxRes = undefined; - if (req.query.maxResults) { - maxRes = +req.query.maxResults.toString(); + try { + listResponse = await adminStorageLayer.listObjects({ + bucketId: req.params.bucketId, + prefix: req.query.prefix ? req.query.prefix.toString() : "", + delimiter: req.query.delimiter ? req.query.delimiter.toString() : "", + pageToken: req.query.pageToken ? req.query.pageToken.toString() : undefined, + maxResults: req.query.maxResults ? +req.query.maxResults.toString() : undefined, + authorization: req.header("authorization"), + }); + } catch (err) { + if (err instanceof ForbiddenError) { + return res.sendStatus(403); + } + throw err; } - const delimiter = req.query.delimiter ? req.query.delimiter.toString() : ""; - const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined; - const prefix = req.query.prefix ? req.query.prefix.toString() : ""; - - const listResult = adminStorageLayer.listItems( - req.params.bucketId, - prefix, - delimiter, - pageToken, - maxRes - ); - - res.json({ ...listResult, kind: "#storage/objects" }); + return res.status(200).json({ + kind: "#storage/objects", + nextPageToken: listResponse.nextPageToken, + prefixes: listResponse.prefixes, + items: listResponse.items?.map((item) => new CloudStorageObjectMetadata(item)), + }); }); gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { try { - await adminStorageLayer.handleDeleteObject({ + await adminStorageLayer.deleteObject({ bucketId: req.params.bucketId, decodedObjectId: req.params.objectId, }); @@ -144,7 +149,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { let metadata: StoredFileMetadata; try { - metadata = await adminStorageLayer.handleUploadObject(upload); + metadata = await adminStorageLayer.uploadObject(upload); } catch (err) { if (err instanceof ForbiddenError) { return res.sendStatus(403); @@ -162,7 +167,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { ); let getObjectResponse: GetObjectResponse; try { - getObjectResponse = await adminStorageLayer.handleGetObject({ + getObjectResponse = await adminStorageLayer.getObject({ bucketId: req.params.bucketId, decodedObjectId: req.params.objectId, }); @@ -252,7 +257,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { }); let metadata: StoredFileMetadata; try { - metadata = await adminStorageLayer.handleUploadObject(upload); + metadata = await adminStorageLayer.uploadObject(upload); } catch (err) { if (err instanceof ForbiddenError) { return res.sendStatus(403); @@ -266,7 +271,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => { let getObjectResponse: GetObjectResponse; try { - getObjectResponse = await adminStorageLayer.handleGetObject({ + getObjectResponse = await adminStorageLayer.getObject({ bucketId: req.params.bucketId, decodedObjectId: req.params.objectId, }); diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index c81d831a41c..3ecd9e13ab4 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -1,7 +1,4 @@ import { existsSync, readFileSync, readdirSync, statSync } from "fs"; -import { tmpdir } from "os"; -import { v4 } from "uuid"; -import { ListItem, ListResponse } from "./list"; import { CloudStorageBucketMetadata, CloudStorageObjectMetadata, @@ -50,7 +47,7 @@ export class StoredFile { } } -/** Parsed request object for {@link StorageLayer#handleGetObject}. */ +/** Parsed request object for {@link StorageLayer#getObject}. */ export type GetObjectRequest = { bucketId: string; decodedObjectId: string; @@ -58,13 +55,13 @@ export type GetObjectRequest = { downloadToken?: string; }; -/** Response object for {@link StorageLayer#handleGetObject}. */ +/** Response object for {@link StorageLayer#getObject}. */ export type GetObjectResponse = { metadata: StoredFileMetadata; data: Buffer; }; -/** Parsed request object for {@link StorageLayer#handleUpdateObjectMetadata}. */ +/** Parsed request object for {@link StorageLayer#updateObjectMetadata}. */ export type UpdateObjectMetadataRequest = { bucketId: string; decodedObjectId: string; @@ -72,14 +69,14 @@ export type UpdateObjectMetadataRequest = { authorization?: string; }; -/** Parsed request object for {@link StorageLayer#handleDeleteObject}. */ +/** Parsed request object for {@link StorageLayer#deleteObject}. */ export type DeleteObjectRequest = { bucketId: string; decodedObjectId: string; authorization?: string; }; -/** Parsed request object for {@link StorageLayer#handleListObjects}. */ +/** Parsed request object for {@link StorageLayer#listObjects}. */ export type ListObjectsRequest = { bucketId: string; prefix: string; @@ -89,14 +86,21 @@ export type ListObjectsRequest = { authorization?: string; }; -/** Parsed request object for {@link StorageLayer#handleCreateDownloadToken}. */ +/** Response object for {@link StorageLayer#listObjects}. */ +export type ListObjectsResponse = { + prefixes?: string[]; + items?: StoredFileMetadata[]; + nextPageToken?: string; +}; + +/** Parsed request object for {@link StorageLayer#createDownloadToken}. */ export type CreateDownloadTokenRequest = { bucketId: string; decodedObjectId: string; authorization?: string; }; -/** Parsed request object for {@link StorageLayer#handleDeleteDownloadToken}. */ +/** Parsed request object for {@link StorageLayer#deleteDownloadToken}. */ export type DeleteDownloadTokenRequest = { bucketId: string; decodedObjectId: string; @@ -138,7 +142,7 @@ export class StorageLayer { * @throws {NotFoundError} if object does not exist * @throws {ForbiddenError} if request is unauthorized */ - public async handleGetObject(request: GetObjectRequest): Promise { + public async getObject(request: GetObjectRequest): Promise { const metadata = this.getMetadata(request.bucketId, request.decodedObjectId); // If a valid download token is present, skip Firebase Rules auth. Mainly used by the js sdk. @@ -196,7 +200,7 @@ export class StorageLayer { * @throws {ForbiddenError} if the request is not authorized. * @throws {NotFoundError} if the object does not exist. */ - public async handleDeleteObject(request: DeleteObjectRequest): Promise { + public async deleteObject(request: DeleteObjectRequest): Promise { const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId); const authorized = await this._rulesValidator.validate( ["b", request.bucketId, "o", request.decodedObjectId].join("/"), @@ -245,7 +249,7 @@ export class StorageLayer { * @throws {ForbiddenError} if the request is not authorized. * @throws {NotFoundError} if the object does not exist. */ - public async handleUpdateObjectMetadata( + public async updateObjectMetadata( request: UpdateObjectMetadataRequest ): Promise { const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId); @@ -275,7 +279,7 @@ export class StorageLayer { * Last step in uploading a file. Validates the request and persists the staging * object to its permanent location on disk. */ - public async handleUploadObject(upload: Upload): Promise { + public async uploadObject(upload: Upload): Promise { if (upload.status !== UploadStatus.FINISHED) { throw new Error(`Unexpected upload status encountered: ${upload.status}.`); } @@ -374,46 +378,23 @@ export class StorageLayer { * Lists all files and prefixes (folders) at a path. * @throws {ForbiddenError} if the request is not authorized. */ - public async handleListObjects(request: ListObjectsRequest): Promise { + public async listObjects(request: ListObjectsRequest): Promise { + const { bucketId, prefix, delimiter, pageToken, authorization } = request; const authorized = await this._rulesValidator.validate( - ["b", request.bucketId, "o", request.prefix].join("/"), - request.bucketId, + ["b", bucketId, "o", prefix].join("/"), + bucketId, RulesetOperationMethod.LIST, {}, - request.authorization + authorization ); if (!authorized) { throw new ForbiddenError(); } - const itemsResults = this.listItems( - request.bucketId, - request.prefix, - request.delimiter, - request.pageToken, - request.maxResults - ); - return new ListResponse( - itemsResults.prefixes ?? [], - itemsResults.items?.map((i) => new ListItem(i.name, i.bucket)) ?? [], - itemsResults.nextPageToken - ); - } - public listItems( - bucket: string, - prefix: string, - delimiter: string, - pageToken: string | undefined, - maxResults: number | undefined - ): { - prefixes?: string[]; - items?: CloudStorageObjectMetadata[]; - nextPageToken?: string; - } { let items: Array = []; const prefixes = new Set(); for (const [, file] of this._files) { - if (file.metadata.bucket !== bucket) { + if (file.metadata.bucket !== bucketId) { continue; } @@ -456,10 +437,7 @@ export class StorageLayer { } } - if (!maxResults) { - maxResults = 1000; - } - + const maxResults = request.maxResults ?? 1000; let nextPageToken = undefined; if (items.length > maxResults) { nextPageToken = items[maxResults].name; @@ -469,13 +447,12 @@ export class StorageLayer { return { nextPageToken, prefixes: prefixes.size > 0 ? [...prefixes].sort() : undefined, - items: - items.length > 0 ? items.map((item) => new CloudStorageObjectMetadata(item)) : undefined, + items: items.length > 0 ? items : undefined, }; } /** Creates a new Firebase download token for an object. */ - public handleCreateDownloadToken(request: CreateDownloadTokenRequest): StoredFileMetadata { + public createDownloadToken(request: CreateDownloadTokenRequest): StoredFileMetadata { if (!this._adminCredsValidator.validate(request.authorization)) { throw new ForbiddenError(); } @@ -492,7 +469,7 @@ export class StorageLayer { * present, calling this method is a no-op. This method will also regenerate a new token * if the last remaining token is deleted. */ - public handleDeleteDownloadToken(request: DeleteDownloadTokenRequest): StoredFileMetadata { + public deleteDownloadToken(request: DeleteDownloadTokenRequest): StoredFileMetadata { if (!this._adminCredsValidator.validate(request.authorization)) { throw new ForbiddenError(); } diff --git a/src/emulator/storage/list.ts b/src/emulator/storage/list.ts deleted file mode 100644 index ca86ee00ad2..00000000000 --- a/src/emulator/storage/list.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class ListItem { - name: string; - bucket: string; - constructor(name: string, bucket: string) { - this.name = name; - this.bucket = bucket; - } -} - -export class ListResponse { - prefixes: string[]; - items: ListItem[]; - nextPageToken: string | undefined; - - constructor(prefixes: string[], items: ListItem[], nextPageToken: string | undefined) { - this.prefixes = prefixes; - this.items = items; - this.nextPageToken = nextPageToken; - } -} diff --git a/src/test/emulators/storage/files.spec.ts b/src/test/emulators/storage/files.spec.ts index b10c0408537..55a66658fd0 100644 --- a/src/test/emulators/storage/files.spec.ts +++ b/src/test/emulators/storage/files.spec.ts @@ -72,9 +72,7 @@ describe("files", () => { metadataRaw: "{}", }); - expect(storageLayer.handleUploadObject(upload)).to.be.rejectedWith( - "Unexpected upload status" - ); + expect(storageLayer.uploadObject(upload)).to.be.rejectedWith("Unexpected upload status"); }); it("should throw if upload is not authorized", () => { @@ -88,7 +86,7 @@ describe("files", () => { _uploadService.continueResumableUpload(uploadId, Buffer.from("hello world")); const upload = _uploadService.finalizeResumableUpload(uploadId); - expect(storageLayer.handleUploadObject(upload)).to.be.rejectedWith(ForbiddenError); + expect(storageLayer.uploadObject(upload)).to.be.rejectedWith(ForbiddenError); }); }); @@ -101,9 +99,9 @@ describe("files", () => { metadataRaw: `{"contentType": "mime/type"}`, dataRaw: Buffer.from("Hello, World!"), }); - await storageLayer.handleUploadObject(upload); + await storageLayer.uploadObject(upload); - const { metadata, data } = await storageLayer.handleGetObject({ + const { metadata, data } = await storageLayer.getObject({ bucketId: "bucket", decodedObjectId: "dir%2Fobject", }); @@ -116,7 +114,7 @@ describe("files", () => { const storageLayer = getStorageLayer(ALWAYS_FALSE_RULES_VALIDATOR); expect( - storageLayer.handleGetObject({ + storageLayer.getObject({ bucketId: "bucket", decodedObjectId: "dir%2Fobject", }) @@ -127,7 +125,7 @@ describe("files", () => { const storageLayer = getStorageLayer(ALWAYS_TRUE_RULES_VALIDATOR); expect( - storageLayer.handleGetObject({ + storageLayer.getObject({ bucketId: "bucket", decodedObjectId: "dir%2Fobject", }) From 0e5b515df19c133b79b8a5d0d1cabcbb82cc0781 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Wed, 23 Mar 2022 15:01:05 -0400 Subject: [PATCH 0189/1699] Refactor copyFile in StorageLayer (#4351) * multipart and firebase.ts integration * update gcloud.ts * refactor persistence out into StorageEmulator * fix lint and method references * fix tests * upload.ts * update callers of UploadService and tests * more tests * more tests * md -> metadata * address pr comments * lint * fix tests * fix tests * stash * more integration tests, fix upload bug * more fixing * fix remaining * lint * metadata * deleteobject * list * get * fix tests * lint * address pr comments * lint * restructure * cleanup * fix merge * remove skipAuth param from StorageLayer * rename to adminStorageLayer to make StorageLayer type explicit * fix imports * change to use passthrough logic * share state across storagelayers * fix tests * fix import * address pr comments * lint * revert * pr comments * more cleanup * fix tests * remove only * move internal tests to new file * internals test file * refactor gcloud to use same list method in StorageLayer * fix imports * delete object * lint * copyfile * public -> private * override auth * lint --- src/emulator/storage/apis/gcloud.ts | 38 ++++++++++--------- src/emulator/storage/files.ts | 59 +++++++++++++++++++---------- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index 4e3480bd596..3ef78780c07 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -290,27 +290,31 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { gcloudStorageAPI.post( "/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", (req, res, next) => { - const md = adminStorageLayer.getMetadata(req.params.bucketId, req.params.objectId); - - if (!md) { - return sendObjectNotFound(req, res); - } - if (req.params.method === "rewriteTo" && req.query.rewriteToken) { // Don't yet support multi-request copying return next(); } - - const metadata = adminStorageLayer.copyFile( - md, - req.params.destBucketId, - req.params.destObjectId, - req.body - ); - - if (!metadata) { - res.sendStatus(400); - return; + let metadata: StoredFileMetadata; + try { + metadata = adminStorageLayer.copyObject({ + sourceBucket: req.params.bucketId, + sourceObject: req.params.objectId, + destinationBucket: req.params.destBucketId, + destinationObject: req.params.destObjectId, + incomingMetadata: req.body, + // TODO(tonyjhuang): Until we have a way of validating OAuth tokens passed by + // the GCS sdk or gcloud tool, we must assume all requests have valid admin creds. + // authorization: req.header("authorization") + authorization: "Bearer owner", + }); + } catch (err) { + if (err instanceof NotFoundError) { + return sendObjectNotFound(req, res); + } + if (err instanceof ForbiddenError) { + return res.sendStatus(403); + } + throw err; } const resource = new CloudStorageObjectMetadata(metadata); diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 3ecd9e13ab4..c7d500889d7 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -108,6 +108,16 @@ export type DeleteDownloadTokenRequest = { authorization?: string; }; +/** Parsed request object for {@link StorageLayer#copyObject}. */ +export type CopyObjectRequest = { + sourceBucket: string; + sourceObject: string; + destinationBucket: string; + destinationObject: string; + incomingMetadata?: IncomingMetadata; + authorization?: string; +}; + export class StorageLayer { constructor( private _projectId: string, @@ -170,7 +180,7 @@ export class StorageLayer { return { metadata: metadata!, data: this.getBytes(request.bucketId, request.decodedObjectId)! }; } - public getMetadata(bucket: string, object: string): StoredFileMetadata | undefined { + private getMetadata(bucket: string, object: string): StoredFileMetadata | undefined { const key = this.path(bucket, object); const val = this._files.get(key); @@ -319,31 +329,39 @@ export class StorageLayer { return metadata; } - public copyFile( - sourceFile: StoredFileMetadata, - destinationBucket: string, - destinationObject: string, - incomingMetadata?: IncomingMetadata - ): StoredFileMetadata { - const filePath = this.path(destinationBucket, destinationObject); - - this._persistence.deleteFile(filePath, /* failSilently = */ true); + public copyObject({ + sourceBucket, + sourceObject, + destinationBucket, + destinationObject, + incomingMetadata, + authorization, + }: CopyObjectRequest): StoredFileMetadata { + if (!this._adminCredsValidator.validate(authorization)) { + throw new ForbiddenError(); + } + const sourceMetadata = this.getMetadata(sourceBucket, sourceObject); + if (!sourceMetadata) { + throw new NotFoundError(); + } + const sourceBytes = this.getBytes(sourceBucket, sourceObject) as Buffer; - const bytes = this.getBytes(sourceFile.bucket, sourceFile.name) as Buffer; - this._persistence.appendBytes(filePath, bytes); + const destinationFilePath = this.path(destinationBucket, destinationObject); + this._persistence.deleteFile(destinationFilePath, /* failSilently = */ true); + this._persistence.appendBytes(destinationFilePath, sourceBytes); const newMetadata: IncomingMetadata = { - ...sourceFile, - metadata: sourceFile.customMetadata, + ...sourceMetadata, + metadata: sourceMetadata.customMetadata, ...incomingMetadata, }; if ( - sourceFile.downloadTokens.length && + sourceMetadata.downloadTokens.length && // Only copy download tokens if we're not overwriting any custom metadata !(incomingMetadata?.metadata && Object.keys(incomingMetadata?.metadata).length) ) { if (!newMetadata.metadata) newMetadata.metadata = {}; - newMetadata.metadata.firebaseStorageDownloadTokens = sourceFile.downloadTokens.join(","); + newMetadata.metadata.firebaseStorageDownloadTokens = sourceMetadata.downloadTokens.join(","); } if (newMetadata.metadata) { // Convert null metadata values to empty strings @@ -364,11 +382,14 @@ export class StorageLayer { customMetadata: newMetadata.metadata, }, this._cloudFunctions, - bytes, + sourceBytes, incomingMetadata ); - const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(filePath)); - this._files.set(filePath, file); + const file = new StoredFile( + copiedFileMetadata, + this._persistence.getDiskPath(destinationFilePath) + ); + this._files.set(destinationFilePath, file); this._cloudFunctions.dispatch("finalize", new CloudStorageObjectMetadata(file.metadata)); return file.metadata; From 588eba3688c385b6178d81e302678d9c1c326a38 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 23 Mar 2022 16:45:10 -0700 Subject: [PATCH 0190/1699] Add deprecation notice for Java<11. (#4347) * Add deprecation notice for Java<11. * Add CHANGELOG and fix lint. --- CHANGELOG.md | 1 + src/commands/emulators-start.ts | 8 +- src/commands/ext-dev-emulators-start.ts | 7 +- src/emulator/commandUtils.ts | 103 +++++++++++++++++++++++- src/emulator/controller.ts | 17 +++- src/emulator/downloadableEmulators.ts | 7 ++ 6 files changed, 138 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..b0a5aa7913b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Marks Java 10 and below as deprecated. Support will be dropped in Firebase CLI v11. Please upgrade to Java version 11 or above to continue using the emulators. (#4347) diff --git a/src/commands/emulators-start.ts b/src/commands/emulators-start.ts index 615324cd3b3..8f71aa57cd5 100644 --- a/src/commands/emulators-start.ts +++ b/src/commands/emulators-start.ts @@ -6,6 +6,7 @@ import { EmulatorRegistry } from "../emulator/registry"; import { Emulators, EMULATORS_SUPPORTED_BY_UI } from "../emulator/types"; import * as clc from "cli-color"; import { Constants } from "../emulator/constants"; +import { logLabeledWarning } from "../utils"; // eslint-disable-next-line @typescript-eslint/no-var-requires const Table = require("cli-table"); @@ -26,8 +27,9 @@ module.exports = new Command("emulators:start") .action(async (options: any) => { const killSignalPromise = commandUtils.shutdownWhenKilled(options); + let deprecationNotices; try { - await controller.startAll(options); + ({ deprecationNotices } = await controller.startAll(options)); } catch (e: any) { await controller.cleanShutdown(); throw e; @@ -111,6 +113,10 @@ Issues? Report them at ${stylizeLink( // Add this line above once connect page is implemented // It is now safe to connect your app. Instructions: http://${uiInfo?.host}:${uiInfo?.port}/connect + for (const notice of deprecationNotices) { + logLabeledWarning("emulators", notice, "warn"); + } + // Hang until explicitly killed await killSignalPromise; }); diff --git a/src/commands/ext-dev-emulators-start.ts b/src/commands/ext-dev-emulators-start.ts index 782ce63fa5f..fcab1d6edd7 100644 --- a/src/commands/ext-dev-emulators-start.ts +++ b/src/commands/ext-dev-emulators-start.ts @@ -18,9 +18,11 @@ module.exports = new Command("ext:dev:emulators:start") .action(async (options: any) => { const killSignalPromise = commandUtils.shutdownWhenKilled(options); const emulatorOptions = await optionsHelper.buildOptions(options); + + let deprecationNotices; try { commandUtils.beforeEmulatorCommand(emulatorOptions); - await controller.startAll(emulatorOptions); + ({ deprecationNotices } = await controller.startAll(emulatorOptions)); } catch (e: any) { await controller.cleanShutdown(); if (!(e instanceof FirebaseError)) { @@ -30,6 +32,9 @@ module.exports = new Command("ext:dev:emulators:start") } utils.logSuccess("All emulators ready, it is now safe to connect."); + for (const notice of deprecationNotices) { + utils.logLabeledWarning("emulators", notice, "warn"); + } // Hang until explicitly killed await killSignalPromise; diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index 9aa6c2dd541..f1ba2ede4d4 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -416,18 +416,119 @@ export async function emulatorExec(script: string, options: any) { extraEnv.GCLOUD_PROJECT = projectId; } let exitCode = 0; + let deprecationNotices; try { const showUI = !!options.ui; - await controller.startAll(options, showUI); + ({ deprecationNotices } = await controller.startAll(options, showUI)); exitCode = await runScript(script, extraEnv); await onExit(options); } finally { await controller.cleanShutdown(); } + for (const notice of deprecationNotices) { + utils.logLabeledWarning("emulators", notice, "warn"); + } + if (exitCode !== 0) { throw new FirebaseError(`Script "${clc.bold(script)}" exited with code ${exitCode}`, { exit: exitCode, }); } } + +// Regex to extract Java major version. Only works with Java >= 9. +// See: http://openjdk.java.net/jeps/223 +const JAVA_VERSION_REGEX = /version "([1-9][0-9]*)/; +const MIN_SUPPORTED_JAVA_MAJOR_VERSION = 11; +const JAVA_HINT = "Please make sure Java is installed and on your system PATH."; + +/** + * Return whether Java major verion is supported. Throws if Java not available. + * + * @returns true if Java >= 11, false otherwise + */ +export async function checkJavaSupported(): Promise { + return new Promise((resolve, reject) => { + let child; + try { + child = childProcess.spawn( + "java", + ["-Duser.language=en", "-Dfile.encoding=UTF-8", "-version"], + { + stdio: ["inherit", "pipe", "pipe"], + } + ); + } catch (err: any) { + return reject( + new FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err }) + ); + } + + let output = ""; + let error = ""; + child.stdout?.on("data", (data) => { + const str = data.toString("utf8"); + logger.debug(str); + output += str; + }); + child.stderr?.on("data", (data) => { + const str = data.toString("utf8"); + logger.debug(str); + error += str; + }); + + child.once("error", (err) => { + reject( + new FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err }) + ); + }); + + child.once("exit", (code, signal) => { + if (signal) { + // This is an unlikely situation where the short-lived Java process to + // check version was killed by a signal. + reject(new FirebaseError(`Process \`java -version\` was killed by signal ${signal}.`)); + } else if (code && code !== 0) { + // `java -version` failed. For example, this may happen on some OS X + // where `java` is by default a stub that prints out more information on + // how to install Java. It is critical for us to relay stderr/stdout. + reject( + new FirebaseError( + `Process \`java -version\` has exited with code ${code}. ${JAVA_HINT}\n` + + `-----Original stdout-----\n${output}` + + `-----Original stderr-----\n${error}` + ) + ); + } else { + // Join child process stdout and stderr for further parsing. Order does + // not matter here because we'll parse only a small part later. + resolve(`${output}\n${error}`); + } + }); + }).then((output) => { + const match = output.match(JAVA_VERSION_REGEX); + if (match) { + const version = match[1]; + const versionInt = parseInt(version, 10); + if (!versionInt) { + utils.logLabeledWarning( + "emulators", + `Failed to parse Java version. Got "${match[0]}".`, + "warn" + ); + } else { + logger.debug(`Parsed Java major version: ${versionInt}`); + return versionInt >= MIN_SUPPORTED_JAVA_MAJOR_VERSION; + } + } else { + logger.debug("java -version outputs:", output); + logger.warn(`Failed to parse Java version.`); + } + return false; + }); +} + +export const JAVA_DEPRECATION_WARNING = + "Support for Java version <= 10 will be dropped soon in firebase-tools@11. " + + "Please upgrade to Java version 11 or above to continue using the emulators."; diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index b047e64b1d4..ac84a34e966 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -35,7 +35,7 @@ import { EmulatorLogger } from "./emulatorLogger"; import * as portUtils from "./portUtils"; import { EmulatorHubClient } from "./hubClient"; import { promptOnce } from "../prompt"; -import { FLAG_EXPORT_ON_EXIT_NAME } from "./commandUtils"; +import { FLAG_EXPORT_ON_EXIT_NAME, JAVA_DEPRECATION_WARNING } from "./commandUtils"; import { fileExistsSync } from "../fsutils"; import { StorageEmulator } from "./storage"; import { getStorageRulesConfig } from "./storage/rules/config"; @@ -46,6 +46,7 @@ import { ParsedTriggerDefinition } from "./functionsEmulatorShared"; import { ExtensionsEmulator } from "./extensionsEmulator"; import { previews } from "../previews"; import { normalizeAndValidate } from "../functions/projectConfig"; +import { requiresJava } from "./downloadableEmulators"; const START_LOGGING_EMULATOR = utils.envOverride( "START_LOGGING_EMULATOR", @@ -332,7 +333,10 @@ interface EmulatorOptions extends Options { extDevEnv?: Record; } -export async function startAll(options: EmulatorOptions, showUI = true): Promise { +export async function startAll( + options: EmulatorOptions, + showUI = true +): Promise<{ deprecationNotices: string[] }> { // Emulators config is specified in firebase.json as: // "emulators": { // "firestore": { @@ -353,6 +357,13 @@ export async function startAll(options: EmulatorOptions, showUI = true): Promise `No emulators to start, run ${clc.bold("firebase init emulators")} to get started.` ); } + const deprecationNotices = []; + if (targets.some(requiresJava)) { + if (!(await commandUtils.checkJavaSupported())) { + utils.logLabeledWarning("emulators", JAVA_DEPRECATION_WARNING, "warn"); + deprecationNotices.push(JAVA_DEPRECATION_WARNING); + } + } const hubLogger = EmulatorLogger.forEmulator(Emulators.HUB); hubLogger.logLabeled("BULLET", "emulators", `Starting emulators: ${targets.join(", ")}`); @@ -761,6 +772,8 @@ export async function startAll(options: EmulatorOptions, showUI = true): Promise await instance.connect(); } } + + return { deprecationNotices }; } /** diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index d8941e60b47..a60ecd46d81 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -298,6 +298,13 @@ export async function handleEmulatorProcessError(emulator: Emulators, err: any): } } +export function requiresJava(emulator: Emulators): boolean { + if (emulator in Commands) { + return Commands[emulator as keyof typeof Commands].binary === "java"; + } + return false; +} + async function _runBinary( emulator: DownloadableEmulatorDetails, command: DownloadableEmulatorCommand, From ce65a74031395f4217c9d74189361a9d817f2d17 Mon Sep 17 00:00:00 2001 From: Louis Kuang Date: Wed, 23 Mar 2022 20:15:39 -0400 Subject: [PATCH 0191/1699] Firestore emulator v1.14.1 (#4356) * Release Firestore Emulator v1.14.0. * Remove unnecessary entries from CHANGELOG.md. * Fix CHANGELOG.md formating issue. * Try version 1.14.1. * Update CHANGELOG.md Co-authored-by: Bryan Kendall * Update to v1.14.1. * Fix formatting issue. * Update CHANGELOG.md Co-authored-by: xwkuang5 Co-authored-by: Bryan Kendall Co-authored-by: Yuchen Shi --- CHANGELOG.md | 6 ++++++ src/emulator/downloadableEmulators.ts | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a5aa7913b..d01fa979147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,7 @@ +- Release Cloud Firestore emulator v1.14.1: + - Adds support of x-goog-request-params http header for routing. + - Changes `read-past-max-staleness` error code to align with production + implementation. + - Updates readtime-in-the-future error message. + - Supports importing exports from Windows on UNIX systems. (#2421) - Marks Java 10 and below as deprecated. Support will be dropped in Firebase CLI v11. Please upgrade to Java version 11 or above to continue using the emulators. (#4347) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index a60ecd46d81..d5fb753784e 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -39,14 +39,14 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe }, }, firestore: { - downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.13.1.jar"), - version: "1.13.1", + downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.1.jar"), + version: "1.14.1", opts: { cacheDir: CACHE_DIR, remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.13.1.jar", - expectedSize: 60486708, - expectedChecksum: "e0590880408eacb790874643147c0081", + "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.1.jar", + expectedSize: 60416634, + expectedChecksum: "33cffe8065d4250816f257eb19245932", namePrefix: "cloud-firestore-emulator", }, }, From c58c0f331623dedac892afcadf5be0ad725c25ae Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Thu, 24 Mar 2022 11:31:10 -0700 Subject: [PATCH 0192/1699] Put update-notifier npm command on its own line. (#4359) --- src/bin/firebase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/firebase.js b/src/bin/firebase.js index d833e7ef2ea..50f8f479a3d 100755 --- a/src/bin/firebase.js +++ b/src/bin/firebase.js @@ -26,7 +26,7 @@ marked.setOptions({ }); const updateMessage = `Update available ${clc.xterm(240)("{currentVersion}")} → ${clc.green("{latestVersion}")}\n` + - `To update to the latest version using npm, run ${clc.cyan("npm install -g firebase-tools")}\n` + + `To update to the latest version using npm, run\n${clc.cyan("npm install -g firebase-tools")}\n` + `For other CLI management options, visit the ${marked( "[CLI documentation](https://firebase.google.com/docs/cli#update-cli)" )}`; From 3e501efb600f5b3997d09c7056f39dc9147be4da Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Thu, 24 Mar 2022 12:27:59 -0700 Subject: [PATCH 0193/1699] Add support for UpdateConfig (#4348) Adds support for `updateConfig`. Some noteworthy changes: - Moved logic from `updateTenant` into a helper function for applying updateMasks - Only stores in state a subset of `GoogleCloudIdentitytoolkitAdminV2Config` fields as `Config`, specifically only the fields that are configurable on the agent project level - Point `updateEmulatorProjectConfig` to call `updateConfig` for updating `usageMode` and `allowDuplicateEmails` - Update error codes for passthrough mode Corresponding internal bug: b/192387243, b/192387797 --- src/emulator/auth/operations.ts | 63 +++++--- src/emulator/auth/state.ts | 193 ++++++++++++++++++------- src/emulator/auth/utils.ts | 7 + src/test/emulators/auth/config.spec.ts | 159 ++++++++++++++++++++ src/test/emulators/auth/misc.spec.ts | 6 +- 5 files changed, 346 insertions(+), 82 deletions(-) create mode 100644 src/test/emulators/auth/config.spec.ts diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index ebe0e50680e..ba38d26f3d1 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -33,6 +33,7 @@ import { AgentProjectState, TenantProjectState, MfaConfig, + BlockingFunctionEvents, } from "./state"; import { MfaEnrollments, Schemas } from "./types"; @@ -73,6 +74,7 @@ export const authOperations: AuthOps = { projects: { createSessionCookie, queryAccounts, + updateConfig, accounts: { _: signUp, delete: deleteAccount, @@ -1739,31 +1741,22 @@ function getEmulatorProjectConfig(state: ProjectState): Schemas["EmulatorV1Proje function updateEmulatorProjectConfig( state: ProjectState, - reqBody: Schemas["EmulatorV1ProjectsConfig"] + reqBody: Schemas["EmulatorV1ProjectsConfig"], + ctx: ExegesisContext ): Schemas["EmulatorV1ProjectsConfig"] { - const allowDuplicateEmails = reqBody.signIn?.allowDuplicateEmails; - if (allowDuplicateEmails != null) { - assert( - state instanceof AgentProjectState, - "((Only top level projects can set oneAccountPerEmail.))" - ); - state.oneAccountPerEmail = !allowDuplicateEmails; - } - const usageMode = reqBody.usageMode; - if (usageMode != null) { - assert(state instanceof AgentProjectState, "((Only top level projects can set usageMode.))"); - switch (usageMode) { - case "PASSTHROUGH": - assert(state.getUserCount() === 0, "Users are present, unable to set passthrough mode"); - state.usageMode = UsageMode.PASSTHROUGH; - break; - case "DEFAULT": - state.usageMode = UsageMode.DEFAULT; - break; - default: - throw new BadRequestError("Invalid usage mode provided"); - } + // New developers should not use updateEmulatorProjectConfig to update the + // allowDuplicateEmails and usageMode settings and should instead use + // updateConfig to do so. + const updateMask = []; + if (reqBody.signIn?.allowDuplicateEmails != null) { + updateMask.push("signIn.allowDuplicateEmails"); + } + if (reqBody.usageMode) { + updateMask.push("usageMode"); } + ctx.params.query.updateMask = updateMask.join(); + + updateConfig(state, reqBody, ctx); return getEmulatorProjectConfig(state); } @@ -2007,6 +2000,30 @@ function mfaSignInFinalize( }; } +function updateConfig( + state: ProjectState, + reqBody: Schemas["GoogleCloudIdentitytoolkitAdminV2Config"], + ctx: ExegesisContext +): Schemas["GoogleCloudIdentitytoolkitAdminV2Config"] { + assert( + state instanceof AgentProjectState, + "((Can only update top-level configurations on agent projects.))" + ); + for (const event in reqBody.blockingFunctions?.triggers) { + if (Object.prototype.hasOwnProperty.call(reqBody.blockingFunctions!.triggers, event)) { + assert( + Object.values(BlockingFunctionEvents).includes(event as BlockingFunctionEvents), + "INVALID_BLOCKING_FUNCTION: ((Event type is invalid.))" + ); + assert( + parseAbsoluteUri(reqBody.blockingFunctions!.triggers[event].functionUri!), + "INVALID_BLOCKING_FUNCTION: ((Expected an absolute URI with valid scheme and host.))" + ); + } + } + return state.updateConfig(reqBody, ctx.params.query.updateMask); +} + export type AuthOperation = ( state: ProjectState, reqBody: object, diff --git a/src/emulator/auth/state.ts b/src/emulator/auth/state.ts index 194586c99e3..51b04751a37 100644 --- a/src/emulator/auth/state.ts +++ b/src/emulator/auth/state.ts @@ -4,6 +4,8 @@ import { mirrorFieldTo, randomDigits, isValidPhoneNumber, + DeepPartial, + parseAbsoluteUri, } from "./utils"; import { MakeRequired } from "./utils"; import { AuthCloudFunction } from "./cloudFunctions"; @@ -582,10 +584,13 @@ export abstract class ProjectState { } export class AgentProjectState extends ProjectState { - private _oneAccountPerEmail = true; - private _usageMode = UsageMode.DEFAULT; private tenantProjectForTenantId: Map = new Map(); private readonly _authCloudFunction = new AuthCloudFunction(this.projectId); + private _config: Config = { + signIn: { allowDuplicateEmails: false }, + usageMode: UsageMode.DEFAULT, + blockingFunctions: {}, + }; constructor(projectId: string) { super(projectId); @@ -596,19 +601,19 @@ export class AgentProjectState extends ProjectState { } get oneAccountPerEmail() { - return this._oneAccountPerEmail; + return !this._config.signIn.allowDuplicateEmails; } set oneAccountPerEmail(oneAccountPerEmail: boolean) { - this._oneAccountPerEmail = oneAccountPerEmail; + this._config.signIn.allowDuplicateEmails = !oneAccountPerEmail; } get usageMode() { - return this._usageMode; + return this._config.usageMode; } set usageMode(usageMode: UsageMode) { - this._usageMode = usageMode; + this._config.usageMode = usageMode; } get allowPasswordSignup() { @@ -631,6 +636,48 @@ export class AgentProjectState extends ProjectState { return true; } + get config() { + return this._config; + } + + get blockingFunctionsConfig() { + return this._config.blockingFunctions; + } + + set blockingFunctionsConfig(blockingFunctions: BlockingFunctionsConfig) { + this._config.blockingFunctions = blockingFunctions; + } + + // TODO(lisajian): Once v2 API discovery is updated, type of update should be + // changed to Schemas["GoogleCloudIdentitytoolkitAdminV2Config"] and validation + // of update.usageMode should be moved to operations.ts + updateConfig( + update: Schemas["GoogleCloudIdentitytoolkitAdminV2Config"] & { usageMode?: UsageMode }, + updateMask: string | undefined + ): Config { + if (update.usageMode) { + assert( + update.usageMode !== UsageMode.USAGE_MODE_UNSPECIFIED, + "INVALID_USAGE_MODE: ((Invalid usage mode provided.))" + ); + if (update.usageMode === UsageMode.PASSTHROUGH) { + assert( + this.getUserCount() === 0, + "USERS_STILL_EXIST: ((Users are present, unable to set passthrough mode.))" + ); + } + } + + // Empty masks indicate a full update. + if (!updateMask) { + this.oneAccountPerEmail = !update.signIn?.allowDuplicateEmails ?? true; + this.blockingFunctionsConfig = update.blockingFunctions ?? {}; + this.usageMode = update.usageMode ?? UsageMode.DEFAULT; + return this.config; + } + return applyMask(updateMask, this.config, update); + } + getTenantProject(tenantId: string): TenantProjectState { if (!this.tenantProjectForTenantId.has(tenantId)) { // Implicitly creates tenant if it does not already exist and sets all @@ -779,52 +826,7 @@ export class TenantProjectState extends ProjectState { return this.tenantConfig; } - const paths = updateMask.split(","); - for (const path of paths) { - const fields = path.split("."); - // Using `any` here to recurse over Tenant config objects - let updateField: any = update; - let existingField: any = this._tenantConfig; - let field; - for (let i = 0; i < fields.length - 1; i++) { - field = fields[i]; - - // Doesn't exist on update - if (updateField[field] == null) { - console.warn(`Unable to find field '${field}' in update '${updateField}`); - break; - } - - // Field on existing is an array or is a primitive (i.e. cannot index - // any further) - if ( - Array.isArray(updateField[field]) || - Object(updateField[field]) !== updateField[field] - ) { - console.warn(`Field '${field}' is singular and cannot have sub-fields`); - break; - } - - // Non-standard behavior, this creates new fields regardless of if the - // final field is set. Typical behavior would not modify the config - // payload if the final field is not successfully set. - if (!existingField[field]) { - existingField[field] = {}; - } - - updateField = updateField[field]; - existingField = existingField[field]; - } - // Reassign final field if possible - field = fields[fields.length - 1]; - if (updateField[field] == null) { - console.warn(`Unable to find field '${field}' in update '${JSON.stringify(updateField)}`); - continue; - } - existingField[field] = updateField[field]; - } - - return this.tenantConfig; + return applyMask(updateMask, this.tenantConfig, update); } } @@ -851,6 +853,24 @@ export type Tenant = Omit< "testPhoneNumbers" | "mfaConfig" > & { tenantId: string; mfaConfig: MfaConfig }; +export type SignInConfig = MakeRequired< + Schemas["GoogleCloudIdentitytoolkitAdminV2SignInConfig"], + "allowDuplicateEmails" +>; + +export type BlockingFunctionsConfig = + Schemas["GoogleCloudIdentitytoolkitAdminV2BlockingFunctionsConfig"]; + +// Serves as a substitute for Schemas["GoogleCloudIdentitytoolkitAdminV2Config"], +// i.e. the configuration object for top-level AgentProjectStates. Emulator +// fixes certain configurations for ease of use / testing, so as non-standard +// behavior, Config only stores the configurable fields. +export type Config = { + signIn: SignInConfig; + usageMode: UsageMode; + blockingFunctions: BlockingFunctionsConfig; +}; + interface RefreshTokenRecord { localId: string; provider: string; @@ -881,6 +901,19 @@ export interface PhoneVerificationRecord { sessionInfo: string; } +export enum UsageMode { + // Should never be used + USAGE_MODE_UNSPECIFIED = "USAGE_MODE_UNSPECIFIED", + + DEFAULT = "DEFAULT", + PASSTHROUGH = "PASSTHROUGH", +} + +export enum BlockingFunctionEvents { + BEFORE_CREATE = "beforeCreate", + BEFORE_SIGN_IN = "beforeSignIn", +} + interface TemporaryProofRecord { phoneNumber: string; temporaryProof: string; @@ -899,7 +932,57 @@ function getProviderEmailsForUser(user: UserInfo): Set { return emails; } -export enum UsageMode { - DEFAULT = "DEFAULT", - PASSTHROUGH = "PASSTHROUGH", +/** + * Updates fields based on specified update mask. Note that this is a no-op if + * the update mask is empty. + * + * @param updateMask a comma separated list of fully qualified names of fields + * @param dest the destination to apply updates to + * @param update the updates to apply + * @returns the updated destination object + */ +function applyMask(updateMask: string, dest: T, update: DeepPartial): T { + const paths = updateMask.split(","); + for (const path of paths) { + const fields = path.split("."); + // Using `any` here to recurse over destination objects + let updateField: any = update; + let existingField: any = dest; + let field; + for (let i = 0; i < fields.length - 1; i++) { + field = fields[i]; + + // Doesn't exist on update + if (updateField[field] == null) { + console.warn(`Unable to find field '${field}' in update '${updateField}`); + break; + } + + // Field on existing is an array or is a primitive (i.e. cannot index + // any further) + if (Array.isArray(updateField[field]) || Object(updateField[field]) !== updateField[field]) { + console.warn(`Field '${field}' is singular and cannot have sub-fields`); + break; + } + + // Non-standard behavior, this creates new fields regardless of if the + // final field is set. Typical behavior would not modify the config + // payload if the final field is not successfully set. + if (!existingField[field]) { + existingField[field] = {}; + } + + updateField = updateField[field]; + existingField = existingField[field]; + } + // Reassign final field if possible + field = fields[fields.length - 1]; + if (updateField[field] == null) { + console.warn(`Unable to find field '${field}' in update '${JSON.stringify(updateField)}`); + continue; + } + existingField[field] = updateField[field]; + } + + return dest; } diff --git a/src/emulator/auth/utils.ts b/src/emulator/auth/utils.ts index b86925f20ff..8e065571839 100644 --- a/src/emulator/auth/utils.ts +++ b/src/emulator/auth/utils.ts @@ -9,6 +9,13 @@ import { EmulatorLogger } from "../emulatorLogger"; */ export type MakeRequired = T & Required>; +/** + * Utility type to make all fields recursively optional. + */ +export type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; +}; + /** * Checks if email looks like a valid email address. * diff --git a/src/test/emulators/auth/config.spec.ts b/src/test/emulators/auth/config.spec.ts new file mode 100644 index 00000000000..490fd74e6eb --- /dev/null +++ b/src/test/emulators/auth/config.spec.ts @@ -0,0 +1,159 @@ +import { expect } from "chai"; +import { expectStatusCode, registerAnonUser } from "./helpers"; +import { describeAuthEmulator, PROJECT_ID } from "./setup"; + +describeAuthEmulator("config management", ({ authApi }) => { + describe("updateConfig", () => { + it("updates the project level config", async () => { + const updateMask = + "signIn.allowDuplicateEmails,blockingFunctions.forwardInboundCredentials.idToken,usageMode"; + + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .query({ updateMask }) + .send({ + signIn: { allowDuplicateEmails: true }, + blockingFunctions: { forwardInboundCredentials: { idToken: true } }, + usageMode: "PASSTHROUGH", + }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body.signIn?.allowDuplicateEmails).to.be.true; + expect(res.body.blockingFunctions).to.eql({ + forwardInboundCredentials: { idToken: true }, + }); + expect(res.body.usageMode).to.eql("PASSTHROUGH"); + }); + }); + + it("does not update if the field does not exist on the update config", async () => { + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .query({ updateMask: "displayName" }) + .send({}) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).not.to.have.property("displayName"); + }); + }); + + it("performs a full update if the update mask is empty", async () => { + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send({ + signIn: { allowDuplicateEmails: true }, + blockingFunctions: { forwardInboundCredentials: { idToken: true } }, + usageMode: "PASSTHROUGH", + }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body.signIn?.allowDuplicateEmails).to.be.true; + expect(res.body.blockingFunctions).to.eql({ + forwardInboundCredentials: { idToken: true }, + }); + expect(res.body.usageMode).to.eql("PASSTHROUGH"); + }); + }); + + it("performs a full update with production defaults if the update mask is empty", async () => { + // Update to non-default values + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send({ + signIn: { allowDuplicateEmails: true }, + blockingFunctions: { forwardInboundCredentials: { idToken: true } }, + usageMode: "PASSTHROUGH", + }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body.signIn?.allowDuplicateEmails).to.be.true; + expect(res.body.blockingFunctions).to.eql({ + forwardInboundCredentials: { idToken: true }, + }); + expect(res.body.usageMode).to.eql("PASSTHROUGH"); + }); + + // Perform a full update and check that production defaults are set + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send({}) + .then((res) => { + expectStatusCode(200, res); + expect(res.body.signIn?.allowDuplicateEmails).to.be.false; + expect(res.body.blockingFunctions).to.eql({}); + expect(res.body.usageMode).to.eql("DEFAULT"); + }); + }); + + it("should error on unspecified usageMode", async () => { + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send({ + usageMode: "USAGE_MODE_UNSPECIFIED", + }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error).to.have.property("message").contains("INVALID_USAGE_MODE"); + }); + }); + + it("should error when users are present for passthrough mode", async () => { + await registerAnonUser(authApi()); + + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send({ + usageMode: "PASSTHROUGH", + }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error).to.have.property("message").contains("USERS_STILL_EXIST"); + }); + }); + + it("should error when updating an invalid blocking function event", async () => { + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send({ + blockingFunctions: { + triggers: { + invalidEventTrigger: { + functionUri: "http://localhost", + }, + }, + }, + }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error).to.have.property("message").contains("INVALID_BLOCKING_FUNCTION"); + }); + }); + + it("should error if functionUri is invalid", async () => { + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send({ + blockingFunctions: { + triggers: { + beforeCreate: { + functionUri: "invalidUri", + }, + }, + }, + }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error).to.have.property("message").contains("INVALID_BLOCKING_FUNCTION"); + }); + }); + }); +}); diff --git a/src/test/emulators/auth/misc.spec.ts b/src/test/emulators/auth/misc.spec.ts index c0d69dd3f9d..73bd187ba55 100644 --- a/src/test/emulators/auth/misc.spec.ts +++ b/src/test/emulators/auth/misc.spec.ts @@ -523,7 +523,7 @@ describeAuthEmulator("emulator utility APIs", ({ authApi }) => { .send({ usageMode: "USAGE_MODE_UNSPECIFIED" }) .then((res) => { expectStatusCode(400, res); - expect(res.body.error).to.have.property("message").equals("Invalid usage mode provided"); + expect(res.body.error).to.have.property("message").contains("INVALID_USAGE_MODE"); }); }); @@ -547,9 +547,7 @@ describeAuthEmulator("emulator utility APIs", ({ authApi }) => { .send({ usageMode: "PASSTHROUGH" }) .then((res) => { expectStatusCode(400, res); - expect(res.body.error) - .to.have.property("message") - .equals("Users are present, unable to set passthrough mode"); + expect(res.body.error).to.have.property("message").contains("USERS_STILL_EXIST"); }); }); }); From c48fcf0010576b7cfdd4797cae5aa0c1ec9de7b3 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 24 Mar 2022 21:38:03 +0000 Subject: [PATCH 0194/1699] 10.5.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 83599146185..aa13687eacc 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.4.2", + "version": "10.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.4.2", + "version": "10.5.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 71514e3bf12..a0c22a48e2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.4.2", + "version": "10.5.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 4f116cb02d14aa2ae8f87a1daa61010b51cc9226 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 24 Mar 2022 21:38:26 +0000 Subject: [PATCH 0195/1699] [firebase-release] Removed change log and reset repo after 10.5.0 release --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d01fa979147..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +0,0 @@ -- Release Cloud Firestore emulator v1.14.1: - - Adds support of x-goog-request-params http header for routing. - - Changes `read-past-max-staleness` error code to align with production - implementation. - - Updates readtime-in-the-future error message. - - Supports importing exports from Windows on UNIX systems. (#2421) -- Marks Java 10 and below as deprecated. Support will be dropped in Firebase CLI v11. Please upgrade to Java version 11 or above to continue using the emulators. (#4347) From 6f64cc09bc419cdc220b683063293549ac899f27 Mon Sep 17 00:00:00 2001 From: abhis3 Date: Thu, 24 Mar 2022 16:16:41 -0700 Subject: [PATCH 0196/1699] Set custom metadata correctly for resumable uploads (#4278) * Set custom metadata correctly for resumable uploads --- CHANGELOG.md | 2 + scripts/storage-emulator-integration/tests.ts | 74 ++++++++++++++++++- src/emulator/storage/apis/firebase.ts | 12 +-- src/emulator/storage/files.ts | 4 +- src/emulator/storage/metadata.ts | 28 +++++-- 5 files changed, 106 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..9ba25526e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Fixes bug where resumable uploads were not setting custom metadata on upload (#3398). +- Fixes bug where GCS metadataUpdate cloud functions were triggered in incorrect situations (#3398). diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index efd5333e71d..329f79712fc 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -187,6 +187,7 @@ describe("Storage emulator", () => { bucket: "string", cacheControl: "string", contentDisposition: "string", + contentEncoding: "string", generation: "string", metageneration: "string", contentType: "string", @@ -664,6 +665,7 @@ describe("Storage emulator", () => { bucket: "string", contentType: "string", contentDisposition: "string", + contentEncoding: "string", generation: "string", md5Hash: "string", crc32c: "string", @@ -884,10 +886,13 @@ describe("Storage emulator", () => { } } + expect(metadata.name).to.equal("small_file"); + expect(metadata.contentType).to.equal("application/octet-stream"); expect(metadataTypes).to.deep.equal({ bucket: "string", - contentType: "string", contentDisposition: "string", + contentEncoding: "string", + contentType: "string", generation: "string", md5Hash: "string", crc32c: "string", @@ -907,6 +912,34 @@ describe("Storage emulator", () => { }); }); + it("should return generated custom metadata for new upload", async () => { + const customMetadata = { + contentDisposition: "initialCommit", + contentType: "image/jpg", + name: "test_upload.jpg", + }; + + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post( + `/upload/storage/v1/b/${storageBucket}/o?name=test_upload.jpg&uploadType=resumable` + ) + .send(customMetadata) + .set({ + Authorization: "Bearer owner", + }) + .expect(200) + .then((res) => new URL(res.header["location"])); + + const returnedMetadata = await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .expect(200) + .then((res) => res.body); + + expect(returnedMetadata.name).to.equal(customMetadata.name); + expect(returnedMetadata.contentType).to.equal(customMetadata.contentType); + expect(returnedMetadata.contentDisposition).to.equal(customMetadata.contentDisposition); + }); + it("should return a functional media link", async () => { await testBucket.upload(smallFilePath); const [{ mediaLink }] = await testBucket @@ -1002,8 +1035,9 @@ describe("Storage emulator", () => { expect(metadata.contentType).to.equal("very/fake"); expect(metadataTypes).to.deep.equal({ bucket: "string", - contentType: "string", contentDisposition: "string", + contentEncoding: "string", + contentType: "string", generation: "string", md5Hash: "string", crc32c: "string", @@ -1107,6 +1141,9 @@ describe("Storage emulator", () => { }); }); + /** + * TODO(abhisun): Add test coverage to validate how many times various cloud functions are triggered. + */ describe("Firebase Endpoints", () => { let storage: Storage; let browser: puppeteer.Browser; @@ -1290,6 +1327,39 @@ describe("Storage emulator", () => { expect(uploadState).to.equal("success"); }); + it("should set custom metadata on resumable uploads", async () => { + const customMetadata = { + contentDisposition: "initialCommit", + contentType: "image/jpg", + name: "test_upload.jpg", + }; + + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post( + `/v0/b/${storageBucket}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg` + ) + .send(customMetadata) + .set({ + Authorization: "Bearer owner", + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + const returnedMetadata = await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload, finalize", + }) + .expect(200) + .then((res) => res.body); + expect(returnedMetadata.name).to.equal(customMetadata.name); + expect(returnedMetadata.contentType).to.equal(customMetadata.contentType); + expect(returnedMetadata.contentDisposition).to.equal(customMetadata.contentDisposition); + }); + it("should return a 403 on rules deny", async () => { const uploadState = await page.evaluate(async (IMAGE_FILE_BASE64) => { const _file = new File([IMAGE_FILE_BASE64], "toUpload.txt"); diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 4b33f3c03a7..ca37ecc22b0 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -232,7 +232,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } throw err; } - metadata.addDownloadToken(); + metadata.addDownloadToken(/* shouldTrigger = */ false); return res.status(200).json(new OutgoingFirebaseMetadata(metadata)); } @@ -330,9 +330,10 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } throw err; } - let metadata: StoredFileMetadata; + + let storedMetadata: StoredFileMetadata; try { - metadata = await storageLayer.uploadObject(upload); + storedMetadata = await storageLayer.uploadObject(upload); } catch (err) { if (err instanceof ForbiddenError) { return res.status(403).json({ @@ -344,8 +345,9 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } throw err; } - metadata.addDownloadToken(); - return res.status(200).json(new OutgoingFirebaseMetadata(metadata)); + + storedMetadata.addDownloadToken(/* shouldTrigger = */ false); + return res.status(200).json(new OutgoingFirebaseMetadata(storedMetadata)); } // Unsupported upload command. diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index c7d500889d7..95e5dd24b98 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -287,7 +287,7 @@ export class StorageLayer { /** * Last step in uploading a file. Validates the request and persists the staging - * object to its permanent location on disk. + * object to its permanent location on disk, updates metadata. */ public async uploadObject(upload: Upload): Promise { if (upload.status !== UploadStatus.FINISHED) { @@ -309,6 +309,8 @@ export class StorageLayer { this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size) ); + metadata.update(upload.metadata, /* shouldTrigger = */ false); + const authorized = await this._rulesValidator.validate( ["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, diff --git a/src/emulator/storage/metadata.ts b/src/emulator/storage/metadata.ts index a5c607af79c..99446b11fc1 100644 --- a/src/emulator/storage/metadata.ts +++ b/src/emulator/storage/metadata.ts @@ -90,7 +90,7 @@ export class StoredFileMetadata { } if (incomingMetadata) { - this.update(incomingMetadata); + this.update(incomingMetadata, /* shouldTrigger = */ false); } this.deleteFieldsSetAsNull(); @@ -175,7 +175,11 @@ export class StoredFileMetadata { } } - update(incoming: IncomingMetadata): void { + /** + * TODO(abhisun): Move all cloud function triggers to the storage layer to + * avoid needing the shouldTrigger field + */ + update(incoming: IncomingMetadata, shouldTrigger = true): void { if (incoming.contentDisposition) { this.contentDisposition = incoming.contentDisposition; } @@ -213,17 +217,19 @@ export class StoredFileMetadata { this.setDownloadTokensFromCustomMetadata(); this.deleteFieldsSetAsNull(); - this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this)); + if (shouldTrigger) { + this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this)); + } } - addDownloadToken(): void { + addDownloadToken(shouldTrigger = true): void { if (!this.downloadTokens.length) { this.downloadTokens.push(uuid.v4()); return; } this.downloadTokens = [...this.downloadTokens, uuid.v4()]; - this.update({}); + this.update({}, shouldTrigger); } deleteDownloadToken(token: string): void { @@ -235,7 +241,8 @@ export class StoredFileMetadata { this.downloadTokens = remainingTokens; if (remainingTokens.length === 0) { // if empty after deleting, always add a new token. - this.addDownloadToken(); + // shouldTrigger is false as it's taken care of in the subsequent update + this.addDownloadToken(/* shouldTrigger = */ false); } this.update({}); } @@ -392,6 +399,7 @@ export class CloudStorageObjectMetadata { contentLanguage?: string; contentDisposition: string; cacheControl?: string; + contentEncoding?: string; customTime?: string; id: string; timeStorageClassUpdated: string; @@ -439,6 +447,14 @@ export class CloudStorageObjectMetadata { this.cacheControl = metadata.cacheControl; } + if (metadata.contentDisposition) { + this.contentDisposition = metadata.contentDisposition; + } + + if (metadata.contentEncoding) { + this.contentEncoding = metadata.contentEncoding; + } + if (metadata.customTime) { this.customTime = toSerializedDate(metadata.customTime); } From 529c3cdd1040f4d79c8bfcc23ed1b45b5c47ba1c Mon Sep 17 00:00:00 2001 From: Jim Boswell Date: Fri, 25 Mar 2022 11:42:30 -0700 Subject: [PATCH 0197/1699] Cloud Functions: Fix env var escaping (#4271) Fixes #4270 and a little bit more. The bug results from the use of `String.prototype.replace` with a first (search) argument of type `string`. This is an unintuitive corner of JavaScript that only replaces the *first* occurrence of the search string. [See MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace). To replace all occurrences, either the search argument must be expressed as an equivalent regex with the `/g` flag, or [.replaceAll](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll) must be used. .replaceAll isn't supported below Node 15, and given this package's dependency on Node >= 12, the regex approach has been used here. Quick demonstration: ``` Welcome to Node.js v16.13.2. Type ".help" for more information. > const aaaa = "aaaa"; undefined > aaaa.replace("a", "b") // string argument; replaces only the first occurrence 'baaa' > aaaa.replace(/a/, "b") // regex argument *without* /g flag; replaces only the first occurrence 'baaa' > aaaa.replace(/a/g, "b") // regex argument *with* /g flag; replaces all occurrences 'bbbb' > aaaa.replaceAll("a", "b") // replaceAll method in Node >= 15; replaces all occurrences 'bbbb' ``` There's another reason to use a regex approach - running the string through repeated invocations of .replace(All) processes the escapes in an implementation-defined order, whereas the string really should be scanned from start to end in order to correctly convert (for example) `\\n` to `\` `n`. The popular npm package [dotenv](https://www.npmjs.com/package/dotenv) gets tripped up by this as well. The changes outside `.env` files result from manually searching the repo for uses of `String.prototype.replace` having a non-regex first argument. There are more occurrences than the ones fixed here that probably should be audited, but 1. Not all instances are immediately obvious to me whether they intend to replace *all* occurrences of the search argument 2. I wanted to keep this PR focused. --- CHANGELOG.md | 1 + src/functions/env.ts | 17 +++++++-- src/functions/runtimeConfigExport.ts | 19 ++++++---- src/functions/secrets.ts | 3 +- src/test/functions/env.spec.ts | 37 +++++++++++++++++++ .../functions/runtimeConfigExport.spec.ts | 4 +- src/test/functions/secrets.spec.ts | 15 +++++--- 7 files changed, 76 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba25526e5c..225fddc8a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Fixes bug where resumable uploads were not setting custom metadata on upload (#3398). - Fixes bug where GCS metadataUpdate cloud functions were triggered in incorrect situations (#3398). +- Fixes bug where quoted escape sequences in .env files were incompletely unescaped. (#4270) diff --git a/src/functions/env.ts b/src/functions/env.ts index 8cee9d6f450..dcff7a67242 100644 --- a/src/functions/env.ts +++ b/src/functions/env.ts @@ -58,6 +58,16 @@ const LINE_RE = new RegExp( "gms" // flags: global, multiline, dotall ); +const ESCAPE_SEQUENCES_TO_CHARACTERS: Record = { + "\\n": "\n", + "\\r": "\r", + "\\t": "\t", + "\\v": "\v", + "\\\\": "\\", + "\\'": "'", + '\\"': '"', +}; + interface ParseResult { envs: Record; errors: string[]; @@ -104,10 +114,9 @@ export function parse(data: string): ParseResult { // Remove surrounding single/double quotes. v = quotesMatch[2]; if (quotesMatch[1] === '"') { - // Unescape newlines and tabs. - v = v.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\v", "\v"); - // Unescape other escapable characters. - v = v.replace(/\\([\\'"])/g, "$1"); + // Substitute escape sequences. The regex passed to replace() must + // match every key in ESCAPE_SEQUENCES_TO_CHARACTERS. + v = v.replace(/\\[nrtv\\'"]/g, (match) => ESCAPE_SEQUENCES_TO_CHARACTERS[match]); } } diff --git a/src/functions/runtimeConfigExport.ts b/src/functions/runtimeConfigExport.ts index 0c135ea3a9c..6b9f0e74cc6 100644 --- a/src/functions/runtimeConfigExport.ts +++ b/src/functions/runtimeConfigExport.ts @@ -169,14 +169,19 @@ export function hydrateEnvs(pInfos: ProjectConfigInfo[], prefix: string): string return errMsg; } +const CHARACTERS_TO_ESCAPE_SEQUENCES: Record = { + "\n": "\\n", + "\r": "\\r", + "\t": "\\t", + "\v": "\\v", + "\\": "\\\\", + '"': '\\"', + "'": "\\'", +}; + function escape(s: string): string { - // Escape newlines and tabs - const result = s - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t") - .replace("\v", "\\v"); - return result.replace(/(['"])/g, "\\$1"); + // Escape newlines, tabs, backslashes and quotes + return s.replace(/[\n\r\t\v\\"']/g, (ch) => CHARACTERS_TO_ESCAPE_SEQUENCES[ch]); } /** diff --git a/src/functions/secrets.ts b/src/functions/secrets.ts index 926eca36606..2bb289745c7 100644 --- a/src/functions/secrets.ts +++ b/src/functions/secrets.ts @@ -34,8 +34,7 @@ export function labels(): Record { function toUpperSnakeCase(key: string): string { return key - .replace("-", "_") - .replace(".", "_") + .replace(/[.-]/g, "_") .replace(/([a-z])([A-Z])/g, "$1_$2") .toUpperCase(); } diff --git a/src/test/functions/env.spec.ts b/src/test/functions/env.spec.ts index 2668b5ed3c4..49a95b71878 100644 --- a/src/test/functions/env.spec.ts +++ b/src/test/functions/env.spec.ts @@ -53,6 +53,43 @@ BAR=bar input: 'FOO="foo1\\nfoo2"\nBAR=bar', want: { FOO: "foo1\nfoo2", BAR: "bar" }, }, + { + description: "should parse escape sequences in order, from start to end", + input: `BAZ=baz +ONE_NEWLINE="foo1\\nfoo2" +ONE_BSLASH_AND_N="foo3\\\\nfoo4" +ONE_BSLASH_AND_NEWLINE="foo5\\\\\\nfoo6" +TWO_BSLASHES_AND_N="foo7\\\\\\\\nfoo8" +BAR=bar`, + want: { + BAZ: "baz", + ONE_NEWLINE: "foo1\nfoo2", + ONE_BSLASH_AND_N: "foo3\\nfoo4", + ONE_BSLASH_AND_NEWLINE: "foo5\\\nfoo6", + TWO_BSLASHES_AND_N: "foo7\\\\nfoo8", + BAR: "bar", + }, + }, + { + description: "should parse double quoted with multiple escaped newlines", + input: 'FOO="foo1\\nfoo2\\nfoo3"\nBAR=bar', + want: { FOO: "foo1\nfoo2\nfoo3", BAR: "bar" }, + }, + { + description: "should parse double quoted with multiple escaped horizontal tabs", + input: 'FOO="foo1\\tfoo2\\tfoo3"\nBAR=bar', + want: { FOO: "foo1\tfoo2\tfoo3", BAR: "bar" }, + }, + { + description: "should parse double quoted with multiple escaped vertical tabs", + input: 'FOO="foo1\\vfoo2\\vfoo3"\nBAR=bar', + want: { FOO: "foo1\vfoo2\vfoo3", BAR: "bar" }, + }, + { + description: "should parse double quoted with multiple escaped carriage returns", + input: 'FOO="foo1\\rfoo2\\rfoo3"\nBAR=bar', + want: { FOO: "foo1\rfoo2\rfoo3", BAR: "bar" }, + }, { description: "should leave single quotes when double quoted", input: `FOO="'foo'"`, diff --git a/src/test/functions/runtimeConfigExport.spec.ts b/src/test/functions/runtimeConfigExport.spec.ts index 34e42d5146a..cb7338e4cc5 100644 --- a/src/test/functions/runtimeConfigExport.spec.ts +++ b/src/test/functions/runtimeConfigExport.spec.ts @@ -133,11 +133,11 @@ describe("functions-config-export", () => { it("should preserve newline characters", () => { const dotenv = configExport.toDotenvFormat([ - { origKey: "service.api.url", newKey: "SERVICE_API_URL", value: "hello\nworld" }, + { origKey: "service.api.url", newKey: "SERVICE_API_URL", value: "hello\nthere\nworld" }, ]); const { envs, errors } = env.parse(dotenv); expect(envs).to.be.deep.equal({ - SERVICE_API_URL: "hello\nworld", + SERVICE_API_URL: "hello\nthere\nworld", }); expect(errors).to.be.empty; }); diff --git a/src/test/functions/secrets.spec.ts b/src/test/functions/secrets.spec.ts index 152b9de31ca..74dd9adfe8c 100644 --- a/src/test/functions/secrets.spec.ts +++ b/src/test/functions/secrets.spec.ts @@ -27,22 +27,27 @@ describe("functions/secret", () => { }); it("returns the original key if it follows convention", async () => { - expect(await secrets.ensureValidKey("MY_KEY", options)).to.equal("MY_KEY"); + expect(await secrets.ensureValidKey("MY_SECRET_KEY", options)).to.equal("MY_SECRET_KEY"); expect(warnStub).to.not.have.been.called; }); - it("returns the transformed key (with warning) if with dashses", async () => { - expect(await secrets.ensureValidKey("MY-KEY", options)).to.equal("MY_KEY"); + it("returns the transformed key (with warning) if with dashes", async () => { + expect(await secrets.ensureValidKey("MY-SECRET-KEY", options)).to.equal("MY_SECRET_KEY"); + expect(warnStub).to.have.been.calledOnce; + }); + + it("returns the transformed key (with warning) if with periods", async () => { + expect(await secrets.ensureValidKey("MY.SECRET.KEY", options)).to.equal("MY_SECRET_KEY"); expect(warnStub).to.have.been.calledOnce; }); it("returns the transformed key (with warning) if with lower cases", async () => { - expect(await secrets.ensureValidKey("my_key", options)).to.equal("MY_KEY"); + expect(await secrets.ensureValidKey("my_secret_key", options)).to.equal("MY_SECRET_KEY"); expect(warnStub).to.have.been.calledOnce; }); it("returns the transformed key (with warning) if camelCased", async () => { - expect(await secrets.ensureValidKey("myKey", options)).to.equal("MY_KEY"); + expect(await secrets.ensureValidKey("mySecretKey", options)).to.equal("MY_SECRET_KEY"); expect(warnStub).to.have.been.calledOnce; }); From bf94d9e1381b4246e950447e215662316f759275 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 28 Mar 2022 15:49:18 -0700 Subject: [PATCH 0198/1699] Remove maxBurstSize since it's output only (#4225) --- src/deploy/functions/backend.ts | 1 - src/deploy/functions/runtimes/discovery/v1alpha1.ts | 1 - src/deploy/functions/runtimes/node/parseTriggers.ts | 1 - src/gcp/cloudtasks.ts | 3 --- src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts | 2 -- src/test/gcp/cloudtasks.spec.ts | 3 +-- 6 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 56e9a4cca3a..264b018daf3 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -106,7 +106,6 @@ export interface EventTriggered { } export interface TaskQueueRateLimits { - maxBurstSize?: number; maxConcurrentDispatches?: number; maxDispatchesPerSecond?: number; } diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 0ae3d0de8d0..f4171016357 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -164,7 +164,6 @@ function parseEndpoints( }); if (ep.taskQueueTrigger.rateLimits) { assertKeyTypes(prefix + ".taskQueueTrigger.rateLimits", ep.taskQueueTrigger.rateLimits, { - maxBurstSize: "number", maxConcurrentDispatches: "number", maxDispatchesPerSecond: "number", }); diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 5921f05155c..711d9e01f1e 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -56,7 +56,6 @@ export interface TriggerAnnotation { }; taskQueueTrigger?: { rateLimits?: { - maxBurstSize?: number; maxConcurrentDispatches?: number; maxDispatchesPerSecond?: number; }; diff --git a/src/gcp/cloudtasks.ts b/src/gcp/cloudtasks.ts index 67cb0dc07a4..bcabb2f7504 100644 --- a/src/gcp/cloudtasks.ts +++ b/src/gcp/cloudtasks.ts @@ -22,7 +22,6 @@ export interface AppEngineRouting { export interface RateLimits { maxDispatchesPerSecond?: number; - maxBurstSize?: number; maxConcurrentDispatches?: number; } @@ -69,7 +68,6 @@ export interface Queue { export const DEFAULT_SETTINGS: Omit = { rateLimits: { maxConcurrentDispatches: 1000, - maxBurstSize: 100, maxDispatchesPerSecond: 500, }, state: "RUNNING", @@ -217,7 +215,6 @@ export function queueFromEndpoint(endpoint: backend.Endpoint & backend.TaskQueue proto.copyIfPresent( queue.rateLimits, endpoint.taskQueueTrigger.rateLimits, - "maxBurstSize", "maxConcurrentDispatches", "maxDispatchesPerSecond" ); diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 6e881e96370..9a26612461c 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -211,7 +211,6 @@ describe("backendFromV1Alpha1", () => { describe("taskQueueTriggers", () => { const validTrigger: backend.TaskQueueTrigger = { rateLimits: { - maxBurstSize: 5, maxConcurrentDispatches: 10, maxDispatchesPerSecond: 20, }, @@ -226,7 +225,6 @@ describe("backendFromV1Alpha1", () => { }; const invalidRateLimits = { - maxBurstSize: "5", maxConcurrentDispatches: "10", maxDispatchesPerSecond: "20", }; diff --git a/src/test/gcp/cloudtasks.spec.ts b/src/test/gcp/cloudtasks.spec.ts index 81cf4a8fd33..16567e208df 100644 --- a/src/test/gcp/cloudtasks.spec.ts +++ b/src/test/gcp/cloudtasks.spec.ts @@ -39,7 +39,6 @@ describe("CloudTasks", () => { it("handles complex endpoints", () => { const rateLimits: backend.TaskQueueRateLimits = { - maxBurstSize: 100, maxConcurrentDispatches: 5, maxDispatchesPerSecond: 5, }; @@ -88,7 +87,7 @@ describe("CloudTasks", () => { name: "projects/p/locations/r/queues/f", ...cloudtasks.DEFAULT_SETTINGS, rateLimits: { - maxBurstSize: 9_000, + maxConcurrentDispatches: 20, }, }; const haveQueue: cloudtasks.Queue = { From 366aece075fc954581dd3b46b64ef538de16cb20 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 29 Mar 2022 12:18:18 -0700 Subject: [PATCH 0199/1699] Start tracking v2 function deploys as Firebase functions (#4369) --- src/deploy/functions/release/fabricator.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index ac7b84e8677..7abe33d1f4f 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -168,10 +168,7 @@ export class Fabricator { } async updateEndpoint(update: planner.EndpointUpdate, scraper: SourceTokenScraper): Promise { - // GCF team wants us to stop setting the deployment-tool labels on updates for gen 2 - if (update.deleteAndRecreate || update.endpoint.platform !== "gcfv2") { - update.endpoint.labels = { ...update.endpoint.labels, ...deploymentTool.labels() }; - } + update.endpoint.labels = { ...update.endpoint.labels, ...deploymentTool.labels() }; if (update.deleteAndRecreate) { await this.deleteEndpoint(update.deleteAndRecreate); await this.createEndpoint(update.endpoint, scraper); From 7757ea1ddb70b0e6c4c36ee8a3e44b070d3d335e Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 30 Mar 2022 11:51:46 -0400 Subject: [PATCH 0200/1699] Update /internal/setRules endpoint; fix ruleset file watcher (#4337) * Update internal/setRules endpoint to create new storage manager with updated ruleset; fix file watcher * Address PR feedback * Add CHANGELOG entry --- CHANGELOG.md | 1 + .../rules/manager.test.ts | 68 +++------ scripts/storage-emulator-integration/tests.ts | 141 +++++++++++++++++- src/emulator/storage/index.ts | 16 +- src/emulator/storage/rules/manager.ts | 35 +---- src/emulator/storage/server.ts | 107 +++++++++---- 6 files changed, 265 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 225fddc8a53..9f825343160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Fixes bug where resumable uploads were not setting custom metadata on upload (#3398). - Fixes bug where GCS metadataUpdate cloud functions were triggered in incorrect situations (#3398). - Fixes bug where quoted escape sequences in .env files were incompletely unescaped. (#4270) +- Fixes Storage Emulator ruleset file watcher (#4337). diff --git a/scripts/storage-emulator-integration/rules/manager.test.ts b/scripts/storage-emulator-integration/rules/manager.test.ts index d2afe88ef3f..9a5fa6e5467 100644 --- a/scripts/storage-emulator-integration/rules/manager.test.ts +++ b/scripts/storage-emulator-integration/rules/manager.test.ts @@ -15,13 +15,11 @@ import { RulesetOperationMethod, SourceFile } from "../../../src/emulator/storag import { isPermitted } from "../../../src/emulator/storage/rules/utils"; import { readFile } from "../../../src/fsutils"; +const EMULATOR_LOAD_RULESET_DELAY_MS = 2000; + describe("Storage Rules Manager", function () { const rulesRuntime = new StorageRulesRuntime(); - const rules = [ - { resource: "bucket_0", rules: StorageRulesFiles.readWriteIfTrue }, - { resource: "bucket_1", rules: StorageRulesFiles.readWriteIfAuth }, - ]; - const opts = { method: RulesetOperationMethod.GET, file: {}, path: "/b/bucket_2/o/" }; + const opts = { method: RulesetOperationMethod.GET, file: {}, path: "/b/bucket_0/o/" }; let rulesManager: StorageRulesManager; // eslint-disable-next-line @typescript-eslint/no-invalid-this @@ -29,9 +27,6 @@ describe("Storage Rules Manager", function () { beforeEach(async () => { await rulesRuntime.start(); - - rulesManager = createStorageRulesManager(rules, rulesRuntime); - await rulesManager.start(); }); afterEach(async () => { @@ -40,47 +35,28 @@ describe("Storage Rules Manager", function () { }); it("should load multiple rulesets on start", async () => { + const rules = [ + { resource: "bucket_0", rules: StorageRulesFiles.readWriteIfTrue }, + { resource: "bucket_1", rules: StorageRulesFiles.readWriteIfAuth }, + ]; + rulesManager = createStorageRulesManager(rules, rulesRuntime); + await rulesManager.start(); + const bucket0Ruleset = rulesManager.getRuleset("bucket_0"); - expect(await isPermitted({ ...opts, ruleset: bucket0Ruleset! })).to.be.true; + expect(await isPermitted({ ...opts, path: "/b/bucket_0/o/", ruleset: bucket0Ruleset! })).to.be + .true; const bucket1Ruleset = rulesManager.getRuleset("bucket_1"); - expect(await isPermitted({ ...opts, ruleset: bucket1Ruleset! })).to.be.false; + expect(await isPermitted({ ...opts, path: "/b/bucket_1/o/", ruleset: bucket1Ruleset! })).to.be + .false; }); it("should load single ruleset on start", async () => { - const otherRulesManager = createStorageRulesManager( - StorageRulesFiles.readWriteIfTrue, - rulesRuntime - ); - await otherRulesManager.start(); + rulesManager = createStorageRulesManager(StorageRulesFiles.readWriteIfTrue, rulesRuntime); + await rulesManager.start(); - const ruleset = otherRulesManager.getRuleset("default"); + const ruleset = rulesManager.getRuleset("bucket"); expect(await isPermitted({ ...opts, ruleset: ruleset! })).to.be.true; - - await otherRulesManager.stop(); - }); - - it("should load ruleset on update with SourceFile object", async () => { - expect(rulesManager.getRuleset("bucket_2")).to.be.undefined; - await rulesManager.updateSourceFile(StorageRulesFiles.readWriteIfTrue, "bucket_2"); - expect(rulesManager.getRuleset("bucket_2")).not.to.be.undefined; - }); - - it("should set source file", async () => { - await rulesManager.updateSourceFile(StorageRulesFiles.readWriteIfTrue, "bucket_2"); - - expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket_2")! })).to.be - .true; - - const issues = await rulesManager.updateSourceFile( - StorageRulesFiles.readWriteIfAuth, - "bucket_2" - ); - - expect(issues.errors.length).to.equal(0); - expect(issues.warnings.length).to.equal(0); - expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket_2")! })).to.be - .false; }); it("should reload ruleset on changes to source file", async () => { @@ -91,15 +67,17 @@ describe("Storage Rules Manager", function () { persistence.appendBytes(fileName, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); const sourceFile = getSourceFile(testDir, fileName); - await rulesManager.updateSourceFile(sourceFile, "bucket_2"); - expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket_2")! })).to.be - .true; + rulesManager = createStorageRulesManager(sourceFile, rulesRuntime); + await rulesManager.start(); + + expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket")! })).to.be.true; // Write new rules to file persistence.deleteFile(fileName); persistence.appendBytes(fileName, Buffer.from(StorageRulesFiles.readWriteIfAuth.content)); - expect(await isPermitted(opts)).to.be.false; + await new Promise((resolve) => setTimeout(resolve, EMULATOR_LOAD_RULESET_DELAY_MS)); + expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket")! })).to.be.false; }); }); diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index 329f79712fc..d4b24f8e0f9 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -9,7 +9,7 @@ import * as puppeteer from "puppeteer"; import { Bucket, Storage, CopyOptions } from "@google-cloud/storage"; import supertest = require("supertest"); -import { IMAGE_FILE_BASE64 } from "../../src/test/emulators/fixtures"; +import { IMAGE_FILE_BASE64, StorageRulesFiles } from "../../src/test/emulators/fixtures"; import { TriggerEndToEndTest } from "../integration-helpers/framework"; import { createRandomFile, @@ -1141,6 +1141,145 @@ describe("Storage emulator", () => { }); }); + emulatorSpecificDescribe("Internal Endpoints", () => { + before(async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, emulatorConfig); + await test.startEmulators(["--only", "storage"]); + }); + + after(async () => { + await test.stopEmulators(); + }); + + describe("setRules", () => { + it("should set single ruleset", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [StorageRulesFiles.readWriteIfTrue], + }, + }) + .expect(200); + }); + + it("should set multiple rules/resource objects", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [ + { resource: "bucket_0", ...StorageRulesFiles.readWriteIfTrue }, + { resource: "bucket_1", ...StorageRulesFiles.readWriteIfAuth }, + ], + }, + }) + .expect(200); + }); + + it("should overwrite single ruleset with multiple rules/resource objects", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [StorageRulesFiles.readWriteIfTrue], + }, + }) + .expect(200); + + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [ + { resource: "bucket_0", ...StorageRulesFiles.readWriteIfTrue }, + { resource: "bucket_1", ...StorageRulesFiles.readWriteIfAuth }, + ], + }, + }) + .expect(200); + }); + + it("should return 400 if rules.files array is missing", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ rules: {} }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal("Request body must include 'rules.files' array"); + }); + + it("should return 400 if rules.files array has missing name field", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [{ content: StorageRulesFiles.readWriteIfTrue.content }], + }, + }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal( + "Each member of 'rules.files' array must contain 'name' and 'content'" + ); + }); + + it("should return 400 if rules.files array has missing content field", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [{ name: StorageRulesFiles.readWriteIfTrue.name }], + }, + }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal( + "Each member of 'rules.files' array must contain 'name' and 'content'" + ); + }); + + it("should return 400 if rules.files array has missing resource field", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [ + { resource: "bucket_0", ...StorageRulesFiles.readWriteIfTrue }, + StorageRulesFiles.readWriteIfAuth, + ], + }, + }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal( + "Each member of 'rules.files' array must contain 'name', 'content', and 'resource'" + ); + }); + + it("should return 400 if rules.files array has invalid content", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [{ name: StorageRulesFiles.readWriteIfTrue.name, content: "foo" }], + }, + }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal( + "There was an error updating rules, see logs for more details" + ); + }); + }); + }); + /** * TODO(abhisun): Add test coverage to validate how many times various cloud functions are triggered. */ diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index f86f002a9a0..46d836e144d 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -6,7 +6,7 @@ import { createApp } from "./server"; import { StorageLayer, StoredFile } from "./files"; import { EmulatorLogger } from "../emulatorLogger"; import { createStorageRulesManager, StorageRulesManager } from "./rules/manager"; -import { StorageRulesRuntime } from "./rules/runtime"; +import { StorageRulesIssues, StorageRulesRuntime } from "./rules/runtime"; import { SourceFile } from "./rules/types"; import express = require("express"); import { @@ -42,7 +42,7 @@ export class StorageEmulator implements EmulatorInstance { private _logger = EmulatorLogger.forEmulator(Emulators.STORAGE); private _rulesRuntime: StorageRulesRuntime; - private _rulesManager: StorageRulesManager; + private _rulesManager!: StorageRulesManager; private _files: Map = new Map(); private _buckets: Map = new Map(); private _cloudFunctions: StorageCloudFunctions; @@ -54,7 +54,7 @@ export class StorageEmulator implements EmulatorInstance { constructor(private args: StorageEmulatorArgs) { this._rulesRuntime = new StorageRulesRuntime(); - this._rulesManager = createStorageRulesManager(this.args.rules, this._rulesRuntime); + this._rulesManager = this.createRulesManager(this.args.rules); this._cloudFunctions = new StorageCloudFunctions(args.projectId); this._persistence = new Persistence(this.getPersistenceTmpDir()); this._uploadService = new UploadService(this._persistence); @@ -141,6 +141,16 @@ export class StorageEmulator implements EmulatorInstance { return this._app!; } + async replaceRules(rules: SourceFile | RulesConfig[]): Promise { + await this._rulesManager.stop(); + this._rulesManager = this.createRulesManager(rules); + return this._rulesManager.start(); + } + + private createRulesManager(rules: SourceFile | RulesConfig[]): StorageRulesManager { + return createStorageRulesManager(rules, this._rulesRuntime); + } + private getPersistenceTmpDir(): string { return `${tmpdir()}/firebase/storage/blobs`; } diff --git a/src/emulator/storage/rules/manager.ts b/src/emulator/storage/rules/manager.ts index ddceebe475a..f8cb05b814a 100644 --- a/src/emulator/storage/rules/manager.ts +++ b/src/emulator/storage/rules/manager.ts @@ -4,6 +4,7 @@ import { Emulators } from "../../types"; import { SourceFile } from "./types"; import { StorageRulesIssues, StorageRulesRuntime, StorageRulesetInstance } from "./runtime"; import { RulesConfig } from ".."; +import { readFile } from "../../../fsutils"; /** * Keeps track of rules source file(s) and generated ruleset(s), either one for all storage @@ -14,7 +15,6 @@ import { RulesConfig } from ".."; * ``` * const rulesManager = createStorageRulesManager(initialRules); * rulesManager.start(); - * rulesManager.updateSourceFile(newRules); * rulesManager.stop(); * ``` */ @@ -28,12 +28,6 @@ export interface StorageRulesManager { */ getRuleset(resource: string): StorageRulesetInstance | undefined; - /** - * Updates the source file and, correspondingly, the file watcher and ruleset for the resource. - * Returns an array of errors and/or warnings that arise from loading the ruleset. - */ - updateSourceFile(rules: SourceFile, resource: string): Promise; - /** Removes listeners from all files for all managed resources. */ stop(): Promise; } @@ -65,31 +59,21 @@ class DefaultStorageRulesManager implements StorageRulesManager { this._rules = _rules; } - start(): Promise { - return this.updateSourceFile(this._rules); + async start(): Promise { + const issues = await this.loadRuleset(); + this.updateWatcher(this._rules.name); + return issues; } getRuleset(): StorageRulesetInstance | undefined { return this._ruleset; } - async updateSourceFile(rules: SourceFile): Promise { - const prevRulesFile = this._rules.name; - this._rules = rules; - const issues = await this.loadRuleset(); - this.updateWatcher(rules.name, prevRulesFile); - return issues; - } - async stop(): Promise { await this._watcher.close(); } - private updateWatcher(rulesFile: string, prevRulesFile?: string): void { - if (prevRulesFile) { - this._watcher.unwatch(prevRulesFile); - } - + private updateWatcher(rulesFile: string): void { this._watcher = chokidar .watch(rulesFile, { persistent: true, ignoreInitial: true }) .on("change", async () => { @@ -103,6 +87,7 @@ class DefaultStorageRulesManager implements StorageRulesManager { "storage", "Change detected, updating rules for Cloud Storage..." ); + this._rules.content = readFile(rulesFile); await this.loadRuleset(); }); } @@ -157,12 +142,6 @@ class ResourceBasedStorageRulesManager implements StorageRulesManager { return this._rulesManagers.get(resource)?.getRuleset(); } - updateSourceFile(rules: SourceFile, resource: string): Promise { - const rulesManager = - this._rulesManagers.get(resource) || this.createRulesManager(resource, rules); - return rulesManager.updateSourceFile(rules); - } - async stop(): Promise { await Promise.all( Array.from(this._rulesManagers.values(), async (rulesManager) => await rulesManager.stop()) diff --git a/src/emulator/storage/server.ts b/src/emulator/storage/server.ts index c8a3e3c1114..684ccf61574 100644 --- a/src/emulator/storage/server.ts +++ b/src/emulator/storage/server.ts @@ -4,8 +4,10 @@ import { EmulatorLogger } from "../emulatorLogger"; import { Emulators } from "../types"; import * as bodyParser from "body-parser"; import { createCloudEndpoints } from "./apis/gcloud"; -import { StorageEmulator } from "./index"; +import { RulesConfig, StorageEmulator } from "./index"; import { createFirebaseEndpoints } from "./apis/firebase"; +import { InvalidArgumentError } from "../auth/errors"; +import { SourceFile } from "./rules/types"; /** * @param defaultProjectId @@ -65,38 +67,79 @@ export function createApp( res.sendStatus(200); }); + /** + * Internal endpoint to overwrite current rules. Callers provide either a single set of rules to + * be applied to all resources or an array of rules/resource objects. + * + * Example payload for single set of rules: + * + * ``` + * { + * rules: { + * files: [{ name: , content: }] + * } + * } + * ``` + * + * Example payload for multiple rules/resource objects: + * + * ``` + * { + * rules: { + * files: [ + * { name: , content: , resource: }, + * ... + * ] + * } + * } + * ``` + */ app.put("/internal/setRules", async (req, res) => { - // Payload: - // { - // rules: { - // files: [{ name: content: }] - // } - // } - // TODO: Add a bucket parameter for per-bucket rules support - - const rules = req.body.rules; - if (!(rules && Array.isArray(rules.files) && rules.files.length > 0)) { - res.status(400).send("Request body must include 'rules.files' array ."); + const rulesRaw = req.body.rules; + if (!(rulesRaw && Array.isArray(rulesRaw.files) && rulesRaw.files.length > 0)) { + res.status(400).json({ + message: "Request body must include 'rules.files' array", + }); return; } - const file = rules.files[0]; - if (!(file.name && file.content)) { - res - .status(400) - .send( - "Request body must include 'rules.files' array where each member contains 'name' and 'content'." - ); - return; + const { files } = rulesRaw; + + function parseRulesFromFiles(files: Array): SourceFile | RulesConfig[] { + if (files.length === 1) { + const file = files[0]; + if (!isRulesFile(file)) { + throw new InvalidArgumentError( + "Each member of 'rules.files' array must contain 'name' and 'content'" + ); + } + return { name: file.name, content: file.content }; + } + + const rules: RulesConfig[] = []; + for (const file of files) { + if (!isRulesFile(file) || !file.resource) { + throw new InvalidArgumentError( + "Each member of 'rules.files' array must contain 'name', 'content', and 'resource'" + ); + } + rules.push({ resource: file.resource, rules: { name: file.name, content: file.content } }); + } + return rules; } - const name = file.name; - const content = file.content; - const issues = await emulator.rulesManager.updateSourceFile( - { name, content }, - req.params.bucketId - ); + let rules: SourceFile | RulesConfig[]; + try { + rules = parseRulesFromFiles(files); + } catch (err) { + if (err instanceof InvalidArgumentError) { + res.status(400).json({ message: err.message }); + return; + } + throw err; + } + const issues = await emulator.replaceRules(rules); if (issues.errors.length > 0) { res.status(400).json({ message: "There was an error updating rules, see logs for more details", @@ -119,3 +162,15 @@ export function createApp( return Promise.resolve(app); } + +interface RulesFile { + name: string; + content: string; + resource?: string; +} + +function isRulesFile(file: unknown): file is RulesFile { + return ( + typeof (file as RulesFile).name === "string" && typeof (file as RulesFile).content === "string" + ); +} From 3d1dd7e75d7ea3f1f32a77a9cd2bd7c73a2ba4c3 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 30 Mar 2022 12:33:49 -0400 Subject: [PATCH 0201/1699] Fix issue with importing Storage Emulator data with nested directory structure (#4358) * Flatten imported emulator data directory structure * Add integration tests for importing emulator data * Add CHANGELOG entry * Add test for importing exported Storage Emulator data --- CHANGELOG.md | 1 + .../firebase-export-metadata.json | 7 ++ ...e-project-id.appspot.com%2Ftest_upload.jpg | 0 .../storage_export/buckets.json | 7 ++ ...ject-id.appspot.com%2Ftest_upload.jpg.json | 20 +++++ .../firebase-export-metadata.json | 7 ++ .../test_upload.jpg | 0 .../storage_export/buckets.json | 7 ++ .../test_upload.jpg.json | 20 +++++ .../import/tests.ts | 90 +++++++++++++++++++ scripts/storage-emulator-integration/run.sh | 2 + src/emulator/storage/files.ts | 16 ++-- 12 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 scripts/storage-emulator-integration/import/flattened-emulator-data/firebase-export-metadata.json create mode 100644 scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/blobs/fake-project-id.appspot.com%2Ftest_upload.jpg create mode 100644 scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/buckets.json create mode 100644 scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/metadata/fake-project-id.appspot.com%2Ftest_upload.jpg.json create mode 100644 scripts/storage-emulator-integration/import/nested-emulator-data/firebase-export-metadata.json create mode 100644 scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/blobs/fake-project-id.appspot.com/test_upload.jpg create mode 100644 scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/buckets.json create mode 100644 scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/metadata/fake-project-id.appspot.com/test_upload.jpg.json create mode 100644 scripts/storage-emulator-integration/import/tests.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f825343160..fde4c07214e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Fixes bug where GCS metadataUpdate cloud functions were triggered in incorrect situations (#3398). - Fixes bug where quoted escape sequences in .env files were incompletely unescaped. (#4270) - Fixes Storage Emulator ruleset file watcher (#4337). +- Fixes issue with importing Storage Emulator data exported prior to v10.3.0 (#4358). diff --git a/scripts/storage-emulator-integration/import/flattened-emulator-data/firebase-export-metadata.json b/scripts/storage-emulator-integration/import/flattened-emulator-data/firebase-export-metadata.json new file mode 100644 index 00000000000..6568db544e7 --- /dev/null +++ b/scripts/storage-emulator-integration/import/flattened-emulator-data/firebase-export-metadata.json @@ -0,0 +1,7 @@ +{ + "version": "10.4.2", + "storage": { + "version": "10.4.2", + "path": "storage_export" + } +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/blobs/fake-project-id.appspot.com%2Ftest_upload.jpg b/scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/blobs/fake-project-id.appspot.com%2Ftest_upload.jpg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/buckets.json b/scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/buckets.json new file mode 100644 index 00000000000..5cdf3eb336f --- /dev/null +++ b/scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/buckets.json @@ -0,0 +1,7 @@ +{ + "buckets": [ + { + "id": "fake-project-id.appspot.com" + } + ] +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/metadata/fake-project-id.appspot.com%2Ftest_upload.jpg.json b/scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/metadata/fake-project-id.appspot.com%2Ftest_upload.jpg.json new file mode 100644 index 00000000000..bc5caae9e79 --- /dev/null +++ b/scripts/storage-emulator-integration/import/flattened-emulator-data/storage_export/metadata/fake-project-id.appspot.com%2Ftest_upload.jpg.json @@ -0,0 +1,20 @@ +{ + "name": "test_upload.jpg", + "bucket": "fake-project-id.appspot.com", + "contentType": "application/octet-stream", + "metageneration": 1, + "generation": 1648084940926, + "storageClass": "STANDARD", + "contentDisposition": "inline", + "cacheControl": "public, max-age=3600", + "contentEncoding": "identity", + "downloadTokens": [ + "c3c71086-95a8-445d-96e7-f625972de4b0" + ], + "etag": "PQJQBXRweACX9yRsBEInQjOJ/0s", + "timeCreated": "2022-03-24T01:22:20.926Z", + "updated": "2022-03-24T01:22:20.926Z", + "size": 0, + "md5Hash": "1B2M2Y8AsgTpgAmY7PhCfg==", + "crc32c": "0" +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/nested-emulator-data/firebase-export-metadata.json b/scripts/storage-emulator-integration/import/nested-emulator-data/firebase-export-metadata.json new file mode 100644 index 00000000000..6568db544e7 --- /dev/null +++ b/scripts/storage-emulator-integration/import/nested-emulator-data/firebase-export-metadata.json @@ -0,0 +1,7 @@ +{ + "version": "10.4.2", + "storage": { + "version": "10.4.2", + "path": "storage_export" + } +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/blobs/fake-project-id.appspot.com/test_upload.jpg b/scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/blobs/fake-project-id.appspot.com/test_upload.jpg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/buckets.json b/scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/buckets.json new file mode 100644 index 00000000000..5cdf3eb336f --- /dev/null +++ b/scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/buckets.json @@ -0,0 +1,7 @@ +{ + "buckets": [ + { + "id": "fake-project-id.appspot.com" + } + ] +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/metadata/fake-project-id.appspot.com/test_upload.jpg.json b/scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/metadata/fake-project-id.appspot.com/test_upload.jpg.json new file mode 100644 index 00000000000..bc5caae9e79 --- /dev/null +++ b/scripts/storage-emulator-integration/import/nested-emulator-data/storage_export/metadata/fake-project-id.appspot.com/test_upload.jpg.json @@ -0,0 +1,20 @@ +{ + "name": "test_upload.jpg", + "bucket": "fake-project-id.appspot.com", + "contentType": "application/octet-stream", + "metageneration": 1, + "generation": 1648084940926, + "storageClass": "STANDARD", + "contentDisposition": "inline", + "cacheControl": "public, max-age=3600", + "contentEncoding": "identity", + "downloadTokens": [ + "c3c71086-95a8-445d-96e7-f625972de4b0" + ], + "etag": "PQJQBXRweACX9yRsBEInQjOJ/0s", + "timeCreated": "2022-03-24T01:22:20.926Z", + "updated": "2022-03-24T01:22:20.926Z", + "size": 0, + "md5Hash": "1B2M2Y8AsgTpgAmY7PhCfg==", + "crc32c": "0" +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/tests.ts b/scripts/storage-emulator-integration/import/tests.ts new file mode 100644 index 00000000000..a434f55d667 --- /dev/null +++ b/scripts/storage-emulator-integration/import/tests.ts @@ -0,0 +1,90 @@ +import * as path from "path"; +import supertest = require("supertest"); + +import { createTmpDir } from "../../../src/test/emulators/fixtures"; +import { Emulators } from "../../../src/emulator/types"; +import { TriggerEndToEndTest } from "../../integration-helpers/framework"; +import { + EMULATORS_SHUTDOWN_DELAY_MS, + FIREBASE_EMULATOR_CONFIG, + getStorageEmulatorHost, + readEmulatorConfig, + TEST_SETUP_TIMEOUT, +} from "../utils"; + +describe("Import Emulator Data", () => { + const FIREBASE_PROJECT = "fake-project-id"; + const BUCKET = `${FIREBASE_PROJECT}.appspot.com`; + const EMULATOR_CONFIG = readEmulatorConfig(FIREBASE_EMULATOR_CONFIG); + const STORAGE_EMULATOR_HOST = getStorageEmulatorHost(EMULATOR_CONFIG); + const test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, EMULATOR_CONFIG); + + it("retrieves file from imported flattened emulator data", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + await test.startEmulators([ + "--only", + Emulators.STORAGE, + "--import", + path.join(__dirname, "flattened-emulator-data"), + "--export-on-exit", + path.join(__dirname, "other-emulator-data"), + ]); + + await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${BUCKET}/o/test_upload.jpg`) + .set({ Authorization: "Bearer owner" }) + .expect(200); + }); + + it("retrieves file from imported nested emulator data", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + await test.startEmulators([ + "--only", + Emulators.STORAGE, + "--import", + path.join(__dirname, "flattened-emulator-data"), + ]); + + await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${BUCKET}/o/test_upload.jpg`) + .set({ Authorization: "Bearer owner" }) + .expect(200); + }); + + it("retrieves file from importing previously exported emulator data", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + const tmpDir = createTmpDir("exported-emulator-data"); + + // Upload file to Storage and export emulator data to tmp directory + await test.startEmulators(["--only", Emulators.STORAGE, "--export-on-exit", tmpDir]); + const uploadURL = await supertest(STORAGE_EMULATOR_HOST) + .post(`/v0/b/${BUCKET}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`) + .set({ + Authorization: "Bearer owner", + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(STORAGE_EMULATOR_HOST) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload, finalize", + }); + + await test.stopEmulators(); + + // Import emulator data from tmp directory and retrieve file from Storage + await test.startEmulators(["--only", Emulators.STORAGE, "--import", tmpDir]); + await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${BUCKET}/o/test_upload.jpg`) + .set({ Authorization: "Bearer owner" }) + .expect(200); + }); + + afterEach(async function (this) { + this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + await test.stopEmulators(); + }); +}); diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index d6e82eeea46..025bd21bef5 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -9,6 +9,8 @@ firebase setup:emulators:storage mocha scripts/storage-emulator-integration/rules/*.test.ts +mocha scripts/storage-emulator-integration/import/tests.ts + mocha scripts/storage-emulator-integration/multiple-targets/tests.ts mocha scripts/storage-emulator-integration/tests.ts diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index 95e5dd24b98..e762ebb1970 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -583,12 +583,18 @@ export class StorageLayer { continue; } - const file = new StoredFile(metadata, blobPath); - this._files.set(blobPath, file); - } + const decodedBlobPath = decodeURIComponent(blobPath); + const decodedMetadataPath = decodeURIComponent(metadataRelPath); + + const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath); + const metadataDiskPath = this._persistence.getDiskPath(decodedMetadataPath); - // Recursively copy all blobs - fse.copySync(blobsDir, this.dirPath); + const file = new StoredFile(metadata, blobDiskPath); + this._files.set(decodedBlobPath, file); + + fse.copyFileSync(f, metadataDiskPath); + fse.copyFileSync(blobAbsPath, blobDiskPath); + } } private *walkDirSync(dir: string): Generator { From 1bc5644ffd5fbd47e32b2a961a44a575f4c110fe Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 30 Mar 2022 13:26:25 -0400 Subject: [PATCH 0202/1699] Fix import path, remove export in Storage Emulator tests (#4377) --- scripts/storage-emulator-integration/import/tests.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/storage-emulator-integration/import/tests.ts b/scripts/storage-emulator-integration/import/tests.ts index a434f55d667..14ce645d2fd 100644 --- a/scripts/storage-emulator-integration/import/tests.ts +++ b/scripts/storage-emulator-integration/import/tests.ts @@ -26,8 +26,6 @@ describe("Import Emulator Data", () => { Emulators.STORAGE, "--import", path.join(__dirname, "flattened-emulator-data"), - "--export-on-exit", - path.join(__dirname, "other-emulator-data"), ]); await supertest(STORAGE_EMULATOR_HOST) @@ -42,7 +40,7 @@ describe("Import Emulator Data", () => { "--only", Emulators.STORAGE, "--import", - path.join(__dirname, "flattened-emulator-data"), + path.join(__dirname, "nested-emulator-data"), ]); await supertest(STORAGE_EMULATOR_HOST) From f2f8ea8433c0f991b00075c3932c55a1d152a667 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 31 Mar 2022 09:48:20 -0700 Subject: [PATCH 0203/1699] Improve function secrets ergonomics (#4130) * Extend `functions:secrets:set` to redeploy affected function triggers and delete now stale secret versions post redeploy. * Extend `functions:secrets:destroy` to block if the secret version is in use. * Extend function deploy to cleanup unused secret version post successful deploy. Also sneaking in a change to improve `prune` logic by finding all secret versions that isn't destroyed (DISABLED secret versions still cost $). --- CHANGELOG.md | 1 + src/commands/functions-secrets-destroy.ts | 35 +++- src/commands/functions-secrets-prune.ts | 38 ++-- src/commands/functions-secrets-set.ts | 89 ++++++++- src/deploy/functions/release/fabricator.ts | 4 +- src/deploy/functions/release/index.ts | 30 +++ src/functions/secrets.ts | 154 ++++++++++++++- src/gcp/secretManager.ts | 5 +- src/test/functions/secrets.spec.ts | 208 +++++++++++++++++++-- 9 files changed, 502 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fde4c07214e..856cadb298d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Fixes bug where quoted escape sequences in .env files were incompletely unescaped. (#4270) - Fixes Storage Emulator ruleset file watcher (#4337). - Fixes issue with importing Storage Emulator data exported prior to v10.3.0 (#4358). +- Adds ergonomic improvements to CF3 secret commands to automatically redeploy functions and delete unused secrets (#4130). diff --git a/src/commands/functions-secrets-destroy.ts b/src/commands/functions-secrets-destroy.ts index eb5a4dac289..ab3691481f2 100644 --- a/src/commands/functions-secrets-destroy.ts +++ b/src/commands/functions-secrets-destroy.ts @@ -1,7 +1,6 @@ import { Command } from "../command"; -import { logger } from "../logger"; import { Options } from "../options"; -import { needProjectId } from "../projectUtils"; +import { needProjectId, needProjectNumber } from "../projectUtils"; import { deleteSecret, destroySecretVersion, @@ -10,18 +9,46 @@ import { listSecretVersions, } from "../gcp/secretManager"; import { promptOnce } from "../prompt"; +import { logBullet, logWarning } from "../utils"; import * as secrets from "../functions/secrets"; +import * as backend from "../deploy/functions/backend"; +import * as args from "../deploy/functions/args"; export default new Command("functions:secrets:destroy [@version]") .description("Destroy a secret. Defaults to destroying the latest version.") .withForce("Destroys a secret without confirmation.") .action(async (key: string, options: Options) => { const projectId = needProjectId(options); + const projectNumber = await needProjectNumber(options); + const haveBackend = await backend.existingBackend({ projectId } as args.Context); + let [name, version] = key.split("@"); if (!version) { version = "latest"; } const sv = await getSecretVersion(projectId, name, version); + + if (sv.state === "DESTROYED") { + logBullet(`Secret ${sv.secret.name}@${version} is already destroyed. Nothing to do.`); + return; + } + + const boundEndpoints = backend + .allEndpoints(haveBackend) + .filter((e) => secrets.inUse({ projectId, projectNumber }, sv.secret, e)); + if (boundEndpoints.length > 0) { + const endpointsMsg = boundEndpoints + .map((e) => `${e.id}[${e.platform}](${e.region})`) + .join("\t\n"); + logWarning( + `Secret ${name}@${version} is currently in use by following functions:\n\t${endpointsMsg}` + ); + if (!options.force) { + logWarning("Refusing to destroy secret in use. Use -f to destroy the secret anyway."); + return; + } + } + if (!options.force) { const confirm = await promptOnce( { @@ -37,13 +64,13 @@ export default new Command("functions:secrets:destroy [@version]") } } await destroySecretVersion(projectId, name, version); - logger.info(`Destroyed secret version ${name}@${sv.versionId}`); + logBullet(`Destroyed secret version ${name}@${sv.versionId}`); const secret = await getSecret(projectId, name); if (secrets.isFirebaseManaged(secret)) { const versions = await listSecretVersions(projectId, name); if (versions.filter((v) => v.state === "ENABLED").length === 0) { - logger.info(`No active secret versions left. Destroying secret ${name}`); + logBullet(`No active secret versions left. Destroying secret ${name}`); // No active secret version. Remove secret resource. await deleteSecret(projectId, name); } diff --git a/src/commands/functions-secrets-prune.ts b/src/commands/functions-secrets-prune.ts index 91b072a2d67..94ef01c192e 100644 --- a/src/commands/functions-secrets-prune.ts +++ b/src/commands/functions-secrets-prune.ts @@ -11,6 +11,7 @@ import { promptOnce } from "../prompt"; import { destroySecretVersion } from "../gcp/secretManager"; export default new Command("functions:secrets:prune") + .withForce("Destroys unused secrets without prompt") .description("Destroys unused secrets") .before(requirePermissions, [ "cloudfunctions.functions.list", @@ -42,27 +43,26 @@ export default new Command("functions:secrets:prune") pruned.map((sv) => `${sv.secret}@${sv.version}`).join("\n\t") ); - const confirm = await promptOnce( - { - name: "destroy", - type: "confirm", - default: true, - message: `Do you want to destroy unused secret versions?`, - }, - options - ); - - if (!confirm) { - logBullet( - "Run the following commands to destroy each unused secret version:\n\t" + - pruned - .map((sv) => `firebase functions:secrets:destroy ${sv.secret}@${sv.version}`) - .join("\n\t") + if (!options.force) { + const confirm = await promptOnce( + { + name: "destroy", + type: "confirm", + default: true, + message: `Do you want to destroy unused secret versions?`, + }, + options ); - return; + if (!confirm) { + logBullet( + "Run the following commands to destroy each unused secret version:\n\t" + + pruned + .map((sv) => `firebase functions:secrets:destroy ${sv.secret}@${sv.version}`) + .join("\n\t") + ); + return; + } } - await Promise.all(pruned.map((sv) => destroySecretVersion(projectId, sv.secret, sv.version))); - logSuccess("Destroyed all unused secrets!"); }); diff --git a/src/commands/functions-secrets-set.ts b/src/commands/functions-secrets-set.ts index a6ec2eae397..98ea4cfb58a 100644 --- a/src/commands/functions-secrets-set.ts +++ b/src/commands/functions-secrets-set.ts @@ -3,20 +3,21 @@ import * as fs from "fs"; import * as clc from "cli-color"; -import { ensureValidKey, ensureSecret } from "../functions/secrets"; +import { ensureValidKey, ensureSecret, pruneAndDestroySecrets } from "../functions/secrets"; import { Command } from "../command"; import { requirePermissions } from "../requirePermissions"; import { Options } from "../options"; import { promptOnce } from "../prompt"; -import { logBullet, logSuccess } from "../utils"; -import { needProjectId } from "../projectUtils"; +import { logBullet, logSuccess, logWarning } from "../utils"; +import { needProjectId, needProjectNumber } from "../projectUtils"; import { addVersion, toSecretVersionResourceName } from "../gcp/secretManager"; +import * as secrets from "../functions/secrets"; +import * as backend from "../deploy/functions/backend"; +import * as args from "../deploy/functions/args"; export default new Command("functions:secrets:set ") - .description("Create or update a secret for use in Cloud Functions for Firebase") - .withForce( - "Does not ensure input keys are valid or upgrade existing secrets to have Firebase manage them." - ) + .description("Create or update a secret for use in Cloud Functions for Firebase.") + .withForce("Automatically updates functions to use the new secret.") .before(requirePermissions, [ "secretmanager.secrets.create", "secretmanager.secrets.get", @@ -29,6 +30,7 @@ export default new Command("functions:secrets:set ") ) .action(async (unvalidatedKey: string, options: Options) => { const projectId = needProjectId(options); + const projectNumber = await needProjectNumber(options); const key = await ensureValidKey(unvalidatedKey, options); const secret = await ensureSecret(projectId, key, options); let secretValue; @@ -49,8 +51,77 @@ export default new Command("functions:secrets:set ") const secretVersion = await addVersion(projectId, key, secretValue); logSuccess(`Created a new secret version ${toSecretVersionResourceName(secretVersion)}`); + + if (!secrets.isFirebaseManaged(secret)) { + logBullet( + "Please deploy your functions for the change to take effect by running:\n\t" + + clc.bold("firebase deploy --only functions") + ); + return; + } + + const haveBackend = await backend.existingBackend({ projectId } as args.Context); + const endpointsToUpdate = backend + .allEndpoints(haveBackend) + .filter((e) => secrets.inUse({ projectId, projectNumber }, secret, e)); + + if (endpointsToUpdate.length === 0) { + return; + } + logBullet( - "Please deploy your functions for the change to take effect by running:\n\t" + - clc.bold("firebase deploy --only functions") + `${endpointsToUpdate.length} functions are using stale version of secret ${secret.name}:\n\t` + + endpointsToUpdate.map((e) => `${e.id}(${e.region})`).join("\n\t") ); + + if (!options.force) { + const confirm = await promptOnce( + { + name: "redeploy", + type: "confirm", + default: true, + message: `Do you want to re-deploy the functions and destroy the stale version of secret ${secret.name}?`, + }, + options + ); + if (!confirm) { + logBullet( + "Please deploy your functions for the change to take effect by running:\n\t" + + clc.bold("firebase deploy --only functions") + ); + return; + } + } + + const updateOps = endpointsToUpdate.map(async (e) => { + logBullet(`Updating function ${e.id}(${e.region})...`); + const updated = await secrets.updateEndpointSecret( + { projectId, projectNumber }, + secretVersion, + e + ); + logBullet(`Updated function ${e.id}(${e.region}).`); + return updated; + }); + const updatedEndpoints = await Promise.all(updateOps); + + logBullet(`Pruning stale secrets...`); + const prunedResult = await pruneAndDestroySecrets( + { projectId, projectNumber }, + updatedEndpoints + ); + if (prunedResult.destroyed.length > 0) { + logBullet( + `Detroyed unused secret versions: ${prunedResult.destroyed + .map((s) => `${s.secret}@${s.version}`) + .join(", ")}` + ); + } + if (prunedResult.erred.length > 0) { + logWarning( + `Failed to destroy unused secret versions:\n\t${prunedResult.erred + .map((err) => err.message) + .join("\n\t")}` + ); + } }); diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 7abe33d1f4f..63ada445807 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -23,14 +23,14 @@ import * as scheduler from "../../../gcp/cloudscheduler"; import * as utils from "../../../utils"; // TODO: Tune this for better performance. -const gcfV1PollerOptions = { +const gcfV1PollerOptions: Omit = { apiOrigin: functionsOrigin, apiVersion: gcf.API_VERSION, masterTimeout: 25 * 60 * 1_000, // 25 minutes is the maximum build time for a function maxBackoff: 10_000, }; -const gcfV2PollerOptions = { +const gcfV2PollerOptions: Omit = { apiOrigin: functionsV2Origin, apiVersion: gcfV2.API_VERSION, masterTimeout: 25 * 60 * 1_000, // 25 minutes is the maximum build time for a function diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index 8ef235377b8..df23e5e663b 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -11,9 +11,12 @@ import * as fabricator from "./fabricator"; import * as reporter from "./reporter"; import * as executor from "./executor"; import * as prompts from "../prompts"; +import * as secrets from "../../../functions/secrets"; import { getAppEngineLocation } from "../../../functionsConfig"; import { getFunctionLabel } from "../functionsDeployHelper"; import { FirebaseError } from "../../../error"; +import { needProjectId, needProjectNumber } from "../../../projectUtils"; +import { logLabeledBullet, logLabeledWarning } from "../../../utils"; /** Releases new versions of functions to prod. */ export async function release( @@ -85,6 +88,33 @@ export async function release( if (allErrors.length) { const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors }; throw new FirebaseError("There was an error deploying functions", { ...opts, exit: 2 }); + } else { + if (secrets.of(haveEndpoints).length > 0) { + const projectId = needProjectId(options); + const projectNumber = await needProjectNumber(options); + // Re-load backend with all endpoints, not just the ones deployed. + const reloadedBackend = await backend.existingBackend({ projectId } as args.Context); + const prunedResult = await secrets.pruneAndDestroySecrets( + { projectId, projectNumber }, + backend.allEndpoints(reloadedBackend) + ); + if (prunedResult.destroyed.length > 0) { + logLabeledBullet( + "functions", + `Destroyed unused secret versions: ${prunedResult.destroyed + .map((s) => `${s.secret}@${s.version}`) + .join(", ")}` + ); + } + if (prunedResult.erred.length > 0) { + logLabeledWarning( + "functions", + `Failed to destroy unused secret versions:\n\t${prunedResult.erred + .map((err) => err.message) + .join("\n\t")}` + ); + } + } } } diff --git a/src/functions/secrets.ts b/src/functions/secrets.ts index 2bb289745c7..44b17112d16 100644 --- a/src/functions/secrets.ts +++ b/src/functions/secrets.ts @@ -1,6 +1,10 @@ +import * as utils from "../utils"; +import * as poller from "../operation-poller"; +import * as gcf from "../gcp/cloudfunctions"; import * as backend from "../deploy/functions/backend"; import { createSecret, + destroySecretVersion, getSecret, getSecretVersion, listSecrets, @@ -8,15 +12,24 @@ import { parseSecretResourceName, patchSecret, Secret, + SecretVersion, } from "../gcp/secretManager"; import { Options } from "../options"; import { FirebaseError } from "../error"; import { logWarning } from "../utils"; import { promptOnce } from "../prompt"; import { validateKey } from "./env"; +import { logger } from "../logger"; +import { functionsOrigin } from "../api"; +import { assertExhaustive } from "../functional"; const FIREBASE_MANGED = "firebase-managed"; +type ProjectInfo = { + projectId: string; + projectNumber: string; +}; + /** * Returns true if secret is managed by Firebase. */ @@ -122,11 +135,27 @@ export function of(endpoints: backend.Endpoint[]): backend.SecretEnvVar[] { ); } +/** + * Checks whether a secret is in use by the given endpoint. + */ +export function inUse(projectInfo: ProjectInfo, secret: Secret, endpoint: backend.Endpoint) { + const { projectId, projectNumber } = projectInfo; + for (const sev of of([endpoint])) { + if ( + (sev.projectId === projectId || sev.projectId === projectNumber) && + sev.secret === secret.name + ) { + return true; + } + } + return false; +} + /** * Returns all secret versions from Firebase managed secrets unused in the given list of endpoints. */ export async function pruneSecrets( - projectInfo: { projectNumber: string; projectId: string }, + projectInfo: ProjectInfo, endpoints: backend.Endpoint[] ): Promise[]> { const { projectId, projectNumber } = projectInfo; @@ -136,17 +165,29 @@ export async function pruneSecrets( // Collect all Firebase managed secret versions const haveSecrets = await listSecrets(projectId, `labels.${FIREBASE_MANGED}=true`); for (const secret of haveSecrets) { - const versions = await listSecretVersions(projectId, secret.name, `state: ENABLED`); + const versions = await listSecretVersions(projectId, secret.name, `NOT state: DESTROYED`); for (const version of versions) { prunedSecrets.add(pruneKey(secret.name, version.versionId)); } } // Prune all project-scoped secrets in use. - const sevs = of(endpoints).filter( - (sev) => sev.projectId === projectId || sev.projectId === projectNumber - ); - for (const sev of sevs) { + const secrets: Required[] = []; + for (const secret of of(endpoints)) { + if (!secret.version) { + // All bets are off if secret version isn't available in the endpoint definition. + // This should never happen for GCFv1 instances. + throw new FirebaseError(`Secret ${secret.secret} version is unexpectedly empty.`); + } + if (secret.projectId === projectId || secret.projectId === projectNumber) { + // We already know that secret.version isn't empty, but TS can't figure it out for some reason. + if (secret.version) { + secrets.push({ ...secret, version: secret.version }); + } + } + } + + for (const sev of secrets) { let name = sev.secret; if (name.includes("/")) { const secret = parseSecretResourceName(name); @@ -160,10 +201,109 @@ export async function pruneSecrets( version = resolved.versionId; } - prunedSecrets.delete(pruneKey(name, version!)); + prunedSecrets.delete(pruneKey(name, version)); } return Array.from(prunedSecrets) .map((key) => key.split("@")) .map(([secret, version]) => ({ projectId, version, secret, key: secret })); } + +type PruneResult = { + destroyed: backend.SecretEnvVar[]; + erred: { message: string }[]; +}; + +/** + * Prune and destroy all unused secret versions. Only Firebase managed secrets will be scanned. + */ +export async function pruneAndDestroySecrets( + projectInfo: ProjectInfo, + endpoints: backend.Endpoint[] +): Promise { + const { projectId, projectNumber } = projectInfo; + + logger.debug("Pruning secrets to find unused secret versions..."); + const unusedSecrets: Required[] = await module.exports.pruneSecrets( + { projectId, projectNumber }, + endpoints + ); + + if (unusedSecrets.length === 0) { + return { destroyed: [], erred: [] }; + } + + const destroyed: PruneResult["destroyed"] = []; + const erred: PruneResult["erred"] = []; + const msg = unusedSecrets.map((s) => `${s.secret}@${s.version}`); + logger.debug(`Found unused secret versions: ${msg}. Destroying them...`); + const destroyResults = await utils.allSettled( + unusedSecrets.map(async (sev) => { + await destroySecretVersion(sev.projectId, sev.secret, sev.version); + return sev; + }) + ); + + for (const result of destroyResults) { + if (result.status === "fulfilled") { + destroyed.push(result.value); + } else { + erred.push(result.reason as { message: string }); + } + } + return { destroyed, erred }; +} + +/** + * Updates given endpoint to use the given secret version. + */ +export async function updateEndpointSecret( + projectInfo: ProjectInfo, + secretVersion: SecretVersion, + endpoint: backend.Endpoint +): Promise { + const { projectId, projectNumber } = projectInfo; + + if (!inUse(projectInfo, secretVersion.secret, endpoint)) { + return endpoint; + } + + const updatedSevs: Required[] = []; + for (const sev of of([endpoint])) { + const updatedSev = { ...sev } as Required; + if ( + (updatedSev.projectId === projectId || updatedSev.projectId === projectNumber) && + updatedSev.secret === secretVersion.secret.name + ) { + updatedSev.version = secretVersion.versionId; + } + updatedSevs.push(updatedSev); + } + + if (endpoint.platform === "gcfv1") { + const fn = gcf.functionFromEndpoint(endpoint, ""); + const op = await gcf.updateFunction({ + name: fn.name, + runtime: fn.runtime, + entryPoint: fn.entryPoint, + secretEnvironmentVariables: updatedSevs, + }); + // Using fabricator.gcfV1PollerOptions doesn't work - apiVersion is empty on that object. + // Possibly due to cyclical dependency? Copying the option in verbatim instead. + const gcfV1PollerOptions = { + apiOrigin: functionsOrigin, + apiVersion: gcf.API_VERSION, + masterTimeout: 25 * 60 * 1_000, // 25 minutes is the maximum build time for a function + maxBackoff: 10_000, + pollerName: `update-${endpoint.region}-${endpoint.id}`, + operationResourceName: op.name, + }; + const cfn = await poller.pollOperation(gcfV1PollerOptions); + return gcf.endpointFromFunction(cfn); + } else if (endpoint.platform === "gcfv2") { + // TODO add support for updating secrets in v2 functions once the feature lands. + throw new FirebaseError(`Unsupported platform ${endpoint.platform}`); + } else { + assertExhaustive(endpoint.platform); + } +} diff --git a/src/gcp/secretManager.ts b/src/gcp/secretManager.ts index 0a7a8eea1bf..121cdf398b5 100644 --- a/src/gcp/secretManager.ts +++ b/src/gcp/secretManager.ts @@ -260,7 +260,10 @@ export async function createSecret( }, { queryParams: { secretId: name } } ); - return parseSecretResourceName(createRes.body.name); + return { + ...parseSecretResourceName(createRes.body.name), + labels, + }; } /** diff --git a/src/test/functions/secrets.spec.ts b/src/test/functions/secrets.spec.ts index 74dd9adfe8c..6dcb25181ef 100644 --- a/src/test/functions/secrets.spec.ts +++ b/src/test/functions/secrets.spec.ts @@ -2,12 +2,25 @@ import * as sinon from "sinon"; import { expect } from "chai"; import * as secretManager from "../../gcp/secretManager"; +import * as gcf from "../../gcp/cloudfunctions"; import * as secrets from "../../functions/secrets"; import * as utils from "../../utils"; import * as prompt from "../../prompt"; import * as backend from "../../deploy/functions/backend"; +import * as poller from "../../operation-poller"; import { Options } from "../../options"; import { FirebaseError } from "../../error"; +import { updateEndpointSecret } from "../../functions/secrets"; + +const ENDPOINT = { + id: "id", + region: "region", + project: "project", + entryPoint: "id", + runtime: "nodejs16", + platform: "gcfv1" as const, + httpsTrigger: {}, +}; describe("functions/secret", () => { const options = { force: false } as Options; @@ -129,16 +142,6 @@ describe("functions/secret", () => { }); describe("of", () => { - const ENDPOINT = { - id: "id", - region: "region", - project: "project", - entryPoint: "id", - runtime: "nodejs16", - platform: "gcfv1" as const, - httpsTrigger: {}, - }; - function makeSecret(name: string, version?: string): backend.SecretEnvVar { return { projectId: "project", @@ -174,16 +177,6 @@ describe("functions/secret", () => { }); describe("pruneSecrets", () => { - const ENDPOINT = { - id: "id", - region: "region", - project: "project", - entryPoint: "id", - runtime: "nodejs16", - platform: "gcfv1" as const, - httpsTrigger: {}, - }; - let listSecretsStub: sinon.SinonStub; let listSecretVersionsStub: sinon.SinonStub; let getSecretVersionStub: sinon.SinonStub; @@ -289,4 +282,179 @@ describe("functions/secret", () => { expect(pruned).to.have.length(2); }); }); + + describe("inUse", () => { + const projectId = "project"; + const projectNumber = "12345"; + const secret: secretManager.Secret = { + projectId, + name: "MY_SECRET", + }; + + it("returns true if secret is in use", () => { + expect( + secrets.inUse({ projectId, projectNumber }, secret, { + ...ENDPOINT, + secretEnvironmentVariables: [ + { projectId, key: secret.name, secret: secret.name, version: "1" }, + ], + }) + ).to.be.true; + }); + + it("returns true if secret is in use by project number", () => { + expect( + secrets.inUse({ projectId, projectNumber }, secret, { + ...ENDPOINT, + secretEnvironmentVariables: [ + { projectId: projectNumber, key: secret.name, secret: secret.name, version: "1" }, + ], + }) + ).to.be.true; + }); + + it("returns false if secret is not in use", () => { + expect(secrets.inUse({ projectId, projectNumber }, secret, ENDPOINT)).to.be.false; + }); + + it("returns false if secret of same name from another project is in use", () => { + expect( + secrets.inUse({ projectId, projectNumber }, secret, { + ...ENDPOINT, + secretEnvironmentVariables: [ + { projectId: "another-project", key: secret.name, secret: secret.name, version: "1" }, + ], + }) + ).to.be.false; + }); + }); + + describe("pruneAndDestroySecrets", () => { + let pruneSecretsStub: sinon.SinonStub; + let destroySecretVersionStub: sinon.SinonStub; + + const projectId = "projectId"; + const projectNumber = "12345"; + const secret0: backend.SecretEnvVar = { + projectId, + key: "MY_SECRET", + secret: "MY_SECRET", + version: "1", + }; + const secret1: backend.SecretEnvVar = { + projectId, + key: "MY_SECRET", + secret: "MY_SECRET", + version: "1", + }; + + beforeEach(() => { + pruneSecretsStub = sinon.stub(secrets, "pruneSecrets").rejects("Unexpected call"); + destroySecretVersionStub = sinon + .stub(secretManager, "destroySecretVersion") + .rejects("Unexpected call"); + }); + + afterEach(() => { + pruneSecretsStub.restore(); + destroySecretVersionStub.restore(); + }); + + it("destroys pruned secrets", async () => { + pruneSecretsStub.resolves([secret1]); + destroySecretVersionStub.resolves(); + + await expect( + secrets.pruneAndDestroySecrets({ projectId, projectNumber }, [ + { + ...ENDPOINT, + secretEnvironmentVariables: [secret0], + }, + { + ...ENDPOINT, + secretEnvironmentVariables: [secret1], + }, + ]) + ).to.eventually.deep.equal({ erred: [], destroyed: [secret1] }); + }); + + it("collects errors", async () => { + pruneSecretsStub.resolves([secret0, secret1]); + destroySecretVersionStub.onFirstCall().resolves(); + destroySecretVersionStub.onSecondCall().rejects({ message: "an error" }); + + await expect( + secrets.pruneAndDestroySecrets({ projectId, projectNumber }, [ + { + ...ENDPOINT, + secretEnvironmentVariables: [secret0], + }, + { + ...ENDPOINT, + secretEnvironmentVariables: [secret1], + }, + ]) + ).to.eventually.deep.equal({ erred: [{ message: "an error" }], destroyed: [secret0] }); + }); + }); + + describe("updateEndpointsSecret", () => { + const projectId = "project"; + const projectNumber = "12345"; + const secretVersion: secretManager.SecretVersion = { + secret: { + projectId, + name: "MY_SECRET", + }, + versionId: "2", + }; + + let gcfMock: sinon.SinonMock; + let pollerStub: sinon.SinonStub; + + beforeEach(() => { + gcfMock = sinon.mock(gcf); + pollerStub = sinon.stub(poller, "pollOperation").rejects("Unexpected call"); + }); + + afterEach(() => { + gcfMock.verify(); + gcfMock.restore(); + pollerStub.restore(); + }); + + it("returns early if secret is not in use", async () => { + const endpoint: backend.Endpoint = { + ...ENDPOINT, + secretEnvironmentVariables: [], + }; + + gcfMock.expects("updateFunction").never(); + await updateEndpointSecret({ projectId, projectNumber }, secretVersion, endpoint); + }); + + it("updates function with the version of the given secret", async () => { + const sev: backend.SecretEnvVar = { + projectId: projectNumber, + secret: secretVersion.secret.name, + key: secretVersion.secret.name, + version: "1", + }; + const endpoint: backend.Endpoint = { + ...ENDPOINT, + secretEnvironmentVariables: [sev], + }; + const fn: Omit = { + name: `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`, + runtime: endpoint.runtime, + entryPoint: endpoint.entryPoint, + secretEnvironmentVariables: [{ ...sev, version: "2" }], + }; + + pollerStub.resolves({ ...fn, httpsTrigger: {} }); + gcfMock.expects("updateFunction").once().withArgs(fn).resolves({}); + + await updateEndpointSecret({ projectId, projectNumber }, secretVersion, endpoint); + }); + }); }); From 63a8c49163af31cc8743fb88daea9416c5ff6978 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Thu, 31 Mar 2022 13:24:54 -0400 Subject: [PATCH 0204/1699] Extensions - Require local secret to be not empty (#4385) * Require local secret to be not empty * Add command to start emulator Co-authored-by: joehan --- src/extensions/askUserForParam.ts | 24 ++++++++++++------------ src/extensions/manifest.ts | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index e186d192fe5..e833a967b74 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -249,18 +249,18 @@ async function promptSecretLocations(paramSpec: Param): Promise { }); } -async function promptLocalSecret( - instanceId: string, - paramSpec: Param -): Promise { - utils.logLabeledBullet(logPrefix, "Configure a local secret value for Extensions Emulator"); - const value = await promptOnce({ - name: paramSpec.param, - type: "input", - message: - `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` + - `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`, - }); +async function promptLocalSecret(instanceId: string, paramSpec: Param): Promise { + let value; + do { + utils.logLabeledBullet(logPrefix, "Configure a local secret value for Extensions Emulator"); + value = await promptOnce({ + name: paramSpec.param, + type: "input", + message: + `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` + + `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`, + }); + } while (!value); return value; } diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index 19efdff737e..a9d30a27954 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -260,7 +260,7 @@ export function showDeprecationWarning() { export function showPreviewWarning() { utils.logLabeledWarning( logPrefix, - "These changes will be reflected in your Firebase Emulator after restart. " + + `See these changes in your Firebase Emulator by running "firebase emulators:start". ` + `Run ${clc.bold( "firebase deploy (--only extensions)" )} to deploy the changes to your Firebase project. ` From e4fc6b0c5f6cfaaedadeac3e484806c5bb38dbc1 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 31 Mar 2022 11:20:33 -0700 Subject: [PATCH 0205/1699] Redirect console links in postinstalls to emulator UI (#4380) * Redirect console links in postinstalls to emulator UI * PR fixes --- src/emulator/extensions/postinstall.ts | 42 +++++++++ src/emulator/functionsEmulatorShared.ts | 9 ++ .../emulators/extensions/postinstall.spec.ts | 85 +++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 src/emulator/extensions/postinstall.ts create mode 100644 src/test/emulators/extensions/postinstall.spec.ts diff --git a/src/emulator/extensions/postinstall.ts b/src/emulator/extensions/postinstall.ts new file mode 100644 index 00000000000..666265b7ac2 --- /dev/null +++ b/src/emulator/extensions/postinstall.ts @@ -0,0 +1,42 @@ +import { EmulatorRegistry } from "../registry"; +import { Emulators } from "../types"; + +/** + * replaceConsoleLinks replaces links to production Firebase console with links to the corresponding Emulator UI page. + * @param postinstall The postinstall instructions to check for console links. + */ +export function replaceConsoleLinks(postinstall: string): string { + const uiInfo = EmulatorRegistry.getInfo(Emulators.UI); + const uiUrl = uiInfo ? `http://${EmulatorRegistry.getInfoHostString(uiInfo)}` : "unknown"; + let subbedPostinstall = postinstall; + const linkReplacements = new Map([ + [ + /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/storage[A-Za-z0-9\/-]*(?=[\)\]\s])/, + `${uiUrl}/${Emulators.STORAGE}`, + ], // Storage console links + [ + /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/firestore[A-Za-z0-9\/-]*(?=[\)\]\s])/, + `${uiUrl}/${Emulators.FIRESTORE}`, + ], // Firestore console links + [ + /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/database[A-Za-z0-9\/-]*(?=[\)\]\s])/, + `${uiUrl}/${Emulators.DATABASE}`, + ], // RTDB console links + [ + /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/authentication[A-Za-z0-9\/-]*(?=[\)\]\s])/, + `${uiUrl}/${Emulators.AUTH}`, + ], // Auth console links + [ + /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/functions[A-Za-z0-9\/-]*(?=[\)\]\s])/, + `${uiUrl}/logs`, // There is no functions page in the UI, so redirect to logs. + ], // Functions console links + [ + /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/extensions[A-Za-z0-9\/-]*(?=[\)\]\s])/, + `${uiUrl}/${Emulators.EXTENSIONS}`, + ], // Extensions console links + ]); + for (const [consoleLinkRegex, replacement] of linkReplacements) { + subbedPostinstall = subbedPostinstall.replace(consoleLinkRegex, replacement); + } + return subbedPostinstall; +} diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index a281f0a8774..f20620560a8 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -13,6 +13,7 @@ import { logger } from "../logger"; import { ENV_DIRECTORY } from "../extensions/manifest"; import { substituteParams } from "../extensions/extensionsHelper"; import { ExtensionSpec, ExtensionVersion } from "../extensions/extensionsApi"; +import { replaceConsoleLinks } from "./extensions/postinstall"; export type SignatureType = "http" | "event" | "cloudevent"; @@ -410,10 +411,18 @@ export function toBackendInfo( let extensionVersion = e.extensionVersion; if (extensionVersion) { extensionVersion = substituteParams(extensionVersion, e.env); + if (extensionVersion.spec?.postinstallContent) { + extensionVersion.spec.postinstallContent = replaceConsoleLinks( + extensionVersion.spec.postinstallContent + ); + } } let extensionSpec = e.extensionSpec; if (extensionSpec) { extensionSpec = substituteParams(extensionSpec, e.env); + if (extensionSpec?.postinstallContent) { + extensionSpec.postinstallContent = replaceConsoleLinks(extensionSpec.postinstallContent); + } } // Parse and stringify to get rid of undefined values diff --git a/src/test/emulators/extensions/postinstall.spec.ts b/src/test/emulators/extensions/postinstall.spec.ts new file mode 100644 index 00000000000..ba4cf79dea9 --- /dev/null +++ b/src/test/emulators/extensions/postinstall.spec.ts @@ -0,0 +1,85 @@ +import { expect } from "chai"; +import * as Sinon from "sinon"; +import * as postinstall from "../../../emulator/extensions/postinstall"; +import { EmulatorRegistry } from "../../../emulator/registry"; +import { Emulators } from "../../../emulator/types"; + +describe("replaceConsoleLinks", () => { + let sandbox: Sinon.SinonSandbox; + beforeEach(() => { + sandbox = Sinon.createSandbox(); + sandbox + .stub(EmulatorRegistry, "getInfo") + .returns({ name: Emulators.UI, host: "localhost", port: 4000 }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + const tests: { + desc: string; + input: string; + expected: string; + }[] = [ + { + desc: "should replace Firestore links", + input: + " Go to your [Cloud Firestore dashboard](https://console.firebase.google.com/project/test-project/firestore/data) in the Firebase console.", + expected: + " Go to your [Cloud Firestore dashboard](http://localhost:4000/firestore) in the Firebase console.", + }, + { + desc: "should replace Functions links", + input: + " Go to your [Cloud Functions dashboard](https://console.firebase.google.com/project/test-project/functions/logs) in the Firebase console.", + expected: + " Go to your [Cloud Functions dashboard](http://localhost:4000/logs) in the Firebase console.", + }, + { + desc: "should replace Extensions links", + input: + " Go to your [Extensions dashboard](https://console.firebase.google.com/project/test-project/extensions) in the Firebase console.", + expected: + " Go to your [Extensions dashboard](http://localhost:4000/extensions) in the Firebase console.", + }, + { + desc: "should replace RTDB links", + input: + " Go to your [Realtime database dashboard](https://console.firebase.google.com/project/test-project/database/test-walkthrough/data) in the Firebase console.", + expected: + " Go to your [Realtime database dashboard](http://localhost:4000/database) in the Firebase console.", + }, + { + desc: "should replace Auth links", + input: + " Go to your [Auth dashboard](https://console.firebase.google.com/project/test-project/authentication/users) in the Firebase console.", + expected: " Go to your [Auth dashboard](http://localhost:4000/auth) in the Firebase console.", + }, + { + desc: "should replace multiple GAIA user links ", + input: + " Go to your [Auth dashboard](https://console.firebase.google.com/u/0/project/test-project/authentication/users) in the Firebase console.", + expected: " Go to your [Auth dashboard](http://localhost:4000/auth) in the Firebase console.", + }, + { + desc: "should replace multiple links", + input: + " Go to your [Cloud Firestore dashboard](https://console.firebase.google.com/project/jh-walkthrough/firestore/data) or [Realtime database dashboard](https://console.firebase.google.com/project/test-project/database/test-walkthrough/data)in the Firebase console.", + expected: + " Go to your [Cloud Firestore dashboard](http://localhost:4000/firestore) or [Realtime database dashboard](http://localhost:4000/database)in the Firebase console.", + }, + { + desc: "should not replace other links", + input: " Go to your [Stripe dashboard](https://stripe.com/payments) to see more information.", + expected: + " Go to your [Stripe dashboard](https://stripe.com/payments) to see more information.", + }, + ]; + + for (const t of tests) { + it(t.desc, () => { + expect(postinstall.replaceConsoleLinks(t.input)).to.equal(t.expected); + }); + } +}); From 1b2ca69a4303410544dc80fa345ff7d8070d89e1 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Thu, 31 Mar 2022 14:35:21 -0400 Subject: [PATCH 0206/1699] Update text copy in extensions secret location selector (#4376) --- src/extensions/askUserForParam.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index e833a967b74..fc4e2e93afb 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -215,13 +215,13 @@ async function promptSecretLocations(paramSpec: Param): Promise { choices: [ { checked: true, - name: "Google Cloud Secret Manager", + name: "Google Cloud Secret Manager (Used by deployed extensions and emulator)", // return type of string is not actually enforced, need to manually convert. value: SecretLocation.CLOUD.toString(), }, { checked: false, - name: "Local file (Only used by Firebase Emulator)", + name: "Local file (Used by emulator only)", value: SecretLocation.LOCAL.toString(), }, ], @@ -236,13 +236,13 @@ async function promptSecretLocations(paramSpec: Param): Promise { choices: [ { checked: false, - name: "Google Cloud Secret Manager", + name: "Google Cloud Secret Manager (Used by deployed extensions and emulator)", // return type of string is not actually enforced, need to manually convert. value: SecretLocation.CLOUD.toString(), }, { checked: false, - name: "Local file (Only used by Firebase Emulator)", + name: "Local file (Used by emulator only)", value: SecretLocation.LOCAL.toString(), }, ], From 12b63f6e1082b7f12955c1e6c6c3dc1a5897f054 Mon Sep 17 00:00:00 2001 From: abhis3 Date: Thu, 31 Mar 2022 17:05:53 -0400 Subject: [PATCH 0207/1699] Fix download token duplication on multiple setMetadata calls (#4383) * Fix download token duplication on multiple setMetadata calls --- scripts/storage-emulator-integration/tests.ts | 37 +++++++++++++++++++ src/emulator/storage/metadata.ts | 6 ++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index d4b24f8e0f9..8fb40d565ac 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -1075,6 +1075,43 @@ describe("Storage emulator", () => { expect(metadata.contentLanguage).to.equal("en"); }); + it("should not duplicate data when called repeatedly", async () => { + const destination = "public/small_file"; + await testBucket.upload(smallFilePath, { + destination, + metadata: {}, + }); + + const cloudFile = testBucket.file(destination); + const incomingMetadata = { + metadata: { + firebaseStorageDownloadTokens: "myFirstToken,mySecondToken", + }, + }; + + // Check that metadata isn't duplicated when setting multiple times in a row + await cloudFile.setMetadata(incomingMetadata); + await cloudFile.setMetadata(incomingMetadata); + await cloudFile.setMetadata(incomingMetadata); + + // Check that the tokens are saved in Firebase metadata + await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${testBucket.name}/o/${encodeURIComponent(destination)}`) + .expect(200) + .then((res) => { + const firebaseMd = res.body; + expect(firebaseMd.downloadTokens).to.equal( + incomingMetadata.metadata.firebaseStorageDownloadTokens + ); + }); + + // Check that the tokens are saved in Cloud metadata + const [storedMetadata] = await cloudFile.getMetadata(); + expect(storedMetadata.metadata.firebaseStorageDownloadTokens).to.equal( + incomingMetadata.metadata.firebaseStorageDownloadTokens + ); + }); + it("should allow fields under .metadata", async () => { await testBucket.upload(smallFilePath); const [metadata] = await testBucket diff --git a/src/emulator/storage/metadata.ts b/src/emulator/storage/metadata.ts index 99446b11fc1..533c7b49e3f 100644 --- a/src/emulator/storage/metadata.ts +++ b/src/emulator/storage/metadata.ts @@ -143,8 +143,10 @@ export class StoredFileMetadata { if (this.customMetadata.firebaseStorageDownloadTokens) { this.downloadTokens = [ - ...this.downloadTokens, - ...this.customMetadata.firebaseStorageDownloadTokens.split(","), + ...new Set([ + ...this.downloadTokens, + ...this.customMetadata.firebaseStorageDownloadTokens.split(","), + ]), ]; delete this.customMetadata.firebaseStorageDownloadTokens; } From 0ab69be1e9dbf358493e9eef21f8ac7a21cfb43d Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Thu, 31 Mar 2022 17:32:58 -0400 Subject: [PATCH 0208/1699] Fix duplicate import for Storage Emulator (#4387) * Fix duplicate import for Storage Emulator * Add integration test checking that only files are stored as blobs --- .../import/tests.ts | 31 ++++++++++++++++--- src/emulator/storage/files.ts | 4 --- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/scripts/storage-emulator-integration/import/tests.ts b/scripts/storage-emulator-integration/import/tests.ts index 14ce645d2fd..47f413b1dab 100644 --- a/scripts/storage-emulator-integration/import/tests.ts +++ b/scripts/storage-emulator-integration/import/tests.ts @@ -1,4 +1,6 @@ import * as path from "path"; +import * as fs from "fs-extra"; +import { expect } from "chai"; import supertest = require("supertest"); import { createTmpDir } from "../../../src/test/emulators/fixtures"; @@ -34,6 +36,27 @@ describe("Import Emulator Data", () => { .expect(200); }); + it("stores only the files as blobs when importing emulator data", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + const exportedData = createTmpDir("exported-emulator-data"); + + // Import data and export it again on exit + await test.startEmulators([ + "--only", + Emulators.STORAGE, + "--import", + path.join(__dirname, "nested-emulator-data"), + "--export-on-exit", + exportedData, + ]); + await test.stopEmulators(); + + expect(fs.readdirSync(path.join(exportedData, "storage_export", "blobs")).length).to.equal(1); + expect(fs.readdirSync(path.join(exportedData, "storage_export", "blobs"))[0]).to.equal( + encodeURIComponent(`${BUCKET}/test_upload.jpg`) + ); + }); + it("retrieves file from imported nested emulator data", async function (this) { this.timeout(TEST_SETUP_TIMEOUT); await test.startEmulators([ @@ -51,10 +74,10 @@ describe("Import Emulator Data", () => { it("retrieves file from importing previously exported emulator data", async function (this) { this.timeout(TEST_SETUP_TIMEOUT); - const tmpDir = createTmpDir("exported-emulator-data"); + const exportedData = createTmpDir("exported-emulator-data"); // Upload file to Storage and export emulator data to tmp directory - await test.startEmulators(["--only", Emulators.STORAGE, "--export-on-exit", tmpDir]); + await test.startEmulators(["--only", Emulators.STORAGE, "--export-on-exit", exportedData]); const uploadURL = await supertest(STORAGE_EMULATOR_HOST) .post(`/v0/b/${BUCKET}/o/test_upload.jpg?uploadType=resumable&name=test_upload.jpg`) .set({ @@ -73,8 +96,8 @@ describe("Import Emulator Data", () => { await test.stopEmulators(); - // Import emulator data from tmp directory and retrieve file from Storage - await test.startEmulators(["--only", Emulators.STORAGE, "--import", tmpDir]); + // Import previously exported emulator data and retrieve file from Storage + await test.startEmulators(["--only", Emulators.STORAGE, "--import", exportedData]); await supertest(STORAGE_EMULATOR_HOST) .get(`/v0/b/${BUCKET}/o/test_upload.jpg`) .set({ Authorization: "Bearer owner" }) diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index e762ebb1970..ec58b50a4b5 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -584,15 +584,11 @@ export class StorageLayer { } const decodedBlobPath = decodeURIComponent(blobPath); - const decodedMetadataPath = decodeURIComponent(metadataRelPath); - const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath); - const metadataDiskPath = this._persistence.getDiskPath(decodedMetadataPath); const file = new StoredFile(metadata, blobDiskPath); this._files.set(decodedBlobPath, file); - fse.copyFileSync(f, metadataDiskPath); fse.copyFileSync(blobAbsPath, blobDiskPath); } } From 8b3bdcc2b0460598e1ccec7133bfdd51c80472d2 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Thu, 31 Mar 2022 19:22:37 -0700 Subject: [PATCH 0209/1699] changing version (#4389) --- .github/workflows/node-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 11063906d25..466265e0a3f 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -25,7 +25,7 @@ jobs: cache: npm cache-dependency-path: npm-shrinkwrap.json - - run: npm i -g npm@8 + - run: npm i -g npm@8.5 - run: npm ci - run: npm run lint:changed-files @@ -45,7 +45,7 @@ jobs: cache: npm cache-dependency-path: npm-shrinkwrap.json - - run: npm i -g npm@8 + - run: npm i -g npm@8.5 - run: npm ci - run: npm test @@ -90,7 +90,7 @@ jobs: key: ${{ runner.os }}-firebase-emulators-${{ hashFiles('emulator-cache/**') }} continue-on-error: true - - run: npm i -g npm@8 + - run: npm i -g npm@8.5 - run: npm ci - run: echo ${{ secrets.service_account_json_base64 }} | base64 -d > ./scripts/service-account.json - run: ${{ matrix.script }} @@ -113,7 +113,7 @@ jobs: uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - - run: npm i -g npm@8 + - run: npm i -g npm@8.5 # --ignore-scripts prevents the `prepare` script from being run. - run: npm install --package-lock-only --ignore-scripts - run: "git diff --exit-code -- npm-shrinkwrap.json || (echo 'Error: npm-shrinkwrap.json is changed during npm install! Please make sure to use npm >= 8 and commit npm-shrinkwrap.json.' && false)" From 26442981f01c4c7590bf1df04fe84ee771364628 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 31 Mar 2022 22:52:24 -0700 Subject: [PATCH 0210/1699] Update TQ options to match the options specified in approved container contract design. (#4384) --- src/deploy/functions/backend.ts | 6 ++--- .../functions/runtimes/discovery/v1alpha1.ts | 6 ++--- src/gcp/cloudtasks.ts | 26 ++++++++++++++++--- .../runtimes/discovery/v1alpha1.spec.ts | 6 ++--- src/test/gcp/cloudtasks.spec.ts | 14 +++++++--- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 264b018daf3..b1cf043898b 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -112,10 +112,10 @@ export interface TaskQueueRateLimits { export interface TaskQueueRetryConfig { maxAttempts?: number; - maxRetryDuration?: proto.Duration; - minBackoff?: proto.Duration; - maxBackoff?: proto.Duration; + maxRetrySeconds?: number; + maxBackoffSeconds?: number; maxDoublings?: number; + minBackoffSeconds?: number; } export interface TaskQueueTrigger { diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index f4171016357..af215a675e3 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -171,9 +171,9 @@ function parseEndpoints( if (ep.taskQueueTrigger.retryConfig) { assertKeyTypes(prefix + ".taskQueueTrigger.retryConfig", ep.taskQueueTrigger.retryConfig, { maxAttempts: "number", - maxRetryDuration: "string", - minBackoff: "string", - maxBackoff: "string", + maxRetrySeconds: "number", + minBackoffSeconds: "number", + maxBackoffSeconds: "number", maxDoublings: "number", }); } diff --git a/src/gcp/cloudtasks.ts b/src/gcp/cloudtasks.ts index bcabb2f7504..2ce7e1b8328 100644 --- a/src/gcp/cloudtasks.ts +++ b/src/gcp/cloudtasks.ts @@ -154,7 +154,7 @@ const ENQUEUER_ROLE = "roles/cloudtasks.enqueuer"; export async function setEnqueuer( name: string, invoker: string[], - assumeEmpty: boolean = false + assumeEmpty = false ): Promise { let existing: iam.Policy; if (assumeEmpty) { @@ -224,10 +224,28 @@ export function queueFromEndpoint(endpoint: backend.Endpoint & backend.TaskQueue queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", - "maxBackoff", - "maxDoublings", + "maxDoublings" + ); + proto.renameIfPresent( + queue.retryConfig, + endpoint.taskQueueTrigger.retryConfig, "maxRetryDuration", - "minBackoff" + "maxRetrySeconds", + proto.durationFromSeconds + ); + proto.renameIfPresent( + queue.retryConfig, + endpoint.taskQueueTrigger.retryConfig, + "maxBackoff", + "maxBackoffSeconds", + proto.durationFromSeconds + ); + proto.renameIfPresent( + queue.retryConfig, + endpoint.taskQueueTrigger.retryConfig, + "minBackoff", + "minBackoffSeconds", + proto.durationFromSeconds ); } return queue; diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 9a26612461c..dc3ade8c77a 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -216,9 +216,9 @@ describe("backendFromV1Alpha1", () => { }, retryConfig: { maxAttempts: 3, - maxRetryDuration: "120s", - minBackoff: "1s", - maxBackoff: "30s", + maxRetrySeconds: 120, + minBackoffSeconds: 1, + maxBackoffSeconds: 30, maxDoublings: 5, }, invoker: ["custom@"], diff --git a/src/test/gcp/cloudtasks.spec.ts b/src/test/gcp/cloudtasks.spec.ts index 16567e208df..52f4a9d1bd8 100644 --- a/src/test/gcp/cloudtasks.spec.ts +++ b/src/test/gcp/cloudtasks.spec.ts @@ -44,10 +44,10 @@ describe("CloudTasks", () => { }; const retryConfig: backend.TaskQueueRetryConfig = { maxAttempts: 10, - maxBackoff: "60s", maxDoublings: 9, - maxRetryDuration: "300s", - minBackoff: "1s", + maxBackoffSeconds: 60, + maxRetrySeconds: 300, + minBackoffSeconds: 1, }; const ep: backend.Endpoint = { @@ -61,7 +61,13 @@ describe("CloudTasks", () => { expect(cloudtasks.queueFromEndpoint(ep)).to.deep.equal({ name: "projects/project/locations/region/queues/id", rateLimits, - retryConfig, + retryConfig: { + maxAttempts: 10, + maxDoublings: 9, + maxRetryDuration: "300s", + maxBackoff: "60s", + minBackoff: "1s", + }, state: "RUNNING", }); }); From 95a246baa87a7cefd37c380305b4d9760d5ee378 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 31 Mar 2022 23:03:52 -0700 Subject: [PATCH 0211/1699] Container contract updates (#4388) --- src/deploy/functions/backend.ts | 35 ++++---- src/deploy/functions/release/planner.ts | 5 +- .../functions/runtimes/discovery/v1alpha1.ts | 10 ++- src/deploy/functions/runtimes/node/index.ts | 2 +- .../functions/runtimes/node/parseTriggers.ts | 21 +---- src/deploy/functions/services/storage.ts | 10 +-- src/emulator/functionsEmulatorShared.ts | 27 ++---- src/gcp/cloudfunctions.ts | 17 +--- src/gcp/cloudfunctionsv2.ts | 38 ++++----- src/test/deploy/functions/checkIam.spec.ts | 77 +++-------------- src/test/deploy/functions/prepare.spec.ts | 16 +--- src/test/deploy/functions/prompts.spec.ts | 7 +- .../functions/release/fabricator.spec.ts | 30 ++----- .../deploy/functions/release/planner.spec.ts | 38 ++------- .../deploy/functions/release/reporter.spec.ts | 2 +- .../runtimes/discovery/v1alpha1.spec.ts | 28 +------ .../runtimes/node/parseTriggers.spec.ts | 14 +--- .../functions/services/firebaseAlerts.spec.ts | 2 +- .../functions/triggerRegionHelper.spec.ts | 53 +++--------- src/test/gcp/cloudfunctions.spec.ts | 50 ++++------- src/test/gcp/cloudfunctionsv2.spec.ts | 83 +++++++------------ 21 files changed, 151 insertions(+), 414 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index b1cf043898b..681a911797c 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -48,16 +48,7 @@ export interface CallableTriggered { callableTrigger: CallableTrigger; } -type EventFilterAttribute = "resource" | "topic" | "bucket" | string; - -// One or more event filters restrict the set of events delivered to an EventTrigger. -interface EventFilter { - attribute: EventFilterAttribute; - value: string; - - // if left unspecified, equality is used. - operator?: "match-path-pattern"; -} +type EventFilterKey = "resource" | "topic" | "bucket" | "alerttype" | "appid" | string; /** API agnostic version of a Cloud Function's event trigger. */ export interface EventTrigger { @@ -73,14 +64,20 @@ export interface EventTrigger { eventType: string; /** - * Additional filters for narrowing down which events to receive. + * Additional exact-match filters for narrowing down which events to receive. + * * While not required by the GCF API, this is always provided in * the Cloud Console, and we are likely to always require it as well. * V1 functions will always (and only) have the "resource" filter. * V2 will have arbitrary filters and some EventArc filters will be * top-level keys in the GCF API (e.g. "pubsubTopic"). */ - eventFilters: EventFilter[]; + eventFilters: Record; + + /** + * Additional path-pattern filters for narrowing down which events to receive. + */ + eventFilterPathPatterns?: Record; /** Should failures in a function execution cause an event to be retried. */ retry: boolean; @@ -98,6 +95,12 @@ export interface EventTrigger { * This field is ignored for v1 and defaults to the */ serviceAccountEmail?: string; + + /** + * The name of the channel where the function receive events. + * Must be provided to receive custom events. + */ + channel?: string; } /** Something that has an EventTrigger */ @@ -575,14 +578,6 @@ export const missingEndpoint = return !hasEndpoint(backend)(endpoint); }; -/** A helper utility to find event filter of given attribute */ -export function findEventFilter( - endpoint: Endpoint & EventTriggered, - attribute: EventFilterAttribute -): EventFilter | undefined { - return endpoint.eventTrigger.eventFilters.find((ef) => ef.attribute === attribute); -} - /** * A standard method for sorting endpoints for display. * Future versions might consider sorting region by pricing tier before diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index f6dc7b96176..3ff60bbec6e 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -196,10 +196,7 @@ export function changedV2PubSubTopic(want: backend.Endpoint, have: backend.Endpo if (have.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) { return false; } - - return ( - backend.findEventFilter(have, "topic")?.value !== backend.findEventFilter(want, "topic")?.value - ); + return have.eventTrigger.eventFilters.topic !== want.eventTrigger.eventFilters.topic; } /** Whether a user upgraded a scheduled function (which goes from Pub/Sub to HTTPS). */ diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index af215a675e3..9ad3a55358f 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -121,17 +121,19 @@ function parseEndpoints( if (backend.isEventTriggered(ep)) { requireKeys(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters"); assertKeyTypes(prefix + ".eventTrigger", ep.eventTrigger, { - eventFilters: "array", + eventFilters: "object", + eventFilterPathPatterns: "object", eventType: "string", retry: "boolean", region: "string", serviceAccountEmail: "string", + channel: "string", }); triggered = { eventTrigger: ep.eventTrigger }; - for (const eventFilter of triggered.eventTrigger.eventFilters) { - if (eventFilter.attribute === "topic" && !eventFilter.value.startsWith("projects/")) { + for (const [k, v] of Object.entries(triggered.eventTrigger.eventFilters)) { + if (k === "topic" && !v.startsWith("projects/")) { // Construct full pubsub topic name. - eventFilter.value = `projects/${project}/topics/${eventFilter.value}`; + triggered.eventTrigger.eventFilters[k] = `projects/${project}/topics/${v}`; } } } else if (backend.isHttpsTriggered(ep)) { diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 3421fd5abcd..983bef1fe7b 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -18,7 +18,7 @@ import * as validate from "./validate"; import * as versioning from "./versioning"; import * as parseTriggers from "./parseTriggers"; -const MIN_FUNCTIONS_SDK_VERSION = "3.19.0"; +const MIN_FUNCTIONS_SDK_VERSION = "3.20.0"; export async function tryCreateDelegate( context: runtimes.DelegateContext diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 711d9e01f1e..8fc8de526db 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -215,12 +215,7 @@ export function addResourcesToBackend( triggered = { eventTrigger: { eventType: annotation.eventTrigger!.eventType, - eventFilters: [ - { - attribute: "resource", - value: annotation.eventTrigger!.resource, - }, - ], + eventFilters: { resource: annotation.eventTrigger!.resource }, retry: !!annotation.failurePolicy, }, }; @@ -229,12 +224,7 @@ export function addResourcesToBackend( // once we use container contract for the functionsv2 experiment. if (annotation.platform === "gcfv2") { if (annotation.eventTrigger!.eventType === v2events.PUBSUB_PUBLISH_EVENT) { - triggered.eventTrigger.eventFilters = [ - { - attribute: "topic", - value: annotation.eventTrigger!.resource, - }, - ]; + triggered.eventTrigger.eventFilters = { topic: annotation.eventTrigger!.resource }; } if ( @@ -242,12 +232,7 @@ export function addResourcesToBackend( (event) => event === (annotation.eventTrigger?.eventType || "") ) ) { - triggered.eventTrigger.eventFilters = [ - { - attribute: "bucket", - value: annotation.eventTrigger!.resource, - }, - ]; + triggered.eventTrigger.eventFilters = { bucket: annotation.eventTrigger!.resource }; } } } diff --git a/src/deploy/functions/services/storage.ts b/src/deploy/functions/services/storage.ts index a6103d1c97c..51e11685f65 100644 --- a/src/deploy/functions/services/storage.ts +++ b/src/deploy/functions/services/storage.ts @@ -43,14 +43,10 @@ export async function ensureStorageTriggerRegion( const { eventTrigger } = endpoint; if (!eventTrigger.region) { logger.debug("Looking up bucket region for the storage event trigger"); - const bucketFilter = backend.findEventFilter(endpoint, "bucket"); - if (!bucketFilter) { - throw new FirebaseError( - "Storage event trigger unexpectedly missing event filter with bucket attribute." - ); - } try { - const bucket: { location: string } = await storage.getBucket(bucketFilter.value); + const bucket: { location: string } = await storage.getBucket( + eventTrigger.eventFilters.bucket! + ); eventTrigger.region = bucket.location.toLowerCase(); logger.debug("Setting the event trigger region to", eventTrigger.region, "."); } catch (err: any) { diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index f20620560a8..baacca10fed 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -168,34 +168,21 @@ export function emulatedFunctionsFromEndpoints( } else if (backend.isEventTriggered(endpoint)) { const eventTrigger = endpoint.eventTrigger; if (endpoint.platform === "gcfv1") { - const resourceFilter = backend.findEventFilter(endpoint, "resource"); - if (!resourceFilter) { - logger.debug( - `Invalid event trigger ${JSON.stringify( - endpoint - )}, expected event filter with resource attribute. Skipping.` - ); - // Silently skip invalid trigger. - continue; - } def.eventTrigger = { eventType: eventTrigger.eventType, - resource: resourceFilter.value, + resource: eventTrigger.eventFilters.resource, }; } else { - const [eventFilter] = endpoint.eventTrigger.eventFilters; - if (!eventFilter) { - logger.debug( - `Invalid event trigger ${JSON.stringify( - endpoint - )}, expected at least one event filter. Skipping.` - ); - // Silently skip invalid trigger. + // Only pubsub and storage events are supported for gcfv2. + const { resource, topic, bucket } = endpoint.eventTrigger.eventFilters; + const eventResource = resource || topic || bucket; + if (!eventResource) { + // Unsupported event type for GCFv2 continue; } def.eventTrigger = { eventType: eventTrigger.eventType, - resource: eventFilter.value, + resource: eventResource, }; } } else if (backend.isScheduleTriggered(endpoint)) { diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 2f4d99f650d..2efed841e4b 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -10,8 +10,6 @@ import * as runtimes from "../deploy/functions/runtimes"; import * as iam from "./iam"; import { Client } from "../apiv2"; import { functionsOrigin } from "../api"; -import { getFirebaseProject } from "../management/projects"; -import { assertExhaustive } from "../functional"; export const API_VERSION = "v1"; const client = new Client({ urlPrefix: functionsOrigin, apiVersion: API_VERSION }); @@ -497,12 +495,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { eventTrigger: { eventType: gcfFunction.eventTrigger!.eventType, - eventFilters: [ - { - attribute: "resource", - value: gcfFunction.eventTrigger!.resource, - }, - ], + eventFilters: { resource: gcfFunction.eventTrigger!.resource }, retry: !!gcfFunction.eventTrigger!.failurePolicy?.retry, }, }; @@ -581,15 +574,9 @@ export function functionFromEndpoint( proto.copyIfPresent(gcfFunction, endpoint, "labels"); if (backend.isEventTriggered(endpoint)) { - const resourceFilter = backend.findEventFilter(endpoint, "resource"); - if (!resourceFilter) { - throw new FirebaseError( - "Invalid event trigger definition. Expected event filter with 'resource' attribute." - ); - } gcfFunction.eventTrigger = { eventType: endpoint.eventTrigger.eventType, - resource: resourceFilter.value, + resource: endpoint.eventTrigger.eventFilters.resource, // Service is unnecessary and deprecated }; diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index a34b246ae27..a05a7815ae4 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -435,25 +435,17 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage eventType: endpoint.eventTrigger.eventType, }; if (gcfFunction.eventTrigger.eventType === PUBSUB_PUBLISH_EVENT) { - const pubsubFilter = backend.findEventFilter(endpoint, "topic"); - if (!pubsubFilter) { - throw new FirebaseError( - "Invalid pubsub endpoint. Expected eventFilter with 'topic' attribute but found none." - ); - } - gcfFunction.eventTrigger.pubsubTopic = pubsubFilter.value; - - for (const filter of endpoint.eventTrigger.eventFilters) { - if (filter.attribute === "topic") { - continue; - } - if (!gcfFunction.eventTrigger.eventFilters) { - gcfFunction.eventTrigger.eventFilters = []; - } - gcfFunction.eventTrigger.eventFilters.push(filter); + gcfFunction.eventTrigger.pubsubTopic = endpoint.eventTrigger.eventFilters.topic; + gcfFunction.eventTrigger.eventFilters = []; + for (const [attribute, value] of Object.entries(endpoint.eventTrigger.eventFilters)) { + if (attribute === "topic") continue; + gcfFunction.eventTrigger.eventFilters.push({ attribute, value }); } } else { - gcfFunction.eventTrigger.eventFilters = endpoint.eventTrigger.eventFilters; + gcfFunction.eventTrigger.eventFilters = []; + for (const [attribute, value] of Object.entries(endpoint.eventTrigger.eventFilters)) { + gcfFunction.eventTrigger.eventFilters.push({ attribute, value }); + } } proto.renameIfPresent( gcfFunction.eventTrigger, @@ -485,6 +477,9 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage return gcfFunction; } +/** + * + */ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoint { const [, project, , region, , id] = gcfFunction.name.split("/"); let trigger: backend.Triggered; @@ -504,18 +499,15 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { eventTrigger: { eventType: gcfFunction.eventTrigger.eventType, - eventFilters: [], + eventFilters: {}, retry: false, }, }; if (gcfFunction.eventTrigger.pubsubTopic) { - trigger.eventTrigger.eventFilters.push({ - attribute: "topic", - value: gcfFunction.eventTrigger.pubsubTopic, - }); + trigger.eventTrigger.eventFilters.topic = gcfFunction.eventTrigger.pubsubTopic; } else { for (const { attribute, value } of gcfFunction.eventTrigger.eventFilters || []) { - trigger.eventTrigger.eventFilters.push({ attribute, value }); + trigger.eventTrigger.eventFilters[attribute] = value; } } proto.renameIfPresent( diff --git a/src/test/deploy/functions/checkIam.spec.ts b/src/test/deploy/functions/checkIam.spec.ts index c81b74bbee8..617dd45717c 100644 --- a/src/test/deploy/functions/checkIam.spec.ts +++ b/src/test/deploy/functions/checkIam.spec.ts @@ -257,12 +257,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, @@ -284,12 +279,7 @@ describe("checkIam", () => { platform: "gcfv1", eventTrigger: { eventType: "google.storage.object.create", - eventFilters: [ - { - attribute: "resource", - value: "projects/_/buckets/myBucket", - }, - ], + eventFilters: { resource: "projects/_/buckets/my-bucket" }, retry: false, }, ...SPEC, @@ -307,12 +297,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, @@ -336,12 +321,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, @@ -352,12 +332,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.metadataUpdated", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, @@ -415,12 +390,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, @@ -460,12 +430,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, @@ -476,12 +441,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.firebase.firebasealerts.alerts.v1.published", - eventFilters: [ - { - attribute: "alerttype", - value: "crashlytics.newFatalIssue", - }, - ], + eventFilters: { alertype: "crashlytics.newFatalIssue" }, retry: false, }, ...SPEC, @@ -535,12 +495,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.firebase.firebasealerts.alerts.v1.published", - eventFilters: [ - { - attribute: "alerttype", - value: "crashlytics.newFatalIssue", - }, - ], + eventFilters: { alertype: "crashlytics.newFatalIssue" }, retry: false, }, ...SPEC, @@ -571,12 +526,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.firebase.firebasealerts.alerts.v1.published", - eventFilters: [ - { - attribute: "alerttype", - value: "crashlytics.newFatalIssue", - }, - ], + eventFilters: { alertype: "crashlytics.newFatalIssue" }, retry: false, }, ...SPEC, @@ -587,12 +537,7 @@ describe("checkIam", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, diff --git a/src/test/deploy/functions/prepare.spec.ts b/src/test/deploy/functions/prepare.spec.ts index e2234f563c5..38ba2a2cc80 100644 --- a/src/test/deploy/functions/prepare.spec.ts +++ b/src/test/deploy/functions/prepare.spec.ts @@ -79,12 +79,7 @@ describe("prepare", () => { ...ENDPOINT_BASE, eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "bucket", - }, - ], + eventFilters: { bucket: "bucket" }, retry: false, }, }; @@ -101,18 +96,13 @@ describe("prepare", () => { ...ENDPOINT_BASE, eventTrigger: { eventType: "google.cloud.storage.object.v1.finalzied", - eventFilters: [ - { - attribute: "bucket", - value: "us-bucket", - }, - ], + eventFilters: { bucket: "us-bucket" }, retry: false, }, }; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const have: backend.Endpoint & backend.EventTriggered = JSON.parse(JSON.stringify(want)); - have.eventTrigger.eventFilters = [{ attribute: "bucket", value: "us-central1-bucket" }]; + have.eventTrigger.eventFilters = { bucket: "us-central1-bucket" }; have.eventTrigger.region = "us-central1"; prepare.inferDetailsFromExisting(backend.of(want), backend.of(have), /* usedDotEnv= */ false); diff --git a/src/test/deploy/functions/prompts.spec.ts b/src/test/deploy/functions/prompts.spec.ts index 42b11146aa7..cea07f0bce6 100644 --- a/src/test/deploy/functions/prompts.spec.ts +++ b/src/test/deploy/functions/prompts.spec.ts @@ -11,12 +11,7 @@ import { RC } from "../../../rc"; const SAMPLE_EVENT_TRIGGER: backend.EventTrigger = { eventType: "google.pubsub.topic.publish", - eventFilters: [ - { - attribute: "resource", - value: "projects/a/topics/b", - }, - ], + eventFilters: { resource: "projects/a/topics/b" }, retry: false, }; diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 6ea04040709..67907ef1ba8 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -275,7 +275,7 @@ describe("Fabricator", () => { const ep1 = endpoint({ eventTrigger: { eventType: "some.event", - eventFilters: [{ attribute: "resource", value: "some-resource" }], + eventFilters: { resource: "some-resource" }, retry: false, }, }); @@ -400,12 +400,7 @@ describe("Fabricator", () => { { eventTrigger: { eventType: v2events.PUBSUB_PUBLISH_EVENT, - eventFilters: [ - { - attribute: "topic", - value: "topic", - }, - ], + eventFilters: { topic: "topic" }, retry: false, }, }, @@ -426,12 +421,7 @@ describe("Fabricator", () => { { eventTrigger: { eventType: v2events.PUBSUB_PUBLISH_EVENT, - eventFilters: [ - { - attribute: "topic", - value: "topic", - }, - ], + eventFilters: { topic: "topic" }, retry: false, }, }, @@ -889,12 +879,7 @@ describe("Fabricator", () => { const ep = endpoint({ eventTrigger: { eventType: v2events.PUBSUB_PUBLISH_EVENT, - eventFilters: [ - { - attribute: "topic", - value: "topic", - }, - ], + eventFilters: { topic: "topic" }, retry: false, }, }); @@ -945,12 +930,7 @@ describe("Fabricator", () => { const ep = endpoint({ eventTrigger: { eventType: v2events.PUBSUB_PUBLISH_EVENT, - eventFilters: [ - { - attribute: "topic", - value: "topic", - }, - ], + eventFilters: { topic: "topic" }, retry: false, }, }); diff --git a/src/test/deploy/functions/release/planner.spec.ts b/src/test/deploy/functions/release/planner.spec.ts index 38fae238ebb..e112ad73ae2 100644 --- a/src/test/deploy/functions/release/planner.spec.ts +++ b/src/test/deploy/functions/release/planner.spec.ts @@ -51,12 +51,7 @@ describe("planner", () => { ...func("a", "b", { eventTrigger: { eventType: v2events.PUBSUB_PUBLISH_EVENT, - eventFilters: [ - { - attribute: "topic", - value: "topic", - }, - ], + eventFilters: { topic: "topic" }, retry: false, }, }), @@ -64,7 +59,7 @@ describe("planner", () => { }; const changed = JSON.parse(JSON.stringify(original)) as backend.Endpoint; if (backend.isEventTriggered(changed)) { - changed.eventTrigger.eventFilters = [{ attribute: "topic", value: "anotherTopic" }]; + changed.eventTrigger.eventFilters = { topic: "anotherTopic" }; } expect(planner.calculateUpdate(changed, original)).to.deep.equal({ endpoint: changed, @@ -90,12 +85,7 @@ describe("planner", () => { const original: backend.Endpoint = func("a", "b", { eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, region: "us-west1", retry: false, }, @@ -104,12 +94,7 @@ describe("planner", () => { const changed: backend.Endpoint = func("a", "b", { eventTrigger: { eventType: "google.cloud.storage.object.v1.finalzied", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, region: "us", retry: false, }, @@ -319,7 +304,7 @@ describe("planner", () => { const want = func("a", "b", { eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: [], + eventFilters: {}, retry: false, }, }); @@ -333,7 +318,7 @@ describe("planner", () => { const have = func("a", "b", { eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: [], + eventFilters: {}, retry: false, }, }); @@ -351,7 +336,7 @@ describe("planner", () => { it("should not throw if a event triggered function keeps the same trigger", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.publish", - eventFilters: [], + eventFilters: {}, retry: false, }; const want = func("a", "b", { eventTrigger }); @@ -384,12 +369,7 @@ describe("planner", () => { it("detects changes to v2 pubsub topics", () => { const eventTrigger: backend.EventTrigger = { eventType: v2events.PUBSUB_PUBLISH_EVENT, - eventFilters: [ - { - attribute: "topic", - value: "projects/p/topic/t", - }, - ], + eventFilters: { topic: "projects/p/topics/t" }, retry: false, }; @@ -419,7 +399,7 @@ describe("planner", () => { // to modify only 'want' want = JSON.parse(JSON.stringify(want)) as backend.Endpoint; if (backend.isEventTriggered(want)) { - want.eventTrigger.eventFilters = [{ attribute: "topic", value: "projects/p/topics/t2" }]; + want.eventTrigger.eventFilters = { topic: "projects/p/topics/t2" }; } expect(planner.changedV2PubSubTopic(want, have)).to.be.true; }); diff --git a/src/test/deploy/functions/release/reporter.spec.ts b/src/test/deploy/functions/release/reporter.spec.ts index 7df26b767e1..d2727022e7d 100644 --- a/src/test/deploy/functions/release/reporter.spec.ts +++ b/src/test/deploy/functions/release/reporter.spec.ts @@ -88,7 +88,7 @@ describe("reporter", () => { platform: "gcfv2", eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: [], + eventFilters: {}, retry: false, }, }) diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index dc3ade8c77a..ccdba80201f 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -99,12 +99,7 @@ describe("backendFromV1Alpha1", () => { describe("Event triggers", () => { const validTrigger: backend.EventTrigger = { eventType: "google.pubsub.v1.topic.publish", - eventFilters: [ - { - attribute: "resource", - value: "projects/p/topics/t", - }, - ], + eventFilters: { resource: "projects/p/topics/t" }, retry: true, region: "global", serviceAccountEmail: "root@", @@ -325,12 +320,7 @@ describe("backendFromV1Alpha1", () => { it("copies event triggers", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.v1.publish", - eventFilters: [ - { - attribute: "resource", - value: "projects/project/topics/topic", - }, - ], + eventFilters: { resource: "projects/project/topics/t" }, region: "us-central1", serviceAccountEmail: "sa@", retry: true, @@ -352,12 +342,7 @@ describe("backendFromV1Alpha1", () => { it("copies event triggers with full resource path", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.v1.publish", - eventFilters: [ - { - attribute: "topic", - value: "my-topic", - }, - ], + eventFilters: { topic: "my-topic" }, region: "us-central1", serviceAccountEmail: "sa@", retry: true, @@ -375,12 +360,7 @@ describe("backendFromV1Alpha1", () => { ...DEFAULTED_ENDPOINT, eventTrigger: { ...eventTrigger, - eventFilters: [ - { - attribute: "topic", - value: `projects/${PROJECT}/topics/my-topic`, - }, - ], + eventFilters: { topic: `projects/${PROJECT}/topics/my-topic` }, }, }); const parsed = v1alpha1.backendFromV1Alpha1(yaml, PROJECT, REGION, RUNTIME); diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index 968c3782c10..cc2bd4ec6d1 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -128,12 +128,7 @@ describe("addResourcesToBackend", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.publish", - eventFilters: [ - { - attribute: "resource", - value: "projects/project/topics/topic", - }, - ], + eventFilters: { resource: "projects/project/topics/topic" }, retry: !!failurePolicy, }; const expected: backend.Backend = backend.of({ ...BASIC_ENDPOINT, eventTrigger }); @@ -202,12 +197,7 @@ describe("addResourcesToBackend", () => { const eventTrigger: backend.EventTrigger = { eventType: "google.pubsub.topic.publish", - eventFilters: [ - { - attribute: "resource", - value: "projects/p/topics/t", - }, - ], + eventFilters: { resource: "projects/p/topics/t" }, retry: false, }; diff --git a/src/test/deploy/functions/services/firebaseAlerts.spec.ts b/src/test/deploy/functions/services/firebaseAlerts.spec.ts index 7178c64ae67..e93e11b900d 100644 --- a/src/test/deploy/functions/services/firebaseAlerts.spec.ts +++ b/src/test/deploy/functions/services/firebaseAlerts.spec.ts @@ -11,7 +11,7 @@ const endpoint: Endpoint = { eventTrigger: { retry: false, eventType: "firebase.firebasealerts.alerts.v1.published", - eventFilters: [], + eventFilters: {}, }, entryPoint: "endpoint", platform: "gcfv2", diff --git a/src/test/deploy/functions/triggerRegionHelper.spec.ts b/src/test/deploy/functions/triggerRegionHelper.spec.ts index c58ffbd7b14..e0c41277538 100644 --- a/src/test/deploy/functions/triggerRegionHelper.spec.ts +++ b/src/test/deploy/functions/triggerRegionHelper.spec.ts @@ -30,12 +30,7 @@ describe("TriggerRegionHelper", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, @@ -53,12 +48,7 @@ describe("TriggerRegionHelper", () => { platform: "gcfv1", eventTrigger: { eventType: "google.storage.object.create", - eventFilters: [ - { - attribute: "resource", - value: "projects/_/buckets/my-bucket", - }, - ], + eventFilters: { resource: "projects/_/buckets/my-bucket" }, retry: false, }, ...SPEC, @@ -73,16 +63,13 @@ describe("TriggerRegionHelper", () => { await triggerRegionHelper.ensureTriggerRegions(backend.of(v1EventFn, v2CallableFn)); - expect(v1EventFn.eventTrigger).to.deep.eq({ + const want: backend.EventTrigger = { eventType: "google.storage.object.create", - eventFilters: [ - { - attribute: "resource", - value: "projects/_/buckets/my-bucket", - }, - ], + eventFilters: { resource: "projects/_/buckets/my-bucket" }, retry: false, - }); + }; + + expect(v1EventFn.eventTrigger).to.deep.eq(want); expect(v2CallableFn.httpsTrigger).to.deep.eq({}); }); @@ -94,12 +81,7 @@ describe("TriggerRegionHelper", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, @@ -107,17 +89,13 @@ describe("TriggerRegionHelper", () => { await triggerRegionHelper.ensureTriggerRegions(backend.of(wantFn)); - expect(wantFn.eventTrigger).to.deep.eq({ + const want: backend.EventTrigger = { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, region: "us", - }); + }; + expect(wantFn.eventTrigger).to.deep.eq(want); }); it("should set trigger region from API then reject on invalid function region", async () => { @@ -128,12 +106,7 @@ describe("TriggerRegionHelper", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: [ - { - attribute: "bucket", - value: "my-bucket", - }, - ], + eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index d0311b5c5b6..1ac3886ee9c 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -68,12 +68,7 @@ describe("cloudfunctions", () => { ...ENDPOINT, eventTrigger: { eventType: "google.pubsub.topic.publish", - eventFilters: [ - { - attribute: "resource", - value: "projects/p/topics/t", - }, - ], + eventFilters: { resource: "projects/p/topics/t" }, retry: false, }, }; @@ -196,6 +191,14 @@ describe("cloudfunctions", () => { }); it("should translate event triggers", () => { + let want: backend.Endpoint = { + ...ENDPOINT, + eventTrigger: { + eventType: "google.pubsub.topic.publish", + eventFilters: { resource: "projects/p/topics/t" }, + retry: true, + }, + }; expect( cloudfunctions.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION, @@ -207,21 +210,16 @@ describe("cloudfunctions", () => { }, }, }) - ).to.deep.equal({ - ...ENDPOINT, - eventTrigger: { - eventType: "google.pubsub.topic.publish", - eventFilters: [ - { - attribute: "resource", - value: "projects/p/topics/t", - }, - ], - retry: true, - }, - }); + ).to.deep.equal(want); // And again w/o the failure policy + want = { + ...want, + eventTrigger: { + ...want.eventTrigger, + retry: false, + }, + }; expect( cloudfunctions.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION, @@ -230,19 +228,7 @@ describe("cloudfunctions", () => { resource: "projects/p/topics/t", }, }) - ).to.deep.equal({ - ...ENDPOINT, - eventTrigger: { - eventType: "google.pubsub.topic.publish", - eventFilters: [ - { - attribute: "resource", - value: "projects/p/topics/t", - }, - ], - retry: false, - }, - }); + ).to.deep.equal(want); }); it("should transalte scheduled triggers", () => { diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 7bd184470e0..b27cf094cc5 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -93,16 +93,10 @@ describe("cloudfunctionsv2", () => { platform: "gcfv2", eventTrigger: { eventType: "google.cloud.audit.log.v1.written", - eventFilters: [ - { - attribute: "resource", - value: "projects/p/regions/r/instances/i", - }, - { - attribute: "serviceName", - value: "compute.googleapis.com", - }, - ], + eventFilters: { + resource: "projects/p/regions/r/instances/i", + serviceName: "compute.googleapis.com", + }, retry: false, }, }; @@ -200,16 +194,10 @@ describe("cloudfunctionsv2", () => { platform: "gcfv2", eventTrigger: { eventType: v2events.PUBSUB_PUBLISH_EVENT, - eventFilters: [ - { - attribute: "topic", - value: "projects/p/topics/t", - }, - { - attribute: "serviceName", - value: "pubsub.googleapis.com", - }, - ], + eventFilters: { + topic: "projects/p/topics/t", + serviceName: "pubsub.googleapis.com", + }, retry: false, }, maxInstances: 42, @@ -260,6 +248,16 @@ describe("cloudfunctionsv2", () => { }); it("should translate event triggers", () => { + let want: backend.Endpoint = { + ...ENDPOINT, + platform: "gcfv2", + uri: RUN_URI, + eventTrigger: { + eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventFilters: { topic: "projects/p/topics/t" }, + retry: false, + }, + }; expect( cloudfunctionsv2.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION_V2, @@ -268,23 +266,20 @@ describe("cloudfunctionsv2", () => { pubsubTopic: "projects/p/topics/t", }, }) - ).to.deep.equal({ - ...ENDPOINT, - platform: "gcfv2", - uri: RUN_URI, + ).to.deep.equal(want); + + // And again w/ a normal event trigger + want = { + ...want, eventTrigger: { - eventType: v2events.PUBSUB_PUBLISH_EVENT, - eventFilters: [ - { - attribute: "topic", - value: "projects/p/topics/t", - }, - ], + eventType: "google.cloud.audit.log.v1.written", + eventFilters: { + resource: "projects/p/regions/r/instances/i", + serviceName: "compute.googleapis.com", + }, retry: false, }, - }); - - // And again w/ a normal event trigger + }; expect( cloudfunctionsv2.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION_V2, @@ -302,25 +297,7 @@ describe("cloudfunctionsv2", () => { ], }, }) - ).to.deep.equal({ - ...ENDPOINT, - platform: "gcfv2", - uri: RUN_URI, - eventTrigger: { - eventType: "google.cloud.audit.log.v1.written", - eventFilters: [ - { - attribute: "resource", - value: "projects/p/regions/r/instances/i", - }, - { - attribute: "serviceName", - value: "compute.googleapis.com", - }, - ], - retry: false, - }, - }); + ).to.deep.equal(want); }); it("should translate task queue functions", () => { From 226a0c229e13929d1a19c07ff917b1888cd3f025 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 1 Apr 2022 10:54:11 -0700 Subject: [PATCH 0212/1699] time is now a number. Cleaned up code (#4381) * time is now a number. Cleaned up code * Add more type support to cloneDeep * Changelog Co-authored-by: Daniel Lee --- CHANGELOG.md | 1 + .../functionsEmulatorRuntime.spec.ts | 5 +- src/deploy/functions/backend.ts | 2 +- .../functions/runtimes/discovery/v1alpha1.ts | 4 +- .../functions/runtimes/node/parseTriggers.ts | 8 ++- src/emulator/functionsEmulatorShared.ts | 10 +-- src/extensions/emulator/optionsHelper.ts | 15 ++--- src/extensions/emulator/triggerHelper.ts | 31 +++++----- src/extensions/extensionsApi.ts | 35 +++++++++-- src/extensions/paramHelper.ts | 36 ++++++----- src/gcp/cloudfunctions.ts | 16 ++++- src/gcp/cloudfunctionsv2.ts | 20 ++---- .../runtimes/discovery/v1alpha1.spec.ts | 4 +- .../runtimes/node/parseTriggers.spec.ts | 9 ++- .../extensions/billingMigrationHelper.spec.ts | 17 +++--- .../extensions/displayExtensionInfo.spec.ts | 61 ++++++++++--------- .../extensions/emulator/triggerHelper.spec.ts | 20 +++--- src/test/extensions/extensionsApi.spec.ts | 4 +- src/test/extensions/paramHelper.spec.ts | 16 ++--- src/test/extensions/updateHelper.spec.ts | 4 +- src/test/gcp/cloudfunctions.spec.ts | 24 ++++++-- src/test/gcp/cloudfunctionsv2.spec.ts | 5 +- src/test/gcp/cloudscheduler.spec.ts | 10 +-- src/utils.ts | 35 +++++++++++ 24 files changed, 241 insertions(+), 151 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 856cadb298d..6a1df746079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - Fixes Storage Emulator ruleset file watcher (#4337). - Fixes issue with importing Storage Emulator data exported prior to v10.3.0 (#4358). - Adds ergonomic improvements to CF3 secret commands to automatically redeploy functions and delete unused secrets (#4130). +- Fixes issue with alpha users setting timeouts (#4381) diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index 3d9c9f45c73..3493fc8e6e8 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -2,7 +2,6 @@ import { Change } from "firebase-functions"; import { DocumentSnapshot } from "firebase-functions/lib/providers/firestore"; import { expect } from "chai"; import { IncomingMessage, request } from "http"; -import * as _ from "lodash"; import * as express from "express"; import * as fs from "fs"; import * as sinon from "sinon"; @@ -12,7 +11,7 @@ import { FunctionRuntimeBundles, TIMEOUT_LONG, TIMEOUT_MED, MODULE_ROOT } from " import { FunctionsRuntimeBundle, SignatureType } from "../../src/emulator/functionsEmulatorShared"; import { InvokeRuntimeOpts, FunctionsEmulator } from "../../src/emulator/functionsEmulator"; import { RuntimeWorker } from "../../src/emulator/functionsRuntimeWorker"; -import { streamToString } from "../../src/utils"; +import { streamToString, cloneDeep } from "../../src/utils"; import * as registry from "../../src/emulator/registry"; const DO_NOTHING = () => { @@ -449,7 +448,7 @@ describe("FunctionsEmulator-Runtime", () => { }).timeout(TIMEOUT_MED); it("should return a real databaseURL when RTDB emulator is not running", async () => { - const frb = _.cloneDeep(FunctionRuntimeBundles.onRequest); + const frb = cloneDeep(FunctionRuntimeBundles.onRequest); const worker = await invokeFunction( frb, () => { diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 681a911797c..1192d7f66d9 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -209,7 +209,7 @@ export interface ServiceConfiguration { environmentVariables?: Record; secretEnvironmentVariables?: SecretEnvVar[]; availableMemoryMb?: MemoryOptions; - timeout?: proto.Duration; + timeoutSeconds?: number; maxInstances?: number; minInstances?: number; vpc?: { diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 9ad3a55358f..89f78aa9523 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -82,7 +82,7 @@ function parseEndpoints( minInstances: "number", concurrency: "number", serviceAccountEmail: "string", - timeout: "string", + timeoutSeconds: "number", vpc: "object", labels: "object", ingressSettings: "string", @@ -205,7 +205,7 @@ function parseEndpoints( "minInstances", "concurrency", "serviceAccountEmail", - "timeout", + "timeoutSeconds", "vpc", "labels", "ingressSettings", diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 8fc8de526db..4c5d0ae1a4f 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -280,11 +280,17 @@ export function addResourcesToBackend( "serviceAccountEmail", "labels", "ingressSettings", - "timeout", "maxInstances", "minInstances", "availableMemoryMb" ); + proto.renameIfPresent( + endpoint, + annotation, + "timeoutSeconds", + "timeout", + proto.secondsFromDuration + ); want.endpoints[region] = want.endpoints[region] || {}; want.endpoints[region][endpoint.id] = endpoint; diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index baacca10fed..6c7009fa6dd 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -21,7 +21,7 @@ export interface ParsedTriggerDefinition { entryPoint: string; platform: backend.FunctionsPlatform; name: string; - timeout?: string | number; // Can be "3s" for some reason lol + timeoutSeconds?: number; regions?: string[]; availableMemoryMb?: "128MB" | "256MB" | "512MB" | "1GB" | "2GB" | "4GB"; httpsTrigger?: any; @@ -109,11 +109,7 @@ export class EmulatedTrigger { } get timeoutMs(): number { - if (typeof this.definition.timeout === "number") { - return this.definition.timeout * 1000; - } else { - return parseInt((this.definition.timeout || "60s").split("s")[0], 10) * 1000; - } + return (this.definition.timeoutSeconds || 60) * 1000; } getRawFunction(): CloudFunction { @@ -152,9 +148,9 @@ export function emulatedFunctionsFromEndpoints( copyIfPresent( def, endpoint, - "timeout", "availableMemoryMb", "labels", + "timeoutSeconds", "platform", "secretEnvironmentVariables" ); diff --git a/src/extensions/emulator/optionsHelper.ts b/src/extensions/emulator/optionsHelper.ts index 5046d0b4fc4..8af9c752a0a 100644 --- a/src/extensions/emulator/optionsHelper.ts +++ b/src/extensions/emulator/optionsHelper.ts @@ -1,5 +1,4 @@ import * as fs from "fs-extra"; -import * as _ from "lodash"; import { ParsedTriggerDefinition } from "../../emulator/functionsEmulatorShared"; import * as path from "path"; import * as paramHelper from "../paramHelper"; @@ -25,10 +24,7 @@ export async function buildOptions(options: any): Promise { extensionsHelper.validateCommandLineParams(params, spec.params); - const functionResources = specHelper.getFunctionResourcesWithParamSubstitution( - spec, - params - ) as Resource[]; + const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, params); let testConfig; if (options.testConfig) { testConfig = readTestConfigFile(options.testConfig); @@ -241,11 +237,8 @@ function buildConfig( function getFunctionSourceDirectory(functionResources: Resource[]): string { let sourceDirectory; for (const r of functionResources) { - let dir = _.get(r, "properties.sourceDirectory"); // If not specified, default sourceDirectory to "functions" - if (!dir) { - dir = "functions"; - } + const dir = r.properties?.sourceDirectory || "functions"; if (!sourceDirectory) { sourceDirectory = dir; } else if (sourceDirectory !== dir) { @@ -254,7 +247,7 @@ function getFunctionSourceDirectory(functionResources: Resource[]): string { ); } } - return sourceDirectory; + return sourceDirectory || "functions"; } function shouldEmulateFunctions(resources: Resource[]): boolean { @@ -263,7 +256,7 @@ function shouldEmulateFunctions(resources: Resource[]): boolean { function shouldEmulate(emulatorName: string, resources: Resource[]): boolean { for (const r of resources) { - const eventType: string = _.get(r, "properties.eventTrigger.eventType", ""); + const eventType: string = r.properties?.eventTrigger?.eventType || ""; if (eventType.includes(emulatorName)) { return true; } diff --git a/src/extensions/emulator/triggerHelper.ts b/src/extensions/emulator/triggerHelper.ts index 91f2526d1fb..1f77c0308ad 100644 --- a/src/extensions/emulator/triggerHelper.ts +++ b/src/extensions/emulator/triggerHelper.ts @@ -1,32 +1,33 @@ -import * as _ from "lodash"; import { ParsedTriggerDefinition, getServiceFromEventType, } from "../../emulator/functionsEmulatorShared"; import { EmulatorLogger } from "../../emulator/emulatorLogger"; import { Emulators } from "../../emulator/types"; +import * as extensionsApi from "../../extensions/extensionsApi"; +import * as proto from "../../gcp/proto"; -export function functionResourceToEmulatedTriggerDefintion(resource: any): ParsedTriggerDefinition { +export function functionResourceToEmulatedTriggerDefintion( + resource: extensionsApi.Resource +): ParsedTriggerDefinition { const etd: ParsedTriggerDefinition = { name: resource.name, entryPoint: resource.name, platform: "gcfv1", }; - const properties = _.get(resource, "properties", {}); - if (properties.timeout) { - etd.timeout = properties.timeout; - } - if (properties.location) { - etd.regions = [properties.location]; - } - if (properties.availableMemoryMb) { - etd.availableMemoryMb = properties.availableMemoryMb; - } + const properties = resource.properties || {}; + proto.renameIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration); + proto.renameIfPresent(etd, properties, "regions", "location", (str: string) => [str]); + proto.copyIfPresent(etd, properties, "availableMemoryMb"); if (properties.httpsTrigger) { etd.httpsTrigger = properties.httpsTrigger; - } else if (properties.eventTrigger) { - properties.eventTrigger.service = getServiceFromEventType(properties.eventTrigger.eventType); - etd.eventTrigger = properties.eventTrigger; + } + if (properties.eventTrigger) { + etd.eventTrigger = { + eventType: properties.eventTrigger.eventType, + resource: properties.eventTrigger.resource, + service: getServiceFromEventType(properties.eventTrigger.eventType), + }; } else { EmulatorLogger.forEmulator(Emulators.FUNCTIONS).log( "WARN", diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index 5eb7b826339..1cad070f076 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -10,7 +10,11 @@ import { FirebaseError } from "../error"; import { logger } from "../logger"; import * as operationPoller from "../operation-poller"; import * as refs from "./refs"; +import * as proto from "../gcp/proto"; import { SpecParamType } from "./extensionsHelper"; +import { Runtime } from "../deploy/functions/runtimes"; +import { HttpsTriggered, EventTriggered } from "../deploy/functions/backend"; +import { StringifyOptions } from "querystring"; const VERSION = "v1beta"; const PAGE_SIZE_MAX = 100; @@ -132,13 +136,36 @@ export interface Role { reason: string; } -export interface Resource { +// Docs at https://firebase.google.com/docs/extensions/alpha/ref-extension-yaml +export const FUNCTIONS_RESOURCE_TYPE = "firebaseextensions.v1beta.function"; +export interface FunctionResourceProperties { + type: typeof FUNCTIONS_RESOURCE_TYPE; + properties?: { + location?: string; + entryPoint?: string; + sourceDirectory?: string; + timeout?: proto.Duration; + availableMemoryMb?: number; + runtime?: Runtime; + httpsTrigger?: Record; + eventTrigger?: { + eventType: string; + resource: string; + service?: string; + }; + }; +} + +// Union of all valid property types so we can have a strongly typed "property" +// field depending on the actual value of "type" +type ResourceProperties = FunctionResourceProperties; + +export type Resource = ResourceProperties & { name: string; - type: string; description?: string; - properties?: { [key: string]: any }; propertiesYaml?: string; -} + entryPoint?: string; +}; export interface Author { authorName: string; diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index a2c46ed374c..637e918124a 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -1,4 +1,3 @@ -import * as _ from "lodash"; import * as path from "path"; import * as clc from "cli-color"; import * as fs from "fs-extra"; @@ -15,6 +14,7 @@ import { import * as askUserForParam from "./askUserForParam"; import * as track from "../track"; import * as env from "../functions/env"; +import { cloneDeep } from "../utils"; /** * Interface for holding different param values for different environments/configs. @@ -80,8 +80,8 @@ export function setNewDefaults( export function getParamsWithCurrentValuesAsDefaults( extensionInstance: extensionsApi.ExtensionInstance ): extensionsApi.Param[] { - const specParams = _.cloneDeep(_.get(extensionInstance, "config.source.spec.params", [])); - const currentParams = _.cloneDeep(_.get(extensionInstance, "config.params", {})); + const specParams = cloneDeep(extensionInstance?.config?.source?.spec?.params || []); + const currentParams = cloneDeep(extensionInstance?.config?.params || {}); return setNewDefaults(specParams, currentParams); } @@ -101,8 +101,8 @@ export async function getParams(args: { nonInteractive?: boolean; paramsEnvPath?: string; reconfiguring?: boolean; -}): Promise<{ [key: string]: ParamBindingOptions }> { - let params: any; +}): Promise> { + let params: Record; if (args.nonInteractive && !args.paramsEnvPath) { const paramsMessage = args.paramSpecs .map((p) => { @@ -130,7 +130,8 @@ export async function getParams(args: { reconfiguring: !!args.reconfiguring, }); } - void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params)); + const paramNames = Object.keys(params); + void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length); return params; } @@ -142,8 +143,8 @@ export async function getParamsForUpdate(args: { paramsEnvPath?: string; nonInteractive?: boolean; instanceId: string; -}): Promise<{ [key: string]: ParamBindingOptions }> { - let params: { [key: string]: ParamBindingOptions }; +}): Promise> { + let params: Record; if (args.nonInteractive && !args.paramsEnvPath) { const paramsMessage = args.newSpec.params .map((p) => { @@ -170,7 +171,8 @@ export async function getParamsForUpdate(args: { instanceId: args.instanceId, }); } - void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params)); + const paramNames = Object.keys(params); + void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length); return params; } @@ -192,22 +194,28 @@ export async function promptForNewParams(args: { const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams); const firebaseProjectParams = await getFirebaseProjectParams(args.projectId); - const comparer = (param1: extensionsApi.Param, param2: extensionsApi.Param) => { + const sameParam = (param1: extensionsApi.Param) => (param2: extensionsApi.Param) => { return param1.type === param2.type && param1.param === param2.param; }; + const paramDiff = ( + left: extensionsApi.Param[], + right: extensionsApi.Param[] + ): extensionsApi.Param[] => { + return left.filter((aLeft) => !right.find(sameParam(aLeft))); + }; // Some params are in the spec but not in currentParams, remove so we can prompt for them. const oldParams = args.spec.params.filter((p) => Object.keys(args.currentParams).includes(p.param) ); - let paramsDiffDeletions = _.differenceWith(oldParams, args.newSpec.params, comparer); + let paramsDiffDeletions = paramDiff(oldParams, args.newSpec.params); paramsDiffDeletions = substituteParams( paramsDiffDeletions, firebaseProjectParams ); - let paramsDiffAdditions = _.differenceWith(args.newSpec.params, oldParams, comparer); + let paramsDiffAdditions = paramDiff(args.newSpec.params, oldParams); paramsDiffAdditions = substituteParams( paramsDiffAdditions, firebaseProjectParams @@ -215,10 +223,10 @@ export async function promptForNewParams(args: { if (paramsDiffDeletions.length) { logger.info("The following params will no longer be used:"); - paramsDiffDeletions.forEach((param) => { + for (const param of paramsDiffDeletions) { logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`)); delete newParamBindingOptions[param.param.toUpperCase()]; - }); + } } if (paramsDiffAdditions.length) { logger.info("To update this instance, configure the following new parameters:"); diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 2efed841e4b..05935dd3606 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -525,7 +525,6 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi gcfFunction, "serviceAccountEmail", "availableMemoryMb", - "timeout", "minInstances", "maxInstances", "ingressSettings", @@ -534,6 +533,13 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi "secretEnvironmentVariables", "sourceUploadUrl" ); + proto.renameIfPresent( + endpoint, + gcfFunction, + "timeoutSeconds", + "timeout", + proto.secondsFromDuration + ); if (gcfFunction.vpcConnector) { endpoint.vpc = { connector: gcfFunction.vpcConnector }; proto.renameIfPresent( @@ -609,7 +615,6 @@ export function functionFromEndpoint( gcfFunction, endpoint, "serviceAccountEmail", - "timeout", "availableMemoryMb", "minInstances", "maxInstances", @@ -617,6 +622,13 @@ export function functionFromEndpoint( "environmentVariables", "secretEnvironmentVariables" ); + proto.renameIfPresent( + gcfFunction, + endpoint, + "timeout", + "timeoutSeconds", + proto.durationFromSeconds + ); if (endpoint.vpc) { proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector"); proto.renameIfPresent( diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index a05a7815ae4..96cfa8b8d57 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -401,7 +401,8 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage endpoint, "environmentVariables", "serviceAccountEmail", - "ingressSettings" + "ingressSettings", + "timeoutSeconds" ); proto.renameIfPresent( gcfFunction.serviceConfig, @@ -410,13 +411,6 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage "availableMemoryMb", (mb: string) => `${mb}M` ); - proto.renameIfPresent( - gcfFunction.serviceConfig, - endpoint, - "timeoutSeconds", - "timeout", - proto.secondsFromDuration - ); proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances"); proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances"); @@ -539,7 +533,8 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi gcfFunction.serviceConfig, "serviceAccountEmail", "ingressSettings", - "environmentVariables" + "environmentVariables", + "timeoutSeconds" ); proto.renameIfPresent( endpoint, @@ -548,13 +543,6 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi "availableMemory", megabytes ); - proto.renameIfPresent( - endpoint, - gcfFunction.serviceConfig, - "timeout", - "timeoutSeconds", - proto.durationFromSeconds - ); proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount"); proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount"); proto.copyIfPresent(endpoint, gcfFunction, "labels"); diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index ccdba80201f..92caba9d6db 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -77,7 +77,7 @@ describe("backendFromV1Alpha1", () => { maxInstances: "2", minInstances: "1", serviceAccountEmail: { ldap: "inlined" }, - timeout: 60, + timeoutSeconds: "60s", trigger: [], vpcConnector: 2, vpcConnectorEgressSettings: {}, @@ -373,7 +373,7 @@ describe("backendFromV1Alpha1", () => { labels: { hello: "world" }, environmentVariables: { foo: "bar" }, availableMemoryMb: 256, - timeout: "60s", + timeoutSeconds: 60, maxInstances: 20, minInstances: 1, vpc: { diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index cc2bd4ec6d1..6c671678551 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -149,7 +149,6 @@ describe("addResourcesToBackend", () => { vpcConnectorEgressSettings: "PRIVATE_RANGES_ONLY", vpcConnector: "projects/project/locations/region/connectors/connector", ingressSettings: "ALLOW_ALL", - timeout: "60s", labels: { test: "testing", }, @@ -167,7 +166,6 @@ describe("addResourcesToBackend", () => { egressSettings: "PRIVATE_RANGES_ONLY", }, ingressSettings: "ALLOW_ALL", - timeout: "60s", labels: { test: "testing", }, @@ -190,6 +188,7 @@ describe("addResourcesToBackend", () => { resource: "projects/p/topics/t", service: "pubsub.googleapis.com", }, + timeout: "60s", }; const result = backend.empty(); @@ -201,7 +200,11 @@ describe("addResourcesToBackend", () => { retry: false, }; - const expected: backend.Backend = backend.of({ ...BASIC_ENDPOINT, eventTrigger }); + const expected: backend.Backend = backend.of({ + ...BASIC_ENDPOINT, + eventTrigger, + timeoutSeconds: 60, + }); expect(result).to.deep.equal(expected); }); diff --git a/src/test/extensions/billingMigrationHelper.spec.ts b/src/test/extensions/billingMigrationHelper.spec.ts index 873547a6454..5d21716517d 100644 --- a/src/test/extensions/billingMigrationHelper.spec.ts +++ b/src/test/extensions/billingMigrationHelper.spec.ts @@ -1,12 +1,13 @@ -import * as _ from "lodash"; import { expect } from "chai"; import * as sinon from "sinon"; import { FirebaseError } from "../../error"; import * as nodejsMigrationHelper from "../../extensions/billingMigrationHelper"; import * as prompt from "../../prompt"; +import { ExtensionSpec } from "../../extensions/extensionsApi"; +import { cloneDeep } from "../../utils"; -const NO_RUNTIME_SPEC = { +const NO_RUNTIME_SPEC: ExtensionSpec = { name: "test", specVersion: "v1beta", displayName: "Old", @@ -28,7 +29,7 @@ const NO_RUNTIME_SPEC = { params: [], }; -const NODE8_SPEC = { +const NODE8_SPEC: ExtensionSpec = { name: "test", specVersion: "v1beta", displayName: "Old", @@ -50,7 +51,7 @@ const NODE8_SPEC = { params: [], }; -const NODE10_SPEC = { +const NODE10_SPEC: ExtensionSpec = { name: "test", specVersion: "v1beta", displayName: "Old", @@ -85,7 +86,7 @@ describe("billingMigrationHelper", () => { describe("displayNode10CreateBillingNotice", () => { it("should notify the user if the runtime requires nodejs10", async () => { promptStub.resolves(true); - const newSpec = _.cloneDeep(NODE10_SPEC); + const newSpec = cloneDeep(NODE10_SPEC); await expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be .rejected; @@ -94,7 +95,7 @@ describe("billingMigrationHelper", () => { it("should notify the user if the runtime does not require nodejs (explicit)", async () => { promptStub.resolves(true); - const newSpec = _.cloneDeep(NODE8_SPEC); + const newSpec = cloneDeep(NODE8_SPEC); await expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be .rejected; @@ -103,7 +104,7 @@ describe("billingMigrationHelper", () => { it("should notify the user if the runtime does not require nodejs (implicit)", async () => { promptStub.resolves(true); - const newSpec = _.cloneDeep(NO_RUNTIME_SPEC); + const newSpec = cloneDeep(NO_RUNTIME_SPEC); await expect(nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true)).not.to.be .rejected; @@ -112,7 +113,7 @@ describe("billingMigrationHelper", () => { it("should error if the user doesn't give consent", async () => { promptStub.resolves(false); - const newSpec = _.cloneDeep(NODE10_SPEC); + const newSpec = cloneDeep(NODE10_SPEC); await expect( nodejsMigrationHelper.displayNode10CreateBillingNotice(newSpec, true) diff --git a/src/test/extensions/displayExtensionInfo.spec.ts b/src/test/extensions/displayExtensionInfo.spec.ts index 52a4e61a63c..1636e02c8e5 100644 --- a/src/test/extensions/displayExtensionInfo.spec.ts +++ b/src/test/extensions/displayExtensionInfo.spec.ts @@ -1,12 +1,13 @@ -import * as _ from "lodash"; import { expect } from "chai"; import * as sinon from "sinon"; import { FirebaseError } from "../../error"; import * as displayExtensionInfo from "../../extensions/displayExtensionInfo"; import * as prompt from "../../prompt"; +import { ExtensionSpec, Resource } from "../../extensions/extensionsApi"; +import { cloneDeep } from "../../utils"; -const SPEC = { +const SPEC: ExtensionSpec = { name: "test", displayName: "Old", description: "descriptive", @@ -22,7 +23,7 @@ const SPEC = { ], resources: [ { name: "resource1", type: "firebaseextensions.v1beta.function", description: "desc" }, - { name: "resource2", type: "other", description: "" }, + { name: "resource2", type: "other", description: "" } as unknown as Resource, ], author: { authorName: "Tester", url: "firebase.google.com" }, contributors: [{ authorName: "Tester 2" }], @@ -57,7 +58,7 @@ describe("displayExtensionInfo", () => { }); describe("displayUpdateChangesNoInput", () => { it("should display changes to display name", () => { - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.displayName = "new"; const loggedLines = displayExtensionInfo.displayUpdateChangesNoInput(SPEC, newSpec); @@ -67,7 +68,7 @@ describe("displayExtensionInfo", () => { }); it("should display changes to description", () => { - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.description = "even better"; const loggedLines = displayExtensionInfo.displayUpdateChangesNoInput(SPEC, newSpec); @@ -82,7 +83,7 @@ describe("displayExtensionInfo", () => { }); it("should notify the user if billing is no longer required", () => { - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.billingRequired = false; const loggedLines = displayExtensionInfo.displayUpdateChangesNoInput(SPEC, newSpec); @@ -102,32 +103,32 @@ describe("displayExtensionInfo", () => { promptStub.restore(); }); - it("should prompt for changes to license and continue if user gives consent", () => { + it("should prompt for changes to license and continue if user gives consent", async () => { promptStub.resolves(true); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.license = "To Kill"; - expect( + await expect( displayExtensionInfo.displayUpdateChangesRequiringConfirmation({ spec: SPEC, newSpec, nonInteractive: false, force: false, }) - ).not.to.be.rejected; + ).to.eventually.not.be.rejected; expect(promptStub.callCount).to.equal(1); }); - it("should prompt for changes to apis and continue if user gives consent", () => { + it("should prompt for changes to apis and continue if user gives consent", async () => { promptStub.resolves(true); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.apis = [ { apiName: "api2", reason: "" }, { apiName: "api3", reason: "" }, ]; - expect( + await expect( displayExtensionInfo.displayUpdateChangesRequiringConfirmation({ spec: SPEC, newSpec, @@ -139,15 +140,15 @@ describe("displayExtensionInfo", () => { expect(promptStub.callCount).to.equal(1); }); - it("should prompt for changes to roles and continue if user gives consent", () => { + it("should prompt for changes to roles and continue if user gives consent", async () => { promptStub.resolves(true); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.roles = [ { role: "role2", reason: "" }, { role: "role3", reason: "" }, ]; - expect( + await expect( displayExtensionInfo.displayUpdateChangesRequiringConfirmation({ spec: SPEC, newSpec, @@ -159,15 +160,15 @@ describe("displayExtensionInfo", () => { expect(promptStub.callCount).to.equal(1); }); - it("should prompt for changes to resources and continue if user gives consent", () => { + it("should prompt for changes to resources and continue if user gives consent", async () => { promptStub.resolves(true); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.resources = [ { name: "resource3", type: "firebaseextensions.v1beta.function", description: "new desc" }, - { name: "resource2", type: "other", description: "" }, + { name: "resource2", type: "other", description: "" } as unknown as Resource, ]; - expect( + await expect( displayExtensionInfo.displayUpdateChangesRequiringConfirmation({ spec: SPEC, newSpec, @@ -179,12 +180,12 @@ describe("displayExtensionInfo", () => { expect(promptStub.callCount).to.equal(1); }); - it("should prompt for changes to resources and continue if user gives consent", () => { + it("should prompt for changes to resources and continue if user gives consent", async () => { promptStub.resolves(true); - const oldSpec = _.cloneDeep(SPEC); + const oldSpec = cloneDeep(SPEC); oldSpec.billingRequired = false; - expect( + await expect( displayExtensionInfo.displayUpdateChangesRequiringConfirmation({ spec: oldSpec, newSpec: SPEC, @@ -196,17 +197,17 @@ describe("displayExtensionInfo", () => { expect(promptStub.callCount).to.equal(1); }); - it("should exit if the user consents to one change but rejects another", () => { + it("should exit if the user consents to one change but rejects another", async () => { promptStub.resolves(true); promptStub.resolves(false); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.license = "New"; newSpec.roles = [ { role: "role2", reason: "" }, { role: "role3", reason: "" }, ]; - expect( + await expect( displayExtensionInfo.displayUpdateChangesRequiringConfirmation({ spec: SPEC, newSpec, @@ -221,12 +222,12 @@ describe("displayExtensionInfo", () => { expect(promptStub.callCount).to.equal(1); }); - it("should error if the user doesn't give consent", () => { + it("should error if the user doesn't give consent", async () => { promptStub.resolves(false); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.license = "new"; - expect( + await expect( displayExtensionInfo.displayUpdateChangesRequiringConfirmation({ spec: SPEC, newSpec, @@ -241,7 +242,7 @@ describe("displayExtensionInfo", () => { it("shouldn't prompt the user if no changes require confirmation", async () => { promptStub.resolves(false); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.version = "1.1.0"; await displayExtensionInfo.displayUpdateChangesRequiringConfirmation({ diff --git a/src/test/extensions/emulator/triggerHelper.spec.ts b/src/test/extensions/emulator/triggerHelper.spec.ts index 509a3e8ac58..a59eaf6308a 100644 --- a/src/test/extensions/emulator/triggerHelper.spec.ts +++ b/src/test/extensions/emulator/triggerHelper.spec.ts @@ -1,26 +1,28 @@ import { expect } from "chai"; import * as triggerHelper from "../../../extensions/emulator/triggerHelper"; +import { Resource } from "../../../extensions/extensionsApi"; describe("triggerHelper", () => { describe("functionResourceToEmulatedTriggerDefintion", () => { it("should assign valid properties from the resource to the ETD and ignore others", () => { - const testResource = { + const testResource: Resource = { name: "test-resource", entryPoint: "functionName", + type: "firebaseextensions.v1beta.function", properties: { timeout: "3s", location: "us-east1", availableMemoryMb: 1024, - somethingInvalid: "a value", }, }; + (testResource.properties as Record).somethingInvalid = "a value"; const expected = { platform: "gcfv1", availableMemoryMb: 1024, entryPoint: "test-resource", name: "test-resource", regions: ["us-east1"], - timeout: "3s", + timeoutSeconds: 3, }; const result = triggerHelper.functionResourceToEmulatedTriggerDefintion(testResource); @@ -29,9 +31,10 @@ describe("triggerHelper", () => { }); it("should handle HTTPS triggers", () => { - const testResource = { + const testResource: Resource = { name: "test-resource", entryPoint: "functionName", + type: "firebaseextensions.v1beta.function", properties: { httpsTrigger: {}, }, @@ -49,9 +52,10 @@ describe("triggerHelper", () => { }); it("should handle firestore triggers", () => { - const testResource = { + const testResource: Resource = { name: "test-resource", entryPoint: "functionName", + type: "firebaseextensions.v1beta.function", properties: { eventTrigger: { eventType: "providers/cloud.firestore/eventTypes/document.write", @@ -76,9 +80,10 @@ describe("triggerHelper", () => { }); it("should handle database triggers", () => { - const testResource = { + const testResource: Resource = { name: "test-resource", entryPoint: "functionName", + type: "firebaseextensions.v1beta.function", properties: { eventTrigger: { eventType: "providers/google.firebase.database/eventTypes/ref.create", @@ -103,9 +108,10 @@ describe("triggerHelper", () => { }); it("should handle pubsub triggers", () => { - const testResource = { + const testResource: Resource = { name: "test-resource", entryPoint: "functionName", + type: "firebaseextensions.v1beta.function", properties: { eventTrigger: { eventType: "google.pubsub.topic.publish", diff --git a/src/test/extensions/extensionsApi.spec.ts b/src/test/extensions/extensionsApi.spec.ts index 9a9da1c64fa..c97c0bf7103 100644 --- a/src/test/extensions/extensionsApi.spec.ts +++ b/src/test/extensions/extensionsApi.spec.ts @@ -1,4 +1,3 @@ -import * as _ from "lodash"; import { expect } from "chai"; import * as nock from "nock"; @@ -6,6 +5,7 @@ import * as api from "../../api"; import { FirebaseError } from "../../error"; import * as extensionsApi from "../../extensions/extensionsApi"; import * as refs from "../../extensions/refs"; +import { cloneDeep } from "../../utils"; const VERSION = "v1beta"; const PROJECT_ID = "test-project"; @@ -100,7 +100,7 @@ const TEST_INSTANCES_RESPONSE = { instances: [TEST_INSTANCE_1, TEST_INSTANCE_2], }; -const TEST_INSTANCES_RESPONSE_NEXT_PAGE_TOKEN: any = _.cloneDeep(TEST_INSTANCES_RESPONSE); +const TEST_INSTANCES_RESPONSE_NEXT_PAGE_TOKEN: any = cloneDeep(TEST_INSTANCES_RESPONSE); TEST_INSTANCES_RESPONSE_NEXT_PAGE_TOKEN.nextPageToken = "abc123"; const PACKAGE_URI = "https://storage.googleapis.com/ABCD.zip"; diff --git a/src/test/extensions/paramHelper.spec.ts b/src/test/extensions/paramHelper.spec.ts index 4ad36b2b42d..f5406273dcd 100644 --- a/src/test/extensions/paramHelper.spec.ts +++ b/src/test/extensions/paramHelper.spec.ts @@ -1,4 +1,3 @@ -import * as _ from "lodash"; import { expect } from "chai"; import * as sinon from "sinon"; import * as fs from "fs-extra"; @@ -10,6 +9,7 @@ import * as extensionsHelper from "../../extensions/extensionsHelper"; import * as paramHelper from "../../extensions/paramHelper"; import * as env from "../../functions/env"; import * as prompt from "../../prompt"; +import { cloneDeep } from "../../utils"; const PROJECT_ID = "test-proj"; const INSTANCE_ID = "ext-instance"; @@ -341,7 +341,7 @@ describe("paramHelper", () => { }); it("should change existing defaults to the current state and leave other values unchanged", () => { - _.get(testInstance, "config.source.spec.params", []).push({ + (testInstance.config?.source?.spec?.params || []).push({ param: "THIRD", label: "3rd", default: "default", @@ -389,7 +389,7 @@ describe("paramHelper", () => { it("should prompt the user for any params in the new spec that are not in the current one", async () => { promptStub.resolves("user input"); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.params = TEST_PARAMS_2; const newParams = await paramHelper.promptForNewParams({ @@ -430,7 +430,7 @@ describe("paramHelper", () => { it("should prompt for params that are not currently populated", async () => { promptStub.resolves("user input"); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.params = TEST_PARAMS_2; const newParams = await paramHelper.promptForNewParams({ @@ -454,7 +454,7 @@ describe("paramHelper", () => { it("should not prompt the user for params that did not change type or param", async () => { promptStub.resolves("Fail"); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.params = TEST_PARAMS_3; const newParams = await paramHelper.promptForNewParams({ @@ -479,7 +479,7 @@ describe("paramHelper", () => { it("should populate the spec with the default value if it is returned by prompt", async () => { promptStub.onFirstCall().resolves("test-proj"); promptStub.onSecondCall().resolves("user input"); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.params = TEST_PARAMS_2; const newParams = await paramHelper.promptForNewParams({ @@ -520,7 +520,7 @@ describe("paramHelper", () => { it("shouldn't prompt if there are no new params", async () => { promptStub.resolves("Fail"); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); const newParams = await paramHelper.promptForNewParams({ spec: SPEC, @@ -543,7 +543,7 @@ describe("paramHelper", () => { it("should exit if a prompt fails", async () => { promptStub.rejects(new FirebaseError("this is an error")); - const newSpec = _.cloneDeep(SPEC); + const newSpec = cloneDeep(SPEC); newSpec.params = TEST_PARAMS_2; await expect( diff --git a/src/test/extensions/updateHelper.spec.ts b/src/test/extensions/updateHelper.spec.ts index 6835ee2719e..c7dff68db35 100644 --- a/src/test/extensions/updateHelper.spec.ts +++ b/src/test/extensions/updateHelper.spec.ts @@ -8,7 +8,7 @@ import * as extensionsApi from "../../extensions/extensionsApi"; import * as extensionsHelper from "../../extensions/extensionsHelper"; import * as updateHelper from "../../extensions/updateHelper"; -const SPEC = { +const SPEC: extensionsApi.ExtensionSpec = { name: "test", displayName: "Old", description: "descriptive", @@ -24,7 +24,7 @@ const SPEC = { ], resources: [ { name: "resource1", type: "firebaseextensions.v1beta.function", description: "desc" }, - { name: "resource2", type: "other", description: "" }, + { name: "resource2", type: "other", description: "" } as unknown as extensionsApi.Resource, ], author: { authorName: "Tester" }, contributors: [{ authorName: "Tester 2" }], diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index 1ac3886ee9c..f6c510e8594 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -98,7 +98,6 @@ describe("cloudfunctions", () => { egressSettings: "ALL_TRAFFIC", }, ingressSettings: "ALLOW_ALL", - timeout: "15s", serviceAccountEmail: "inlined@google.com", labels: { foo: "bar", @@ -124,7 +123,6 @@ describe("cloudfunctions", () => { vpcConnectorEgressSettings: "ALL_TRAFFIC", ingressSettings: "ALLOW_ALL", availableMemoryMb: 128, - timeout: "15s", serviceAccountEmail: "inlined@google.com", }; @@ -137,6 +135,7 @@ describe("cloudfunctions", () => { const complexEndpoint: backend.Endpoint = { ...ENDPOINT, scheduleTrigger: {}, + timeoutSeconds: 20, }; const complexGcfFunction: Omit< @@ -149,6 +148,7 @@ describe("cloudfunctions", () => { eventType: "google.pubsub.topic.publish", resource: `projects/project/topics/${backend.scheduleIdForFunction(FUNCTION_NAME)}`, }, + timeout: "20s", labels: { "deployment-scheduled": "true", }, @@ -274,7 +274,21 @@ describe("cloudfunctions", () => { }); it("should copy optional fields", () => { - const extraFields: Partial = { + const wantExtraFields: Partial = { + availableMemoryMb: 128, + minInstances: 1, + maxInstances: 42, + ingressSettings: "ALLOW_ALL", + serviceAccountEmail: "inlined@google.com", + timeoutSeconds: 15, + labels: { + foo: "bar", + }, + environmentVariables: { + FOO: "bar", + }, + }; + const haveExtraFields: Partial = { availableMemoryMb: 128, minInstances: 1, maxInstances: 42, @@ -294,14 +308,14 @@ describe("cloudfunctions", () => { expect( cloudfunctions.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION, - ...extraFields, + ...haveExtraFields, vpcConnector, vpcConnectorEgressSettings, httpsTrigger: {}, } as cloudfunctions.CloudFunction) ).to.deep.equal({ ...ENDPOINT, - ...extraFields, + ...wantExtraFields, vpc: { connector: vpcConnector, egressSettings: vpcConnectorEgressSettings, diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index b27cf094cc5..143288f8eeb 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -202,7 +202,7 @@ describe("cloudfunctionsv2", () => { }, maxInstances: 42, minInstances: 1, - timeout: "15s", + timeoutSeconds: 15, availableMemoryMb: 128, }; @@ -319,6 +319,7 @@ describe("cloudfunctionsv2", () => { const extraFields: backend.ServiceConfiguration = { ingressSettings: "ALLOW_ALL", serviceAccountEmail: "inlined@google.com", + timeoutSeconds: 15, environmentVariables: { FOO: "bar", }, @@ -359,13 +360,11 @@ describe("cloudfunctionsv2", () => { const extraFields: backend.ServiceConfiguration = { minInstances: 1, maxInstances: 42, - timeout: "15s", }; const extraGcfFields: Partial = { minInstanceCount: 1, maxInstanceCount: 42, - timeoutSeconds: 15, }; expect( diff --git a/src/test/gcp/cloudscheduler.spec.ts b/src/test/gcp/cloudscheduler.spec.ts index a4626445b2a..a76d9d0f0af 100644 --- a/src/test/gcp/cloudscheduler.spec.ts +++ b/src/test/gcp/cloudscheduler.spec.ts @@ -1,11 +1,11 @@ import { expect } from "chai"; -import * as _ from "lodash"; import * as nock from "nock"; import { FirebaseError } from "../../error"; import * as api from "../../api"; import * as backend from "../../deploy/functions/backend"; import * as cloudscheduler from "../../gcp/cloudscheduler"; +import { cloneDeep } from "../../utils"; const VERSION = "v1beta1"; @@ -41,7 +41,7 @@ describe("cloudscheduler", () => { }); it("should do nothing if a functionally identical job exists", async () => { - const otherJob = _.cloneDeep(TEST_JOB); + const otherJob = cloneDeep(TEST_JOB); otherJob.name = "something-different"; nock(api.cloudschedulerOrigin).get(`/${VERSION}/${TEST_JOB.name}`).reply(200, otherJob); @@ -52,7 +52,7 @@ describe("cloudscheduler", () => { }); it("should update if a job exists with the same name and a different schedule", async () => { - const otherJob = _.cloneDeep(TEST_JOB); + const otherJob = cloneDeep(TEST_JOB); otherJob.schedule = "every 6 minutes"; nock(api.cloudschedulerOrigin).get(`/${VERSION}/${TEST_JOB.name}`).reply(200, otherJob); nock(api.cloudschedulerOrigin).patch(`/${VERSION}/${TEST_JOB.name}`).reply(200, otherJob); @@ -64,7 +64,7 @@ describe("cloudscheduler", () => { }); it("should update if a job exists with the same name but a different timeZone", async () => { - const otherJob = _.cloneDeep(TEST_JOB); + const otherJob = cloneDeep(TEST_JOB); otherJob.timeZone = "America/New_York"; nock(api.cloudschedulerOrigin).get(`/${VERSION}/${TEST_JOB.name}`).reply(200, otherJob); nock(api.cloudschedulerOrigin).patch(`/${VERSION}/${TEST_JOB.name}`).reply(200, otherJob); @@ -76,7 +76,7 @@ describe("cloudscheduler", () => { }); it("should update if a job exists with the same name but a different retry config", async () => { - const otherJob = _.cloneDeep(TEST_JOB); + const otherJob = cloneDeep(TEST_JOB); otherJob.retryConfig = { maxDoublings: 10 }; nock(api.cloudschedulerOrigin).get(`/${VERSION}/${TEST_JOB.name}`).reply(200, otherJob); nock(api.cloudschedulerOrigin).patch(`/${VERSION}/${TEST_JOB.name}`).reply(200, otherJob); diff --git a/src/utils.ts b/src/utils.ts index 64838c46ea7..106d1ba4b3e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -638,3 +638,38 @@ export function groupBy( return result; }, {} as Record); } + +function cloneArray(arr: T[]): T[] { + return arr.map((e) => cloneDeep(e)); +} + +function cloneObject>(obj: T): T { + const clone: Record = {}; + for (const [k, v] of Object.entries(obj)) { + clone[k] = cloneDeep(v); + } + return clone as T; +} + +/** + * replacement for lodash cloneDeep that preserves type. + */ +// TODO: replace with builtin once Node 18 becomes the min version. +export function cloneDeep(obj: T): T { + if (typeof obj !== "object" || !obj) { + return obj; + } + if (obj instanceof RegExp) { + return RegExp(obj, obj.flags) as typeof obj; + } + if (obj instanceof Date) { + return new Date(obj) as typeof obj; + } + if (Array.isArray(obj)) { + return cloneArray(obj) as typeof obj; + } + if (obj instanceof Map) { + return new Map(obj.entries()) as typeof obj; + } + return cloneObject(obj as Record) as typeof obj; +} From 09c1bce885cd3aebf46345000801a4cc72d3f2b7 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 4 Apr 2022 10:47:06 -0700 Subject: [PATCH 0213/1699] Improve error messages when trying to use alpha featuers without enablement (#4354) * Improve error messages when trying to use alpha featuers without enablement * Changelog * Run formatter --- CHANGELOG.md | 1 + src/deploy/functions/prepare.ts | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a1df746079..44894fa96c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Improve error messages when using alpha features without being in the alpha program (#4354) - Fixes bug where resumable uploads were not setting custom metadata on upload (#3398). - Fixes bug where GCS metadataUpdate cloud functions were triggered in incorrect situations (#3398). - Fixes bug where quoted escape sequences in .env files were incompletely unescaped. (#4270) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 9ea291d0542..9491020bafd 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -10,7 +10,7 @@ import * as validate from "./validate"; import * as ensure from "./ensure"; import { Options } from "../../options"; import { functionMatchesAnyGroup, getFilterGroups } from "./functionsDeployHelper"; -import { logBullet } from "../../utils"; +import { logBullet, logLabeledError } from "../../utils"; import { getFunctionsConfig, prepareFunctionsUpload } from "./prepareFunctionsUpload"; import { promptForFailurePolicies, promptForMinInstances } from "./prompts"; import { needProjectId, needProjectNumber } from "../../projectUtils"; @@ -20,6 +20,7 @@ import { ensureTriggerRegions } from "./triggerRegionHelper"; import { ensureServiceAgentRoles } from "./checkIam"; import { FirebaseError } from "../../error"; import { normalizeAndValidate } from "../../functions/projectConfig"; +import { previews } from "../../previews"; function hasUserConfig(config: Record): boolean { // "firebase" key is always going to exist in runtime config. @@ -127,6 +128,20 @@ export async function prepare( " directory for uploading..." ); } + if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { + if (!previews.functionsv2) { + throw new FirebaseError( + "This version of firebase-tools does not support Google Cloud " + + "Functions gen 2\n" + + "If Cloud Functions for Firebase gen 2 is still in alpha, sign up " + + "for the alpha program at " + + "https://services.google.com/fb/forms/firebasealphaprogram/\n" + + "If Cloud Functions for Firebase gen 2 is in beta, get the latest " + + "version of Firebse Tools with `npm i -g firebase-tools@latest`" + ); + } + context.functionsSourceV2 = await prepareFunctionsUpload(sourceDir, context.config); + } if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) { context.functionsSourceV1 = await prepareFunctionsUpload( sourceDir, @@ -134,9 +149,6 @@ export async function prepare( runtimeConfig ); } - if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { - context.functionsSourceV2 = await prepareFunctionsUpload(sourceDir, context.config); - } // Setup environment variables on each function. for (const endpoint of backend.allEndpoints(wantBackend)) { From 7d4828d6562638ece1c780c4471d8c5e99d90bcb Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Mon, 4 Apr 2022 15:53:24 -0400 Subject: [PATCH 0214/1699] Fix logging for metadata parse errors for multipart uploads in Storage Emulator (#4397) --- scripts/storage-emulator-integration/tests.ts | 22 +++++++++++++++++++ src/emulator/storage/apis/firebase.ts | 2 +- src/emulator/storage/apis/gcloud.ts | 15 ++++++++----- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index 8fb40d565ac..c1c8926f4cc 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -220,6 +220,17 @@ describe("Storage emulator", () => { expect(fileMetadata).to.deep.include(metadata); }); + it("should return an error message when uploading a file with invalid metadata", async () => { + const fileName = "test_upload.jpg"; + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .post(`/upload/storage/v1/b/${storageBucket}/o?name=${fileName}`) + .set({ Authorization: "Bearer owner", "X-Upload-Content-Type": "foo" }) + .expect(400) + .then((res) => res.body.error.message); + + expect(errorMessage).to.equal("Invalid Content-Type: foo"); + }); + it("should be able to upload file named 'prefix/file.txt' when file named 'prefix' already exists", async () => { await testBucket.upload(smallFilePath, { destination: "prefix", @@ -2083,6 +2094,17 @@ describe("Storage emulator", () => { }); }); + it("should return an error message when uploading a file with invalid metadata", async () => { + const fileName = "test_upload.jpg"; + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .post(`/v0/b/${storageBucket}/o/${fileName}?name=${fileName}`) + .set({ "x-goog-upload-protocol": "multipart", "content-type": "foo" }) + .expect(400) + .then((res) => res.body.error.message); + + expect(errorMessage).to.equal("Invalid Content-Type: foo"); + }); + it("should accept subsequent resumable upload commands without an auth header", async () => { const uploadURL = await supertest(STORAGE_EMULATOR_HOST) .post( diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index ca37ecc22b0..0b2a7e3bd73 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -205,7 +205,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return res.status(400).json({ error: { code: 400, - message: err.toString(), + message: err.message, }, }); } diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index 3ef78780c07..d214f42a50a 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -240,12 +240,15 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { await reqBodyToBuffer(req) )); } catch (err) { - return res.status(400).json({ - error: { - code: 400, - message: err, - }, - }); + if (err instanceof Error) { + return res.status(400).json({ + error: { + code: 400, + message: err.message, + }, + }); + } + throw err; } const upload = uploadService.multipartUpload({ From be22a4590681387e68a924ef3a9458c8e555d0eb Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 4 Apr 2022 21:06:50 +0000 Subject: [PATCH 0215/1699] 10.6.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index aa13687eacc..c06fd293387 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.5.0", + "version": "10.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.5.0", + "version": "10.6.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index a0c22a48e2d..0c9c7d30fef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.5.0", + "version": "10.6.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 3c6b2939d947a9252793c6e7a2e10c9f8269b2cb Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 4 Apr 2022 21:07:16 +0000 Subject: [PATCH 0216/1699] [firebase-release] Removed change log and reset repo after 10.6.0 release --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44894fa96c5..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +0,0 @@ -- Improve error messages when using alpha features without being in the alpha program (#4354) -- Fixes bug where resumable uploads were not setting custom metadata on upload (#3398). -- Fixes bug where GCS metadataUpdate cloud functions were triggered in incorrect situations (#3398). -- Fixes bug where quoted escape sequences in .env files were incompletely unescaped. (#4270) -- Fixes Storage Emulator ruleset file watcher (#4337). -- Fixes issue with importing Storage Emulator data exported prior to v10.3.0 (#4358). -- Adds ergonomic improvements to CF3 secret commands to automatically redeploy functions and delete unused secrets (#4130). -- Fixes issue with alpha users setting timeouts (#4381) From 2d21bb2fdbb5f3a32587e0881a98c8c33556a5f3 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 4 Apr 2022 14:40:21 -0700 Subject: [PATCH 0217/1699] Add support for codebase property for function deployments. (#4350) We add preliminary support for new "codebase" property for function deployments. We allow users to add a codebase property in their `firebase.json`: ```json "functions": { "source": "functions", "codebase": "my-fns" } ``` Adding codebase annotation has following effects: 1. Deployed function will be tagged with label `firebase-functions-codebase={codebase}`. 2. Function deployment will be scoped down to only look at functions within the said codebase. Practically, this means that a function deployment will _not_ touch (a.k.a. delete) functions from other codebases. This makes it easy for users to split their function definitions across different Firebase project repositories. In addition, filters specified using `--only` flag will allow users to specify `codebase` as a resource selector. e.g. ```bash # "Only deploy function w/ id func-id" $ firebase deploy --only functions:func-id # "Only deploy function w/ id func-id in codebase foo" $ firebase deploy --only functions:foo:func-id ``` This isn't very useful today as long as we only can have single codebase within a project directory. (Note that this PR does not bring codebase support to other function commands like `functions:{delete,log,list}`. This will come in coming PRs. Note: * Every endpoint is now annotated w/ "codebase" property. When parsing triggers, we use value from `firebase.json` to fill in the property. When loading existing functions from GCP, labels are derived from label `firebase-functions-codebase` (defaults to `default` if missing). * Minor refactoring of filtering logic (i.e. `--only`) to support filtering by codebase. --- schema/firebase-config.json | 6 + src/commands/functions-delete.ts | 12 +- src/deploy/functions/args.ts | 10 +- src/deploy/functions/backend.ts | 7 +- src/deploy/functions/checkIam.ts | 9 +- src/deploy/functions/deploy.ts | 2 +- src/deploy/functions/functionsDeployHelper.ts | 130 ++++++-- src/deploy/functions/prepare.ts | 63 ++-- src/deploy/functions/release/index.ts | 14 +- src/deploy/functions/release/planner.ts | 29 +- src/firebaseConfig.ts | 1 + src/functions/projectConfig.ts | 34 +- src/gcp/cloudfunctions.ts | 7 + src/gcp/cloudfunctionsv2.ts | 10 +- src/test/deploy/functions/backend.spec.ts | 4 + .../functions/functionsDeployHelper.spec.ts | 300 ++++++++++++++++-- .../deploy/functions/release/planner.spec.ts | 53 ++-- src/test/functions/projectConfig.spec.ts | 63 +++- src/test/gcp/cloudfunctions.spec.ts | 46 +++ src/test/gcp/cloudfunctionsv2.spec.ts | 41 ++- 20 files changed, 699 insertions(+), 142 deletions(-) diff --git a/schema/firebase-config.json b/schema/firebase-config.json index 6d4c51b0d3f..9c82b456a94 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -330,6 +330,9 @@ { "additionalProperties": false, "properties": { + "codebase": { + "type": "string" + }, "ignore": { "items": { "type": "string" @@ -381,6 +384,9 @@ "items": { "additionalProperties": false, "properties": { + "codebase": { + "type": "string" + }, "ignore": { "items": { "type": "string" diff --git a/src/commands/functions-delete.ts b/src/commands/functions-delete.ts index 5147ae388df..ebbd8886bbb 100644 --- a/src/commands/functions-delete.ts +++ b/src/commands/functions-delete.ts @@ -35,7 +35,7 @@ export default new Command("functions:delete [filters...]") const context: args.Context = { projectId: needProjectId(options), - filters: filters.map((f) => f.split(".")), + filters: filters.map((f) => ({ idChunks: f.split(".") })), }; const [config, existingBackend] = await Promise.all([ @@ -48,10 +48,12 @@ export default new Command("functions:delete [filters...]") if (options.region) { existingBackend.endpoints = { [options.region]: existingBackend.endpoints[options.region] }; } - const plan = planner.createDeploymentPlan(/* want= */ backend.empty(), existingBackend, { - filters: context.filters, - deleteAll: true, - }); + const plan = planner.createDeploymentPlan( + backend.empty(), + existingBackend, + context.filters, + /* deleteAll= */ true + ); const allEpToDelete = Object.values(plan) .map((changes) => changes.endpointsToDelete) .reduce(reduceFlat, []) diff --git a/src/deploy/functions/args.ts b/src/deploy/functions/args.ts index b09d2ac9b83..5391d050167 100644 --- a/src/deploy/functions/args.ts +++ b/src/deploy/functions/args.ts @@ -1,13 +1,15 @@ import * as backend from "./backend"; import * as gcfV2 from "../../gcp/cloudfunctionsv2"; import * as projectConfig from "../../functions/projectConfig"; +import * as deployHelper from "./functionsDeployHelper"; -// These types should proably be in a root deploy.ts, but we can only boil the ocean one bit at a time. +// These types should probably be in a root deploy.ts, but we can only boil the ocean one bit at a time. -// Payload holds the output types of what we're building. +// Payload holds the output of what we want to build + what we already have. export interface Payload { functions?: { - backend: backend.Backend; + wantBackend: backend.Backend; + haveBackend: backend.Backend; }; } @@ -16,7 +18,7 @@ export interface Payload { // details. export interface Context { projectId: string; - filters: string[][]; + filters?: deployHelper.EndpointFilter[]; // Filled in the "prepare" phase. config?: projectConfig.ValidatedSingle; diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 1192d7f66d9..497fec8cd44 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -268,7 +268,9 @@ export type Endpoint = TargetIds & runtime: runtimes.Runtime | runtimes.DeprecatedRuntime; // Output only - + // "Codebase" is not part of the container contract. Instead, it's value is provided by firebase.json or derived + // from function labels. + codebase?: string; // URI is available on GCFv1 for HTTPS triggers and // on GCFv2 always uri?: string; @@ -545,7 +547,8 @@ export function matchingBackend( predicate: (endpoint: Endpoint) => boolean ): Backend { const filtered: Backend = { - ...empty(), + ...backend, + endpoints: {}, }; for (const endpoint of allEndpoints(backend)) { if (!predicate(endpoint)) { diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index c02a904883c..aa245edbac6 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -1,7 +1,7 @@ import { bold } from "cli-color"; import { logger } from "../../logger"; -import { getFilterGroups, functionMatchesAnyGroup } from "./functionsDeployHelper"; +import { getEndpointFilters, endpointMatchesAnyFilter } from "./functionsDeployHelper"; import { FirebaseError } from "../../error"; import * as iam from "../../gcp/iam"; import * as args from "./args"; @@ -65,12 +65,13 @@ export async function checkHttpIam( options: Options, payload: args.Payload ): Promise { - const filterGroups = context.filters || getFilterGroups(options); + const filters = context.filters || getEndpointFilters(options); + const wantBackend = payload.functions!.wantBackend; const httpEndpoints = backend - .allEndpoints(payload.functions!.backend) + .allEndpoints(wantBackend) .filter(backend.isHttpsTriggered) - .filter((f) => functionMatchesAnyGroup(f, filterGroups)); + .filter((f) => endpointMatchesAnyFilter(f, filters)); const existing = await backend.existingBackend(context); const newHttpsEndpoints = httpEndpoints.filter(backend.missingEndpoint(existing)); diff --git a/src/deploy/functions/deploy.ts b/src/deploy/functions/deploy.ts index 9909879c129..10c43ad18b3 100644 --- a/src/deploy/functions/deploy.ts +++ b/src/deploy/functions/deploy.ts @@ -58,7 +58,7 @@ export async function deploy( await checkHttpIam(context, options, payload); try { - const want = payload.functions!.backend; + const want = payload.functions!.wantBackend; const uploads: Promise[] = []; const v1Endpoints = backend.allEndpoints(want).filter((e) => e.platform === "gcfv1"); diff --git a/src/deploy/functions/functionsDeployHelper.ts b/src/deploy/functions/functionsDeployHelper.ts index dafa2e45b9e..23d63178f68 100644 --- a/src/deploy/functions/functionsDeployHelper.ts +++ b/src/deploy/functions/functionsDeployHelper.ts @@ -1,42 +1,132 @@ import * as backend from "./backend"; +import * as projectConfig from "../../functions/projectConfig"; -export function functionMatchesAnyGroup(func: backend.TargetIds, filterGroups: string[][]) { - if (!filterGroups.length) { +export interface EndpointFilter { + // If codebase is undefined, match all functions in all codebase that matches the idChunks. + // This is useful when trying to filter just using id chunks across all codebases. + codebase?: string; + // If id chunks is undefined, match all function in the said codebase. + idChunks?: string[]; +} + +/** + * Returns true if endpoint matches any of the given filter. + * + * If no filter is passed, always returns true. + */ +export function endpointMatchesAnyFilter( + endpoint: backend.Endpoint, + filters?: EndpointFilter[] +): boolean { + if (!filters) { return true; } - return filterGroups.some((groupChunk) => functionMatchesGroup(func, groupChunk)); + return filters.some((filter) => endpointMatchesFilter(endpoint, filter)); } -export function functionMatchesGroup(func: backend.TargetIds, groupChunks: string[]): boolean { - const functionNameChunks = func.id.split("-").slice(0, groupChunks.length); - // Should never happen. It would mean the user has asked to deploy something that is - // a sub-function. E.g. function foo-bar and group chunks [foo, bar, baz]. - if (functionNameChunks.length !== groupChunks.length) { +/** + * Returns true if endpoint matches the given filter. + */ +export function endpointMatchesFilter(endpoint: backend.Endpoint, filter: EndpointFilter): boolean { + // Only enforce codebase-based filtering when both the endpoint and filter provides them. + // This allows us to filter using idChunks across all codebases. + if (endpoint.codebase && filter.codebase) { + if (endpoint.codebase !== filter.codebase) { + return false; + } + } + + if (!filter.idChunks) { + // If idChunks is not provided, we match all functions. + return true; + } + + const idChunks = endpoint.id.split("-"); + if (idChunks.length < filter.idChunks.length) { return false; } - for (let i = 0; i < groupChunks.length; i += 1) { - if (groupChunks[i] !== functionNameChunks[i]) { + for (let i = 0; i < filter.idChunks.length; i += 1) { + if (idChunks[i] !== filter.idChunks[i]) { return false; } } return true; } -export function getFilterGroups(options: { only?: string }): string[][] { +/** + * Returns list of filters after parsing selector. + */ +export function parseFunctionSelector(selector: string): EndpointFilter[] { + const fragments = selector.split(":"); + if (fragments.length < 2) { + // This is a plain selector w/o codebase prefix (e.g. "abc" not "abc:efg") . + // This could mean 2 things: + // + // 1. Only the codebase selector (i.e. "abc" refers to a codebase). + // 2. Id filter for the DEFAULT codebase (i.e. "abc" refers to a function id in the default codebase). + // + // We decide here to create filter for both conditions. This sounds sloppy, but it's only troublesome if there is + // conflict between a codebase name as function id in the default codebase. + return [ + { codebase: fragments[0] }, + { codebase: projectConfig.DEFAULT_CODEBASE, idChunks: fragments[0].split(/[-.]/) }, + ]; + } + return [ + { + codebase: fragments[0], + idChunks: fragments[1].split(/[-.]/), + }, + ]; +} + +/** + * Returns parsed --only commandline argument for functions product. + * + * For example, when user pass the following commandline argument: + * options.only = "functions:abc,functions:g1-gfn,hosting,functions:python:another-func + * + * We process the input as follows: + * + * "functions:abc": Filter function w/ id "abc" in the default codebase OR all functions in the "func" codebase. + * "functions:g1-gfn": Filter function w/ id "gfn" in function group g1 OR all functions in the "g1.gfn" codebase. + * "hosting": Ignored. + * "functions:python:another-func": Filter function w/ id "another-func" in "python" codebase. + * + * Note that filters like "functions:abc" are ambiguous. Is it referring to: + * 1) Function id "abc" in the default codebase? + * 2) Grouped functions w/ "abc" prefix in the default codebase? + * 3) All functions in the "abc" codebase? + * + * Current implementation creates filters that match against all conditions. + * + * If no filter exists, we return undefined which the caller should interpret as "match all functions". + */ +export function getEndpointFilters(options: { only?: string }): EndpointFilter[] | undefined { if (!options.only) { - return []; + return undefined; } - const only = options.only!.split(","); - const onlyFunctions = only.filter((filter) => { - const opts = filter.split(":"); - return opts[0] === "functions" && opts[1]; - }); - return onlyFunctions.map((filter) => { - return filter.split(":")[1].split(/[.-]/); - }); + const selectors = options.only.split(","); + const filters: EndpointFilter[] = []; + for (let selector of selectors) { + if (selector.startsWith("functions:")) { + selector = selector.replace("functions:", ""); + if (selector.length > 0) { + filters.push(...parseFunctionSelector(selector)); + } + } + } + + if (filters.length === 0) { + return undefined; + } + return filters; } +/** + * Generate label for a function. + */ export function getFunctionLabel(fn: backend.TargetIds): string { return `${fn.id}(${fn.region})`; } diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 9491020bafd..ba68adb95a6 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -9,8 +9,8 @@ import * as runtimes from "./runtimes"; import * as validate from "./validate"; import * as ensure from "./ensure"; import { Options } from "../../options"; -import { functionMatchesAnyGroup, getFilterGroups } from "./functionsDeployHelper"; -import { logBullet, logLabeledError } from "../../utils"; +import { endpointMatchesAnyFilter, getEndpointFilters } from "./functionsDeployHelper"; +import { logLabeledBullet, logLabeledError } from "../../utils"; import { getFunctionsConfig, prepareFunctionsUpload } from "./prepareFunctionsUpload"; import { promptForFailurePolicies, promptForMinInstances } from "./prompts"; import { needProjectId, needProjectNumber } from "../../projectUtils"; @@ -41,6 +41,20 @@ export async function prepare( const projectNumber = await needProjectNumber(options); context.config = normalizeAndValidate(options.config.src.functions)[0]; + context.filters = getEndpointFilters(options); // Parse --only filters for functions. + + if ( + context.filters && + !context.filters.map((f) => f.codebase).includes(context.config.codebase) + ) { + throw new FirebaseError("No function matches given --only filters. Aborting deployment."); + } + + logLabeledBullet( + "functions", + `preparing codebase ${clc.bold(context.config.codebase)} for deployment` + ); + const sourceDirName = context.config.source; if (!sourceDirName) { throw new FirebaseError( @@ -101,7 +115,10 @@ export async function prepare( logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`); const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs); wantBackend.environmentVariables = { ...userEnvs, ...firebaseEnvs }; - payload.functions = { backend: wantBackend }; + for (const endpoint of backend.allEndpoints(wantBackend)) { + endpoint.environmentVariables = wantBackend.environmentVariables; + endpoint.codebase = context.config.codebase; + } // Note: Some of these are premium APIs that require billing to be enabled. // We'd eventually have to add special error handling for billing APIs, but @@ -121,11 +138,9 @@ export async function prepare( } if (backend.someEndpoint(wantBackend, () => true)) { - logBullet( - clc.cyan.bold("functions:") + - " preparing " + - clc.bold(sourceDirName) + - " directory for uploading..." + logLabeledBullet( + "functions", + `preparing ${clc.bold(sourceDirName)} directory for uploading...` ); } if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { @@ -150,11 +165,6 @@ export async function prepare( ); } - // Setup environment variables on each function. - for (const endpoint of backend.allEndpoints(wantBackend)) { - endpoint.environmentVariables = wantBackend.environmentVariables; - } - // Enable required APIs. This may come implicitly from triggers (e.g. scheduled triggers // require cloudscheudler and, in v1, require pub/sub), or can eventually come from // explicit dependencies. @@ -167,14 +177,31 @@ export async function prepare( // Validate the function code that is being deployed. validate.endpointsAreValid(wantBackend); - // Check what --only filters have been passed in. - context.filters = getFilterGroups(options); - const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => { - return functionMatchesAnyGroup(endpoint, context.filters); + return endpointMatchesAnyFilter(endpoint, context.filters); }); - const haveBackend = await backend.existingBackend(context); + // Load all endpoints for the project, then filter out functions from other codebases. + // + // An endpoint is part a codebase if: + // 1. Endpoint is associated w/ the current codebase (duh). + // 2. Endpoint name matches name of an endoint we want to deploy + // + // Condition (2) might feel wrong but is a practical conflict resolution strategy. It allows user to "claim" an + // endpoint for current codebase without much hassel. + const wantEndpointNames = backend.allEndpoints(wantBackend).map((e) => backend.functionName(e)); + const haveBackend = backend.matchingBackend( + await backend.existingBackend(context), + (endpoint) => { + if (endpoint.codebase === context.config?.codebase) { + return true; + } + return wantEndpointNames.includes(backend.functionName(endpoint)); + } + ); + + payload.functions = { wantBackend: wantBackend, haveBackend: haveBackend }; + await ensureServiceAgentRoles(projectNumber, wantBackend, haveBackend); inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv); await ensureTriggerRegions(wantBackend); diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index df23e5e663b..efab023be3b 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -27,12 +27,12 @@ export async function release( if (!context.config) { return; } + if (!payload.functions) { + return; + } - const plan = planner.createDeploymentPlan( - payload.functions!.backend, - await backend.existingBackend(context), - { filters: context.filters } - ); + const { wantBackend, haveBackend } = payload.functions; + const plan = planner.createDeploymentPlan(wantBackend, haveBackend, context.filters); const fnsToDelete = Object.values(plan) .map((regionalChanges) => regionalChanges.endpointsToDelete) @@ -72,9 +72,9 @@ export async function release( // uri field. createDeploymentPlan copies endpoints by reference. Both of these // subtleties are so we can take out a round trip API call to get the latest // trigger URLs by calling existingBackend again. - printTriggerUrls(payload.functions!.backend); + printTriggerUrls(payload.functions!.wantBackend); - const haveEndpoints = backend.allEndpoints(payload.functions!.backend); + const haveEndpoints = backend.allEndpoints(payload.functions!.wantBackend); const deletedEndpoints = Object.values(plan) .map((r) => r.endpointsToDelete) .reduce(reduceFlat, []); diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index 3ff60bbec6e..1bb6646d49a 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -1,6 +1,11 @@ -import { functionMatchesAnyGroup, getFunctionLabel } from "../functionsDeployHelper"; +import { + EndpointFilter, + endpointMatchesAnyFilter, + getFunctionLabel, +} from "../functionsDeployHelper"; import { isFirebaseManaged } from "../../../deploymentTool"; import { FirebaseError } from "../../../error"; +import * as args from "../args"; import * as utils from "../../../utils"; import * as backend from "../backend"; import * as v2events from "../../../functions/events/v2"; @@ -18,18 +23,12 @@ export interface Changeset { export type DeploymentPlan = Record; -export interface Options { - filters?: string[][]; - // If set to false, will delete only functions that are managed by firebase - deleteAll?: boolean; -} - /** Calculate the changesets of given endpoints by grouping endpoints with keyFn. */ export function calculateChangesets( want: Record, have: Record, keyFn: (e: backend.Endpoint) => string, - options: Options + deleteAll?: boolean ): Record { const toCreate = utils.groupBy( Object.keys(want) @@ -41,7 +40,7 @@ export function calculateChangesets( const toDelete = utils.groupBy( Object.keys(have) .filter((id) => !want[id]) - .filter((id) => options.deleteAll || isFirebaseManaged(have[id].labels || {})) + .filter((id) => deleteAll || isFirebaseManaged(have[id].labels || {})) .map((id) => have[id]), keyFn ); @@ -95,19 +94,21 @@ export function calculateUpdate(want: backend.Endpoint, have: backend.Endpoint): * Create a plan for deploying all functions in one region. * @param want the desired state * @param have the current state - * @param filters The filters, passed in by the user via `--only functions:` + * @param filters filters to apply to backend, passed from users by --only flag. + * @param deleteAll Deletes all functions if set. */ export function createDeploymentPlan( want: backend.Backend, have: backend.Backend, - options: Options = {} + filters?: EndpointFilter[], + deleteAll?: boolean ): DeploymentPlan { let deployment: DeploymentPlan = {}; want = backend.matchingBackend(want, (endpoint) => { - return functionMatchesAnyGroup(endpoint, options.filters || []); + return endpointMatchesAnyFilter(endpoint, filters); }); have = backend.matchingBackend(have, (endpoint) => { - return functionMatchesAnyGroup(endpoint, options.filters || []); + return endpointMatchesAnyFilter(endpoint, filters); }); const regions = new Set([...Object.keys(want.endpoints), ...Object.keys(have.endpoints)]); @@ -116,7 +117,7 @@ export function createDeploymentPlan( want.endpoints[region] || {}, have.endpoints[region] || {}, (e) => `${e.region}-${e.availableMemoryMb || "default"}`, - options + deleteAll ); deployment = { ...deployment, ...changesets }; } diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index 814997c33f7..c6b5631fb47 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -106,6 +106,7 @@ export type FunctionConfig = { source?: string; ignore?: string[]; runtime?: CloudFunctionRuntimes; + codebase?: string; } & Deployable; export type FunctionsConfig = FunctionConfig | FunctionConfig[]; diff --git a/src/functions/projectConfig.ts b/src/functions/projectConfig.ts index 00b1b42273f..1c85b128860 100644 --- a/src/functions/projectConfig.ts +++ b/src/functions/projectConfig.ts @@ -2,9 +2,11 @@ import { FunctionsConfig, FunctionConfig } from "../firebaseConfig"; import { FirebaseError } from "../error"; export type NormalizedConfig = [FunctionConfig, ...FunctionConfig[]]; -export type ValidatedSingle = FunctionConfig & { source: string }; +export type ValidatedSingle = FunctionConfig & { source: string; codebase: string }; export type ValidatedConfig = [ValidatedSingle]; +export const DEFAULT_CODEBASE = "default"; + /** * Normalize functions config to return functions config in an array form. */ @@ -27,7 +29,30 @@ function validateSingle(config: FunctionConfig): ValidatedSingle { if (!config.source) { throw new FirebaseError("functions.source must be specified"); } - return { ...config, source: config.source }; + if (!config.codebase) { + config.codebase = DEFAULT_CODEBASE; + } + if (config.codebase.length > 63 || !/^[a-z0-9_-]+$/.test(config.codebase)) { + throw new FirebaseError( + "Invalid codebase name. Codebase must be less than 63 characters and " + + "can contain only lowercase letters, numeric characters, underscores, and dashes." + ); + } + + return { ...config, source: config.source, codebase: config.codebase }; +} + +function assertUnique(config: ValidatedConfig, property: keyof ValidatedSingle) { + const values = new Set(); + for (const single of config) { + const value = single[property]; + if (values.has(value)) { + throw new FirebaseError( + `functions.${property} must be unique but '${value}' was used more than once.` + ); + } + values.add(value); + } } /** @@ -37,7 +62,10 @@ export function validate(config: NormalizedConfig): ValidatedConfig { if (config.length > 1) { throw new FirebaseError("More than one functions.source detected in firebase.json."); } - return [validateSingle(config[0])]; + const validated = validateSingle(config[0]); + assertUnique([validated], "source"); + assertUnique([validated], "codebase"); + return [validated]; } /** diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 05935dd3606..69dddc700a4 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -8,10 +8,12 @@ import * as utils from "../utils"; import * as proto from "./proto"; import * as runtimes from "../deploy/functions/runtimes"; import * as iam from "./iam"; +import * as projectConfig from "../functions/projectConfig"; import { Client } from "../apiv2"; import { functionsOrigin } from "../api"; export const API_VERSION = "v1"; +export const CODEBASE_LABEL = "firebase-functions-codebase"; const client = new Client({ urlPrefix: functionsOrigin, apiVersion: API_VERSION }); interface Operation { @@ -549,6 +551,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi "vpcConnectorEgressSettings" ); } + endpoint.codebase = gcfFunction.labels?.[CODEBASE_LABEL] || projectConfig.DEFAULT_CODEBASE; return endpoint; } @@ -638,5 +641,9 @@ export function functionFromEndpoint( "egressSettings" ); } + gcfFunction.labels = { + ...gcfFunction.labels, + [CODEBASE_LABEL]: endpoint.codebase || projectConfig.DEFAULT_CODEBASE, + }; return gcfFunction; } diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 96cfa8b8d57..f90dad24a61 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -9,8 +9,10 @@ import * as backend from "../deploy/functions/backend"; import * as runtimes from "../deploy/functions/runtimes"; import * as proto from "./proto"; import * as utils from "../utils"; +import * as projectConfig from "../functions/projectConfig"; export const API_VERSION = "v2alpha"; +export const CODEBASE_LABEL = "firebase-functions-codebase"; const client = new Client({ urlPrefix: functionsV2Origin, @@ -467,7 +469,10 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage } else if (backend.isCallableTriggered(endpoint)) { gcfFunction.labels = { ...gcfFunction.labels, "deployment-callable": "true" }; } - + gcfFunction.labels = { + ...gcfFunction.labels, + [CODEBASE_LABEL]: endpoint.codebase || projectConfig.DEFAULT_CODEBASE, + }; return gcfFunction; } @@ -546,7 +551,6 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount"); proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount"); proto.copyIfPresent(endpoint, gcfFunction, "labels"); - if (gcfFunction.serviceConfig.vpcConnector) { endpoint.vpc = { connector: gcfFunction.serviceConfig.vpcConnector }; proto.renameIfPresent( @@ -556,6 +560,6 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi "vpcConnectorEgressSettings" ); } - + endpoint.codebase = gcfFunction.labels?.[CODEBASE_LABEL] || projectConfig.DEFAULT_CODEBASE; return endpoint; } diff --git a/src/test/deploy/functions/backend.spec.ts b/src/test/deploy/functions/backend.spec.ts index 616c995f5e3..dd990007e7c 100644 --- a/src/test/deploy/functions/backend.spec.ts +++ b/src/test/deploy/functions/backend.spec.ts @@ -8,6 +8,7 @@ import * as backend from "../../../deploy/functions/backend"; import * as gcf from "../../../gcp/cloudfunctions"; import * as gcfV2 from "../../../gcp/cloudfunctionsv2"; import * as utils from "../../../utils"; +import * as projectConfig from "../../../functions/projectConfig"; describe("Backend", () => { const FUNCTION_NAME: backend.TargetIds = { @@ -21,6 +22,7 @@ describe("Backend", () => { ...FUNCTION_NAME, entryPoint: "function", runtime: "nodejs16", + codebase: projectConfig.DEFAULT_CODEBASE, }; const CLOUD_FUNCTION: Omit = { @@ -525,6 +527,7 @@ describe("Backend", () => { }; bkend.endpoints[endpointUS.region] = { [endpointUS.id]: endpointUS }; bkend.endpoints[endpointEU.region] = { [endpointEU.id]: endpointEU }; + bkend.requiredAPIs = [{ api: "api.google.com", reason: "required" }]; it("allEndpoints", () => { const have = backend.allEndpoints(bkend).sort(backend.compareFunctions); @@ -541,6 +544,7 @@ describe("Backend", () => { [endpointUS.id]: endpointUS, }, }, + requiredAPIs: [{ api: "api.google.com", reason: "required" }], }; expect(have).to.deep.equal(want); }); diff --git a/src/test/deploy/functions/functionsDeployHelper.spec.ts b/src/test/deploy/functions/functionsDeployHelper.spec.ts index 469ff205884..0cebc6c1fd6 100644 --- a/src/test/deploy/functions/functionsDeployHelper.spec.ts +++ b/src/test/deploy/functions/functionsDeployHelper.spec.ts @@ -3,70 +3,320 @@ import { expect } from "chai"; import * as backend from "../../../deploy/functions/backend"; import * as helper from "../../../deploy/functions/functionsDeployHelper"; import { Options } from "../../../options"; +import { DEFAULT_CODEBASE, ValidatedSingle } from "../../../functions/projectConfig"; +import { + EndpointFilter, + parseFunctionSelector, +} from "../../../deploy/functions/functionsDeployHelper"; describe("functionsDeployHelper", () => { - const ENDPOINT: Omit = { + const ENDPOINT: backend.Endpoint = { + id: "foo", platform: "gcfv1", project: "project", region: "us-central1", runtime: "nodejs16", entryPoint: "function", + httpsTrigger: {}, + codebase: DEFAULT_CODEBASE, }; - describe("functionMatchesGroup", () => { - it("should match empty filters", () => { + const BASE_FILTER = { + codebase: DEFAULT_CODEBASE, + }; + + describe("endpointMatchesFilter", () => { + it("should match empty filter", () => { const func = { ...ENDPOINT, id: "id" }; - expect(helper.functionMatchesGroup(func, [])).to.be.true; + expect(helper.endpointMatchesFilter(func, { ...BASE_FILTER, idChunks: [] })).to.be.true; }); it("should match full names", () => { const func = { ...ENDPOINT, id: "id" }; - expect(helper.functionMatchesGroup(func, ["id"])).to.be.true; + expect(helper.endpointMatchesFilter(func, { ...BASE_FILTER, idChunks: ["id"] })).to.be.true; }); it("should match group prefixes", () => { const func = { ...ENDPOINT, id: "group-subgroup-func" }; - expect(helper.functionMatchesGroup(func, ["group", "subgroup", "func"])).to.be.true; - expect(helper.functionMatchesGroup(func, ["group", "subgroup"])).to.be.true; - expect(helper.functionMatchesGroup(func, ["group"])).to.be.true; + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + idChunks: ["group", "subgroup", "func"], + }) + ).to.be.true; + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + idChunks: ["group", "subgroup"], + }) + ).to.be.true; + expect(helper.endpointMatchesFilter(func, { ...BASE_FILTER, idChunks: ["group"] })).to.be + .true; }); - it("should exclude functions that don't match", () => { + it("should not match function that id that don't match", () => { const func = { ...ENDPOINT, id: "id" }; - expect(helper.functionMatchesGroup(func, ["group"])).to.be.false; + expect(helper.endpointMatchesFilter(func, { ...BASE_FILTER, idChunks: ["group"] })).to.be + .false; + }); + + it("should not match function in different codebase", () => { + const func = { ...ENDPOINT, id: "group-subgroup-func" }; + + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + codebase: "another-codebase", + idChunks: ["group", "subgroup", "func"], + }) + ).to.be.false; + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + codebase: "another-codebase", + idChunks: ["group", "subgroup"], + }) + ).to.be.false; + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + codebase: "another-codebase", + idChunks: ["group"], + }) + ).to.be.false; + }); + + it("should match function if backend's codebase is undefined", () => { + const func = { ...ENDPOINT, id: "group-subgroup-func" }; + delete func.codebase; + + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + codebase: "my-codebase", + idChunks: ["group", "subgroup", "func"], + }) + ).to.be.true; + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + codebase: "my-codebase", + idChunks: ["group", "subgroup"], + }) + ).to.be.true; + expect(helper.endpointMatchesFilter(func, { ...BASE_FILTER, idChunks: ["group"] })).to.be + .true; + }); + + it("should match function matching ids given no codebase", () => { + const func = { ...ENDPOINT, id: "group-subgroup-func" }; + + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + codebase: undefined, + idChunks: ["group", "subgroup", "func"], + }) + ).to.be.true; + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + codebase: undefined, + idChunks: ["group", "subgroup"], + }) + ).to.be.true; + expect( + helper.endpointMatchesFilter(func, { + ...BASE_FILTER, + codebase: undefined, + idChunks: ["group"], + }) + ).to.be.true; }); }); - describe("functionMatchesAnyGroup", () => { - it("should match empty filters", () => { + describe("endpointMatchesAnyFilters", () => { + it("should match given no filters", () => { const func = { ...ENDPOINT, id: "id" }; - expect(helper.functionMatchesAnyGroup(func, [[]])).to.be.true; + expect(helper.endpointMatchesAnyFilter(func)).to.be.true; }); it("should match against one filter", () => { const func = { ...ENDPOINT, id: "id" }; - expect(helper.functionMatchesAnyGroup(func, [["id"], ["group"]])).to.be.true; + expect( + helper.endpointMatchesAnyFilter(func, [ + { ...BASE_FILTER, idChunks: ["id"] }, + { ...BASE_FILTER, idChunks: ["group"] }, + ]) + ).to.be.true; }); it("should exclude functions that don't match", () => { const func = { ...ENDPOINT, id: "id" }; - expect(helper.functionMatchesAnyGroup(func, [["group"], ["other-group"]])).to.be.false; + expect( + helper.endpointMatchesAnyFilter(func, [ + { ...BASE_FILTER, idChunks: ["group"] }, + { ...BASE_FILTER, idChunks: ["other-group"] }, + ]) + ).to.be.false; }); }); - describe("getFilterGroups", () => { - it("should parse multiple filters", () => { - const options = { + describe("parseFunctionSelector", () => { + interface Testcase { + desc: string; + selector: string; + expected: EndpointFilter[]; + } + + const testcases: Testcase[] = [ + { + desc: "parses selector without codebase", + selector: "func", + expected: [ + { + codebase: DEFAULT_CODEBASE, + idChunks: ["func"], + }, + { + codebase: "func", + }, + ], + }, + { + desc: "parses group selector (with '.') without codebase", + selector: "g1.func", + expected: [ + { + codebase: DEFAULT_CODEBASE, + idChunks: ["g1", "func"], + }, + { + codebase: "g1.func", + }, + ], + }, + { + desc: "parses group selector (with '-') without codebase", + selector: "g1-func", + expected: [ + { + codebase: DEFAULT_CODEBASE, + idChunks: ["g1", "func"], + }, + { + codebase: "g1-func", + }, + ], + }, + { + desc: "parses group selector (with '-') with codebase", + selector: "node:g1-func", + expected: [ + { + codebase: "node", + idChunks: ["g1", "func"], + }, + ], + }, + ]; + + for (const tc of testcases) { + it(tc.desc, () => { + const actual = parseFunctionSelector(tc.selector); + + expect(actual.length).to.equal(tc.expected.length); + expect(actual).to.deep.include.members(tc.expected); + }); + } + }); + + describe("getEndpointFilters", () => { + interface Testcase { + desc: string; + only: string; + expected: EndpointFilter[]; + } + + const testcases: Testcase[] = [ + { + desc: "should parse multiple selectors", only: "functions:myFunc,functions:myOtherFunc", - } as Options; - expect(helper.getFilterGroups(options)).to.deep.equal([["myFunc"], ["myOtherFunc"]]); + expected: [ + { + codebase: DEFAULT_CODEBASE, + idChunks: ["myFunc"], + }, + { + codebase: "myFunc", + }, + { + codebase: DEFAULT_CODEBASE, + idChunks: ["myOtherFunc"], + }, + { + codebase: "myOtherFunc", + }, + ], + }, + { + desc: "should parse nested selector", + only: "functions:groupA.myFunc", + expected: [ + { + codebase: DEFAULT_CODEBASE, + idChunks: ["groupA", "myFunc"], + }, + { + codebase: "groupA.myFunc", + }, + ], + }, + { + desc: "should parse selector with codebase", + only: "functions:my-codebase:myFunc,functions:another-codebase:anotherFunc", + expected: [ + { + codebase: "my-codebase", + idChunks: ["myFunc"], + }, + { + codebase: "another-codebase", + idChunks: ["anotherFunc"], + }, + ], + }, + { + desc: "should parse nested selector with codebase", + only: "functions:my-codebase:groupA.myFunc", + expected: [ + { + codebase: "my-codebase", + idChunks: ["groupA", "myFunc"], + }, + ], + }, + ]; + + for (const tc of testcases) { + it(tc.desc, () => { + const options = { + only: tc.only, + } as Options; + + const actual = helper.getEndpointFilters(options); + + expect(actual?.length).to.equal(tc.expected.length); + expect(actual).to.deep.include.members(tc.expected); + }); + } + + it("returns undefined given no only option", () => { + expect(helper.getEndpointFilters({})).to.be.undefined; }); - it("should parse nested filters", () => { - const options = { - only: "functions:groupA.myFunc", - } as Options; - expect(helper.getFilterGroups(options)).to.deep.equal([["groupA", "myFunc"]]); + it("returns undefined given no functions selector", () => { + expect(helper.getEndpointFilters({ only: "hosting:siteA,storage:bucketB" })).to.be.undefined; }); }); }); diff --git a/src/test/deploy/functions/release/planner.spec.ts b/src/test/deploy/functions/release/planner.spec.ts index e112ad73ae2..11d6c0cc595 100644 --- a/src/test/deploy/functions/release/planner.spec.ts +++ b/src/test/deploy/functions/release/planner.spec.ts @@ -6,6 +6,7 @@ import * as planner from "../../../../deploy/functions/release/planner"; import * as deploymentTool from "../../../../deploymentTool"; import * as utils from "../../../../utils"; import * as v2events from "../../../../functions/events/v2"; +import * as projectConfig from "../../../../functions/projectConfig"; describe("planner", () => { let logLabeledBullet: sinon.SinonStub; @@ -135,7 +136,7 @@ describe("planner", () => { const have = { updated, deleted, pantheon }; // note: pantheon is not updated in any way - expect(planner.calculateChangesets(want, have, (e) => e.region, {})).to.deep.equal({ + expect(planner.calculateChangesets(want, have, (e) => e.region)).to.deep.equal({ region: { endpointsToCreate: [created], endpointsToUpdate: [ @@ -159,9 +160,7 @@ describe("planner", () => { const have = { updated, deleted, pantheon }; // note: pantheon is deleted because we have deleteAll: true - expect( - planner.calculateChangesets(want, have, (e) => e.region, { deleteAll: true }) - ).to.deep.equal({ + expect(planner.calculateChangesets(want, have, (e) => e.region, true)).to.deep.equal({ region: { endpointsToCreate: [created], endpointsToUpdate: [ @@ -195,7 +194,7 @@ describe("planner", () => { region2mem2Updated ); - expect(planner.createDeploymentPlan(want, have, {})).to.deep.equal({ + expect(planner.createDeploymentPlan(want, have)).to.deep.equal({ "region1-default": { endpointsToCreate: [region1mem1Created], endpointsToUpdate: [ @@ -237,7 +236,11 @@ describe("planner", () => { const want = backend.of(group1Updated, group1Created, group2Updated, group2Created); const have = backend.of(group1Updated, group1Deleted, group2Updated, group2Deleted); - expect(planner.createDeploymentPlan(want, have, { filters: [["g1"]] })).to.deep.equal({ + expect( + planner.createDeploymentPlan(want, have, [ + { codebase: projectConfig.DEFAULT_CODEBASE, idChunks: ["g1"] }, + ]) + ).to.deep.equal({ "region-default": { endpointsToCreate: [group1Created], endpointsToUpdate: [ @@ -266,29 +269,29 @@ describe("planner", () => { sinon.match(/change this with the 'concurrency' option/) ); }); - }); - it("does not warn users about concurrency when inappropriate", () => { - allowV2Upgrades(); - // Concurrency isn't set but this isn't an upgrade operation, so there - // should be no warning - const v2Function: backend.Endpoint = { ...func("id", "region"), platform: "gcfv2" }; + it("does not warn users about concurrency when inappropriate", () => { + allowV2Upgrades(); + // Concurrency isn't set but this isn't an upgrade operation, so there + // should be no warning + const v2Function: backend.Endpoint = { ...func("id", "region"), platform: "gcfv2" }; - planner.createDeploymentPlan(backend.of(v2Function), backend.of(v2Function)); - expect(logLabeledBullet).to.not.have.been.called; + planner.createDeploymentPlan(backend.of(v2Function), backend.of(v2Function)); + expect(logLabeledBullet).to.not.have.been.called; - const v1Function: backend.Endpoint = { ...func("id", "region"), platform: "gcfv1" }; - planner.createDeploymentPlan(backend.of(v1Function), backend.of(v1Function)); - expect(logLabeledBullet).to.not.have.been.called; + const v1Function: backend.Endpoint = { ...func("id", "region"), platform: "gcfv1" }; + planner.createDeploymentPlan(backend.of(v1Function), backend.of(v1Function)); + expect(logLabeledBullet).to.not.have.been.called; - // Upgraded but specified concurrency - const concurrencyUpgraded: backend.Endpoint = { - ...v1Function, - platform: "gcfv2", - concurrency: 80, - }; - planner.createDeploymentPlan(backend.of(concurrencyUpgraded), backend.of(v1Function)); - expect(logLabeledBullet).to.not.have.been.called; + // Upgraded but specified concurrency + const concurrencyUpgraded: backend.Endpoint = { + ...v1Function, + platform: "gcfv2", + concurrency: 80, + }; + planner.createDeploymentPlan(backend.of(concurrencyUpgraded), backend.of(v1Function)); + expect(logLabeledBullet).to.not.have.been.called; + }); }); describe("checkForIllegalUpdate", () => { diff --git a/src/test/functions/projectConfig.spec.ts b/src/test/functions/projectConfig.spec.ts index 58676ec1cb2..d871a0eb034 100644 --- a/src/test/functions/projectConfig.spec.ts +++ b/src/test/functions/projectConfig.spec.ts @@ -3,16 +3,20 @@ import { expect } from "chai"; import * as projectConfig from "../../functions/projectConfig"; import { FirebaseError } from "../../error"; -const TEST_CONFIG = { source: "foo" }; +const TEST_CONFIG_0 = { source: "foo" }; +const TEST_CONFIG_1 = { source: "bar", codebase: "bar" }; describe("projectConfig", () => { describe("normalize", () => { it("normalizes singleton config", () => { - expect(projectConfig.normalize(TEST_CONFIG)).to.deep.equal([TEST_CONFIG]); + expect(projectConfig.normalize(TEST_CONFIG_0)).to.deep.equal([TEST_CONFIG_0]); }); it("normalizes array config", () => { - expect(projectConfig.normalize([TEST_CONFIG])).to.deep.equal([TEST_CONFIG]); + expect(projectConfig.normalize([TEST_CONFIG_0, TEST_CONFIG_0])).to.deep.equal([ + TEST_CONFIG_0, + TEST_CONFIG_0, + ]); }); it("throws error if given empty config", () => { @@ -22,13 +26,14 @@ describe("projectConfig", () => { describe("validate", () => { it("passes validation for simple config", () => { - expect(projectConfig.validate([TEST_CONFIG])).to.deep.equal([TEST_CONFIG]); + expect(projectConfig.validate([TEST_CONFIG_0])).to.deep.equal([TEST_CONFIG_0]); }); it("fails validation given more than one config", () => { - expect(() => - projectConfig.validate([TEST_CONFIG, { ...TEST_CONFIG, source: "bar" }]) - ).to.throw(FirebaseError); + expect(() => projectConfig.validate([TEST_CONFIG_0, TEST_CONFIG_1])).to.throw( + FirebaseError, + /More than one functions.source detected/ + ); }); it("fails validation given config w/o source", () => { @@ -44,15 +49,46 @@ describe("projectConfig", () => { /functions.source must be specified/ ); }); + + it("fails validation given config w/ duplicate source", () => { + expect(() => + projectConfig.validate([TEST_CONFIG_0, { ...TEST_CONFIG_0, codebase: "unique-codebase" }]) + ).to.throw(FirebaseError, /functions.source/); + }); + + it("fails validation given codebase name with capital letters", () => { + expect(() => projectConfig.validate([{ ...TEST_CONFIG_0, codebase: "ABCDE" }])).to.throw( + FirebaseError, + /Invalid codebase name/ + ); + }); + + it("fails validation given codebase name with invalid characters", () => { + expect(() => projectConfig.validate([{ ...TEST_CONFIG_0, codebase: "abc.efg" }])).to.throw( + FirebaseError, + /Invalid codebase name/ + ); + }); + + it("fails validation given long codebase name", () => { + expect(() => + projectConfig.validate([ + { + ...TEST_CONFIG_0, + codebase: "thisismorethan63characterslongxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + ]) + ).to.throw(FirebaseError, /Invalid codebase name/); + }); }); describe("normalizeAndValidate", () => { it("returns normalized config for singleton config", () => { - expect(projectConfig.normalizeAndValidate(TEST_CONFIG)).to.deep.equal([TEST_CONFIG]); + expect(projectConfig.normalizeAndValidate(TEST_CONFIG_0)).to.deep.equal([TEST_CONFIG_0]); }); it("returns normalized config for multi-resource config", () => { - expect(projectConfig.normalizeAndValidate([TEST_CONFIG])).to.deep.equal([TEST_CONFIG]); + expect(projectConfig.normalizeAndValidate([TEST_CONFIG_0])).to.deep.equal([TEST_CONFIG_0]); }); it("fails validation given singleton config w/o source", () => { @@ -76,9 +112,16 @@ describe("projectConfig", () => { ); }); + it("fails validation given config w/ duplicate source", () => { + expect(() => projectConfig.normalizeAndValidate([TEST_CONFIG_0, TEST_CONFIG_0])).to.throw( + FirebaseError, + /More than one functions.source detected/ + ); + }); + it("fails validation given more than one config", () => { expect(() => - projectConfig.normalizeAndValidate([TEST_CONFIG, { ...TEST_CONFIG, source: "bar" }]) + projectConfig.normalizeAndValidate([TEST_CONFIG_0, { ...TEST_CONFIG_0, source: "bar" }]) ).to.throw(FirebaseError, /More than one functions.source detected/); }); }); diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index f6c510e8594..c560d1c2db5 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -5,6 +5,7 @@ import { functionsOrigin } from "../../api"; import * as backend from "../../deploy/functions/backend"; import * as cloudfunctions from "../../gcp/cloudfunctions"; +import * as projectConfig from "../../functions/projectConfig"; describe("cloudfunctions", () => { const FUNCTION_NAME: backend.TargetIds = { @@ -19,12 +20,15 @@ describe("cloudfunctions", () => { ...FUNCTION_NAME, entryPoint: "function", runtime: "nodejs16", + codebase: projectConfig.DEFAULT_CODEBASE, + labels: { [cloudfunctions.CODEBASE_LABEL]: projectConfig.DEFAULT_CODEBASE }, }; const CLOUD_FUNCTION: Omit = { name: "projects/project/locations/region/functions/id", entryPoint: "function", runtime: "nodejs16", + labels: { [cloudfunctions.CODEBASE_LABEL]: projectConfig.DEFAULT_CODEBASE }, }; const HAVE_CLOUD_FUNCTION: cloudfunctions.CloudFunction = { @@ -112,6 +116,7 @@ describe("cloudfunctions", () => { sourceUploadUrl: UPLOAD_URL, httpsTrigger: {}, labels: { + ...CLOUD_FUNCTION.labels, foo: "bar", }, environmentVariables: { @@ -150,6 +155,7 @@ describe("cloudfunctions", () => { }, timeout: "20s", labels: { + ...CLOUD_FUNCTION.labels, "deployment-scheduled": "true", }, }; @@ -170,6 +176,7 @@ describe("cloudfunctions", () => { sourceUploadUrl: UPLOAD_URL, httpsTrigger: {}, labels: { + ...CLOUD_FUNCTION.labels, "deployment-taskqueue": "true", }, }; @@ -178,6 +185,24 @@ describe("cloudfunctions", () => { taskQueueFunction ); }); + + it("should export codebase as label", () => { + expect( + cloudfunctions.functionFromEndpoint( + { + ...ENDPOINT, + codebase: "my-codebase", + httpsTrigger: {}, + }, + UPLOAD_URL + ) + ).to.deep.equal({ + ...CLOUD_FUNCTION, + sourceUploadUrl: UPLOAD_URL, + httpsTrigger: {}, + labels: { ...CLOUD_FUNCTION.labels, [cloudfunctions.CODEBASE_LABEL]: "my-codebase" }, + }); + }); }); describe("endpointFromFunction", () => { @@ -323,6 +348,27 @@ describe("cloudfunctions", () => { httpsTrigger: {}, }); }); + + it("should derive codebase from labels", () => { + expect( + cloudfunctions.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION, + httpsTrigger: {}, + labels: { + ...CLOUD_FUNCTION.labels, + [cloudfunctions.CODEBASE_LABEL]: "my-codebase", + }, + }) + ).to.deep.equal({ + ...ENDPOINT, + httpsTrigger: {}, + labels: { + ...ENDPOINT.labels, + [cloudfunctions.CODEBASE_LABEL]: "my-codebase", + }, + codebase: "my-codebase", + }); + }); }); describe("setInvokerCreate", () => { diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 143288f8eeb..3b3583012cb 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -3,6 +3,7 @@ import { expect } from "chai"; import * as cloudfunctionsv2 from "../../gcp/cloudfunctionsv2"; import * as backend from "../../deploy/functions/backend"; import * as v2events from "../../functions/events/v2"; +import * as projectConfig from "../../functions/projectConfig"; describe("cloudfunctionsv2", () => { const FUNCTION_NAME: backend.TargetIds = { @@ -17,6 +18,8 @@ describe("cloudfunctionsv2", () => { ...FUNCTION_NAME, entryPoint: "function", runtime: "nodejs16", + codebase: projectConfig.DEFAULT_CODEBASE, + labels: { [cloudfunctionsv2.CODEBASE_LABEL]: projectConfig.DEFAULT_CODEBASE }, }; const CLOUD_FUNCTION_V2_SOURCE: cloudfunctionsv2.StorageSource = { @@ -37,6 +40,7 @@ describe("cloudfunctionsv2", () => { environmentVariables: {}, }, serviceConfig: {}, + labels: { [cloudfunctionsv2.CODEBASE_LABEL]: projectConfig.DEFAULT_CODEBASE }, }; const RUN_URI = "https://id-nonce-region-project.run.app"; @@ -66,7 +70,6 @@ describe("cloudfunctionsv2", () => { }); }); describe("functionFromEndpoint", () => { - const UPLOAD_URL = "https://storage.googleapis.com/projects/-/buckets/sample/source.zip"; it("should guard against version mixing", () => { expect(() => { cloudfunctionsv2.functionFromEndpoint( @@ -139,6 +142,7 @@ describe("cloudfunctionsv2", () => { ).to.deep.equal({ ...CLOUD_FUNCTION_V2, labels: { + ...CLOUD_FUNCTION_V2.labels, "deployment-taskqueue": "true", }, }); @@ -169,6 +173,7 @@ describe("cloudfunctionsv2", () => { > = { ...CLOUD_FUNCTION_V2, labels: { + ...CLOUD_FUNCTION_V2.labels, foo: "bar", }, serviceConfig: { @@ -235,6 +240,18 @@ describe("cloudfunctionsv2", () => { cloudfunctionsv2.functionFromEndpoint(complexEndpoint, CLOUD_FUNCTION_V2_SOURCE) ).to.deep.equal(complexGcfFunction); }); + + it("should export codebase as label", () => { + expect( + cloudfunctionsv2.functionFromEndpoint( + { ...ENDPOINT, codebase: "my-codebase", httpsTrigger: {} }, + CLOUD_FUNCTION_V2_SOURCE + ) + ).to.deep.equal({ + ...CLOUD_FUNCTION_V2, + labels: { ...CLOUD_FUNCTION_V2.labels, [cloudfunctionsv2.CODEBASE_LABEL]: "my-codebase" }, + }); + }); }); describe("endpointFromFunction", () => { @@ -383,5 +400,27 @@ describe("cloudfunctionsv2", () => { ...extraFields, }); }); + + it("should derive codebase from labels", () => { + expect( + cloudfunctionsv2.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION_V2, + labels: { + ...CLOUD_FUNCTION_V2.labels, + [cloudfunctionsv2.CODEBASE_LABEL]: "my-codebase", + }, + }) + ).to.deep.equal({ + ...ENDPOINT, + platform: "gcfv2", + uri: RUN_URI, + httpsTrigger: {}, + labels: { + ...ENDPOINT.labels, + [cloudfunctionsv2.CODEBASE_LABEL]: "my-codebase", + }, + codebase: "my-codebase", + }); + }); }); }); From 4ee276e0775fbb43d777ea4e1237f94f966d4a6a Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 5 Apr 2022 08:57:02 -0700 Subject: [PATCH 0218/1699] Adding extensions summary table & refactoring extensions emulator into EmulatorInstance (#4398) --- src/commands/emulators-start.ts | 15 ++++--- src/emulator/controller.ts | 3 +- src/emulator/extensionsEmulator.ts | 72 ++++++++++++++++++++++++++++-- src/emulator/registry.ts | 18 ++++---- 4 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/commands/emulators-start.ts b/src/commands/emulators-start.ts index 8f71aa57cd5..df3d6bda212 100644 --- a/src/commands/emulators-start.ts +++ b/src/commands/emulators-start.ts @@ -7,6 +7,7 @@ import { Emulators, EMULATORS_SUPPORTED_BY_UI } from "../emulator/types"; import * as clc from "cli-color"; import { Constants } from "../emulator/constants"; import { logLabeledWarning } from "../utils"; +import { ExtensionsEmulator } from "../emulator/extensionsEmulator"; // eslint-disable-next-line @typescript-eslint/no-var-requires const Table = require("cli-table"); @@ -76,9 +77,7 @@ module.exports = new Command("emulators:start") const emulatorName = Constants.description(emulator).replace(/ emulator/i, ""); const isSupportedByUi = EMULATORS_SUPPORTED_BY_UI.includes(emulator); // The Extensions emulator runs as part of the Functions emulator, so display the Functions emulators info instead. - const info = EmulatorRegistry.getInfo( - emulator === Emulators.EXTENSIONS ? Emulators.FUNCTIONS : emulator - ); + const info = EmulatorRegistry.getInfo(emulator); if (!info) { return [emulatorName, "Failed to initialize (see above)", "", ""]; } @@ -94,7 +93,13 @@ module.exports = new Command("emulators:start") .map((col) => col.slice(0, head.length)) .filter((v) => v) ); - + let extensionsTable: string = ""; + if (EmulatorRegistry.isRunning(Emulators.EXTENSIONS)) { + const extensionsEmulatorInstance = EmulatorRegistry.get( + Emulators.EXTENSIONS + ) as ExtensionsEmulator; + extensionsTable = extensionsEmulatorInstance.extensionsInfoTable(options); + } logger.info(`\n${successMessageTable} ${emulatorsTable} @@ -104,7 +109,7 @@ ${ : clc.blackBright(" Emulator Hub not running.") } ${clc.blackBright(" Other reserved ports:")} ${reservedPortsString} - +${extensionsTable} Issues? Report them at ${stylizeLink( "https://github.com/firebase/firebase-tools/issues" )} and attach the *-debug.log files. diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index ac84a34e966..9f9064fd01e 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -477,10 +477,9 @@ export async function startAll( extensionsBackends ); emulatableBackends.push(...filteredExtensionsBackends); - // Log the command for analytics void track("Emulator Run", Emulators.EXTENSIONS); - EmulatorRegistry.registerExtensionsEmulator(); + await startEmulator(extensionEmulator); } if (emulatableBackends.length) { diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index f4cf1d1a2a6..af7ed06aa25 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -13,11 +13,12 @@ import { downloadExtensionVersion } from "./download"; import { EmulatableBackend } from "./functionsEmulator"; import { getExtensionFunctionInfo } from "../extensions/emulator/optionsHelper"; import { EmulatorLogger } from "./emulatorLogger"; -import { Emulators } from "./types"; +import { EmulatorInfo, EmulatorInstance, Emulators } from "./types"; import { checkForUnemulatedTriggerTypes, getUnemulatedAPIs } from "./extensions/validation"; import { enableApiURI } from "../ensureApiEnabled"; import { shortenUrl } from "../shortenUrl"; import { Constants } from "./constants"; +import { EmulatorRegistry } from "./registry"; export interface ExtensionEmulatorArgs { projectId: string; @@ -29,8 +30,9 @@ export interface ExtensionEmulatorArgs { // TODO: Consider a different name, since this does not implement the EmulatorInstance interface // Note: At the moment, this doesn't really seem like it needs to be a class. However, I think the // statefulness that enables will be useful once we want to watch .env files for config changes. -export class ExtensionsEmulator { +export class ExtensionsEmulator implements EmulatorInstance { private want: planner.InstanceSpec[] = []; + private backends: EmulatableBackend[] = []; private args: ExtensionEmulatorArgs; private logger = EmulatorLogger.forEmulator(Emulators.EXTENSIONS); @@ -41,6 +43,39 @@ export class ExtensionsEmulator { this.args = args; } + public start(): Promise { + this.logger.logLabeled("DEBUG", "Extensions", "Started Extensions emulator, this is a noop."); + return Promise.resolve(); + } + + public stop(): Promise { + this.logger.logLabeled("DEBUG", "Extensions", "Stopping Extensions emulator, this is a noop."); + return Promise.resolve(); + } + + public connect(): Promise { + this.logger.logLabeled( + "DEBUG", + "Extensions", + "Connecting Extensions emulator, this is a noop." + ); + return Promise.resolve(); + } + + public getInfo(): EmulatorInfo { + const info = EmulatorRegistry.getInfo(Emulators.FUNCTIONS); + if (!info) { + throw new FirebaseError( + "Extensions Emulator is running but Functions emulator is not. This should never happen." + ); + } + return info; + } + + public getName(): Emulators { + return Emulators.EXTENSIONS; + } + // readManifest checks the `extensions` section of `firebase.json` for the extension instances to emulate, // and the `{projectRoot}/extensions` directory for param values. private async readManifest(): Promise { @@ -158,11 +193,12 @@ export class ExtensionsEmulator { public async getExtensionBackends(): Promise { await this.readManifest(); await this.checkAndWarnAPIs(this.want); - return Promise.all( + this.backends = await Promise.all( this.want.map((i: planner.InstanceSpec) => { return this.toEmulatableBackend(i); }) ); + return this.backends; } /** @@ -281,4 +317,34 @@ export class ExtensionsEmulator { } return filteredBackends; } + + private extensionDetailsUILink(backend: EmulatableBackend): string { + const uiInfo = EmulatorRegistry.getInfo(Emulators.UI); + if (!uiInfo || !backend.extensionInstanceId) { + // If the Emulator UI is not running, or if this is not an Extension backend, return an empty string + return ""; + } + const uiUrl = EmulatorRegistry.getInfoHostString(uiInfo); + return clc.underline( + clc.bold(`http://${uiUrl}/${Emulators.EXTENSIONS}/${backend.extensionInstanceId}`) + ); + } + + public extensionsInfoTable(options: Options): string { + const filtedBackends = this.filterUnemulatedTriggers(options, this.backends); + const uiRunning = EmulatorRegistry.isRunning(Emulators.UI); + const tableHead = ["Extension Instance Name", "Extension Ref"]; + if (uiRunning) { + tableHead.push("View in Emulator UI"); + } + const table = new Table({ head: tableHead, style: { head: ["yellow"] } }); + for (const b of filtedBackends) { + if (b.extensionInstanceId) { + const tableEntry = [b.extensionInstanceId, b.extensionVersion?.ref || "Local Extension"]; + if (uiRunning) tableEntry.push(this.extensionDetailsUILink(b)); + table.push(tableEntry); + } + } + return table.toString(); + } } diff --git a/src/emulator/registry.ts b/src/emulator/registry.ts index 992c6195ed9..bf98380eb08 100644 --- a/src/emulator/registry.ts +++ b/src/emulator/registry.ts @@ -3,6 +3,7 @@ import { FirebaseError } from "../error"; import * as portUtils from "./portUtils"; import { Constants } from "./constants"; import { EmulatorLogger } from "./emulatorLogger"; +import { ExtensionsEmulator } from "./extensionsEmulator"; /** * Static registry for running emulators to discover each other. @@ -11,8 +12,6 @@ import { EmulatorLogger } from "./emulatorLogger"; * through the start() and stop() methods which ensures correctness. */ export class EmulatorRegistry { - private static extensionsEmulatorRegistered: boolean = false; - static async start(instance: EmulatorInstance): Promise { const description = Constants.description(instance.getName()); if (this.isRunning(instance.getName())) { @@ -24,13 +23,11 @@ export class EmulatorRegistry { // Start the emulator and wait for it to grab its assigned port. await instance.start(); - - const info = instance.getInfo(); - await portUtils.waitForPortClosed(info.port, info.host); - } - - static registerExtensionsEmulator(): void { - this.extensionsEmulatorRegistered = true; + // No need to wait for the Extensions emulator to close its port, since it runs on the Functions emulator. + if (instance.getName() !== Emulators.EXTENSIONS) { + const info = instance.getInfo(); + await portUtils.waitForPortClosed(info.port, info.host); + } } static async stop(name: Emulators): Promise { @@ -99,7 +96,8 @@ export class EmulatorRegistry { static isRunning(emulator: Emulators): boolean { if (emulator === Emulators.EXTENSIONS) { - return this.extensionsEmulatorRegistered && this.isRunning(Emulators.FUNCTIONS); + // Check if the functions emulator is also running - if not, the Extensions emulator won't work. + return this.INSTANCES.get(emulator) !== undefined && this.isRunning(Emulators.FUNCTIONS); } const instance = this.INSTANCES.get(emulator); return instance !== undefined; From 19d3e24b488b4b96bc4428284648f17404b0146d Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 6 Apr 2022 08:58:41 -0700 Subject: [PATCH 0219/1699] Ignore empty values when writing to .env files (#4402) --- src/extensions/manifest.ts | 1 + src/test/extensions/manifest.spec.ts | 35 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index a9d30a27954..4a125c59e2d 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -174,6 +174,7 @@ async function writeEnvFiles( ): Promise { for (const spec of specs) { const content = Object.entries(spec.params) + .filter((r) => r[1].baseValue !== "") // Don't write empty values .sort((a, b) => { return a[0].localeCompare(b[0]); }) diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts index 2ec626235d4..9f1520889cf 100644 --- a/src/test/extensions/manifest.spec.ts +++ b/src/test/extensions/manifest.spec.ts @@ -249,6 +249,41 @@ describe("manifest", () => { false ); }); + + it("should not write empty values", async () => { + // Chooses to overwrite instead of merge. + sandbox.stub(prompt, "promptOnce").resolves(true); + + await manifest.writeToManifest( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { a: { baseValue: "pikachu" }, b: { baseValue: "" } }, + }, + ], + generateBaseConfig(), + { nonInteractive: false, force: false }, + true /** allowOverwrite */ + ); + expect(writeProjectFileStub).calledWithExactly("firebase.json", { + extensions: { + // Original list deleted here. + "instance-1": "firebase/bigquery-export@1.0.0", + }, + }); + + expect(askWriteProjectFileStub).to.have.been.calledOnce; + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-1.env", + `a=pikachu`, + false + ); + }); }); describe(`${manifest.writeLocalSecrets.name}`, () => { From 0adffd8d1ff8e47a2e3577ec34a5570d56f43913 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 6 Apr 2022 13:26:25 -0400 Subject: [PATCH 0220/1699] Fix missing upload status heade in resumable upload finalise response (#4407) --- scripts/storage-emulator-integration/tests.ts | 7 +++++-- src/emulator/storage/apis/firebase.ts | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index c1c8926f4cc..72b5bb60595 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -2127,14 +2127,17 @@ describe("Storage emulator", () => { }) .expect(200); - await supertest(STORAGE_EMULATOR_HOST) + const uploadStatus = await supertest(STORAGE_EMULATOR_HOST) .put(uploadURL.pathname + uploadURL.search) .set({ // No Authorization required in finalize "X-Goog-Upload-Protocol": "resumable", "X-Goog-Upload-Command": "upload, finalize", }) - .expect(200); + .expect(200) + .then((res) => res.header["x-goog-upload-status"]); + + expect(uploadStatus).to.equal("final"); await supertest(STORAGE_EMULATOR_HOST) .get(`/v0/b/${storageBucket}/o/test_upload.jpg`) diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 0b2a7e3bd73..3a72f1a877b 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -346,6 +346,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { throw err; } + res.header("x-goog-upload-status", "final"); storedMetadata.addDownloadToken(/* shouldTrigger = */ false); return res.status(200).json(new OutgoingFirebaseMetadata(storedMetadata)); } From 63b193da4c7b4d5466f8cd402a8039073d726c7a Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 6 Apr 2022 11:24:09 -0700 Subject: [PATCH 0221/1699] Prefer host from request in storage resumable upload. (#4409) * Prefer host from request in storage resumable upload. * Update Firebase API as well for consistency. * Add CHANGELOG. --- CHANGELOG.md | 1 + src/emulator/auth/utils.ts | 46 ++++----------------- src/emulator/functionsEmulator.ts | 6 +-- src/emulator/registry.ts | 58 ++++++++++++++++++++++++--- src/emulator/storage/apis/firebase.ts | 12 +++--- src/emulator/storage/apis/gcloud.ts | 9 +++-- src/test/emulators/controller.spec.ts | 2 +- src/test/emulators/registry.spec.ts | 58 ++++++++++++++++++++++++++- 8 files changed, 134 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..73a4894efcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fix URL with wrong host returned in storage resumable upload (#4374). diff --git a/src/emulator/auth/utils.ts b/src/emulator/auth/utils.ts index 8e065571839..d7798872628 100644 --- a/src/emulator/auth/utils.ts +++ b/src/emulator/auth/utils.ts @@ -170,48 +170,18 @@ export function logError(err: Error): void { /** * Return a URL object with Auth Emulator protocol, host, and port populated. - * @param req a express request to emulator server; used to infer host - * @return the construted URL object + * + * Compared to EmulatorRegistry.url, this functions prefers the configured host + * and port, which are likely more useful when the link is opened on the same + * device running the emulator (assuming developers click on the link printed on + * terminal or Emulator UI). */ export function authEmulatorUrl(req: express.Request): URL { - // WHATWG URL API has no way to create from parts, so let's use a minimal - // working URL as a starting point. (Let's avoid legacy Node.js `url.format`). - const url = new URL("http://localhost/"); - - // Prefer configured host and port since the link will be most likely opened - // on the same device running the emulator (assuming developers click on the - // link printed on terminal or Emulator UI). - // TODO(yuchenshi): Extract these logic into common emulator utils. - const info = EmulatorRegistry.getInfo(Emulators.AUTH); - if (info) { - // If listening to all IPv4/6 addresses, use loopback addresses instead. - // All-zero addresses are invalid and not tolerated by some browsers / platforms. - // See: https://github.com/firebase/firebase-tools-ui/issues/286 - if (info.host === "0.0.0.0") { - url.hostname = "127.0.0.1"; - } else if (info.host === "::") { - url.hostname = "[::1]"; - } else if (info.host.includes(":")) { - url.hostname = `[${info.host}]`; // IPv6 addresses need to be quoted using brackets. - } else { - url.hostname = info.host; - } - url.port = info.port.toString(); + if (EmulatorRegistry.getInfo(Emulators.AUTH)) { + return EmulatorRegistry.url(Emulators.AUTH); } else { - // Or we can try the Host request header, since it contains hostname + port - // already and has been proven working (since we've got the client request). - const host = req.headers.host; - url.protocol = req.protocol; - - if (host) { - url.host = host; - } else { - // This can probably only happen during testing, but let's warn anyway. - console.warn("Cannot determine host and port of auth emulator server."); - } + return EmulatorRegistry.url(Emulators.AUTH, req); } - - return url; } /** diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 1b06cfbffde..fc8fdff16cf 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -744,13 +744,11 @@ export class FunctionsEmulator implements EmulatorInstance { signatureType: SignatureType, schedule: EventSchedule | undefined ): Promise { - const pubsubPort = EmulatorRegistry.getPort(Emulators.PUBSUB); - if (!pubsubPort) { + const pubsubEmulator = EmulatorRegistry.get(Emulators.PUBSUB) as PubsubEmulator | undefined; + if (!pubsubEmulator) { return false; } - const pubsubEmulator = EmulatorRegistry.get(Emulators.PUBSUB) as PubsubEmulator; - logger.debug(`addPubsubTrigger`, JSON.stringify({ eventTrigger })); // "resource":\"projects/{PROJECT_ID}/topics/{TOPIC_ID}"; diff --git a/src/emulator/registry.ts b/src/emulator/registry.ts index bf98380eb08..4ba7446f059 100644 --- a/src/emulator/registry.ts +++ b/src/emulator/registry.ts @@ -3,7 +3,7 @@ import { FirebaseError } from "../error"; import * as portUtils from "./portUtils"; import { Constants } from "./constants"; import { EmulatorLogger } from "./emulatorLogger"; -import { ExtensionsEmulator } from "./extensionsEmulator"; +import * as express from "express"; /** * Static registry for running emulators to discover each other. @@ -117,6 +117,9 @@ export class EmulatorRegistry { return this.INSTANCES.get(emulator); } + /** + * Get information about an emulator. Use `url` instead for creating URLs. + */ static getInfo(emulator: Emulators): EmulatorInfo | undefined { // For Extensions, return the info for the Functions Emulator. const instance = this.INSTANCES.get( @@ -129,6 +132,9 @@ export class EmulatorRegistry { return instance.getInfo(); } + /** + * Get the host:port string for emulator. Use `url` instead for creating URLs. + */ static getInfoHostString(info: EmulatorInfo): string { const { host, port } = info; @@ -140,13 +146,53 @@ export class EmulatorRegistry { } } - static getPort(emulator: Emulators): number | undefined { - const instance = this.INSTANCES.get(emulator); - if (!instance) { - return undefined; + /** + * Return a URL object with the emulator protocol, host, and port populated. + * @param emulator for retrieving host and port from the registry + * @param req if provided, will prefer reflecting back protocol+host+port from + * the express request (if header available) instead of registry + * @returns a WHATWG URL object with .host set to the emulator host + port + */ + static url(emulator: Emulators, req?: express.Request): URL { + // WHATWG URL API has no way to create from parts, so let's use a minimal + // working URL to start. (Let's avoid legacy Node.js `url.format`.) + const url = new URL("http://unknown/"); + + if (req) { + url.protocol = req.protocol; + // Try the Host request header, since it contains hostname + port already + // and has been proved to work (since we've got the client request). + const host = req.headers.host; + if (host) { + url.host = host; + return url; + } + } + + // Fall back to the host and port from registry. This provides a reasonable + // value in most cases but may not work if the client needs to connect via + // another host, e.g. in Dockers or behind reverse proxies. + const info = EmulatorRegistry.getInfo(emulator); + if (info) { + // If listening to all IPv4/6 addresses, use loopback addresses instead. + // All-zero addresses are invalid and not tolerated by some browsers / OS. + // See: https://github.com/firebase/firebase-tools-ui/issues/286 + if (info.host === "0.0.0.0") { + url.hostname = "127.0.0.1"; + } else if (info.host === "::") { + url.hostname = "[::1]"; + } else if (info.host.includes(":")) { + url.hostname = `[${info.host}]`; // IPv6 addresses need to be quoted. + } else { + url.hostname = info.host; + } + url.port = info.port.toString(); + } else { + // This can probably only happen during testing, but let's warn anyway. + console.warn(`Cannot determine host and port of ${emulator}`); } - return instance.getInfo().port; + return url; } private static INSTANCES: Map = new Map(); diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 3a72f1a877b..f28581ec56e 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -255,13 +255,15 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { res.header("x-goog-upload-chunk-granularity", "10000"); res.header("x-goog-upload-control-url", ""); res.header("x-goog-upload-status", "active"); - const emulatorInfo = EmulatorRegistry.getInfo(Emulators.STORAGE); - res.header( - "x-goog-upload-url", - `http://${req.hostname}:${emulatorInfo?.port}/v0/b/${bucketId}/o?name=${objectId}&upload_id=${upload.id}&upload_protocol=resumable` - ); res.header("x-gupload-uploadid", upload.id); + const uploadUrl = EmulatorRegistry.url(Emulators.STORAGE, req); + uploadUrl.pathname = `/v0/b/${bucketId}/o`; + uploadUrl.searchParams.set("name", objectId); + uploadUrl.searchParams.set("upload_id", upload.id); + uploadUrl.searchParams.set("upload_protocol", "resumable"); + res.header("x-goog-upload-url", uploadUrl.toString()); + return res.sendStatus(200); } diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index d214f42a50a..a861c897f38 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -226,9 +226,12 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { authorization: req.header("authorization"), }); - const { host, port } = emulatorInfo; - const uploadUrl = `http://${host}:${port}/upload/storage/v1/b/${req.params.bucketId}/o?name=${name}&uploadType=resumable&upload_id=${upload.id}`; - return res.header("location", uploadUrl).sendStatus(200); + const uploadUrl = EmulatorRegistry.url(Emulators.STORAGE, req); + uploadUrl.pathname = `/upload/storage/v1/b/${req.params.bucketId}/o`; + uploadUrl.searchParams.set("name", name); + uploadUrl.searchParams.set("uploadType", "resumable"); + uploadUrl.searchParams.set("upload_id", upload.id); + return res.header("location", uploadUrl.toString()).sendStatus(200); } // Multipart upload diff --git a/src/test/emulators/controller.spec.ts b/src/test/emulators/controller.spec.ts index bbfcb2acb3d..ce8ff49497f 100644 --- a/src/test/emulators/controller.spec.ts +++ b/src/test/emulators/controller.spec.ts @@ -17,6 +17,6 @@ describe("EmulatorController", () => { await startEmulator(new FakeEmulator(name, "localhost", 7777)); expect(EmulatorRegistry.isRunning(name)).to.be.true; - expect(EmulatorRegistry.getPort(name)).to.eql(7777); + expect(EmulatorRegistry.getInfo(name)!.port).to.eql(7777); }); }); diff --git a/src/test/emulators/registry.spec.ts b/src/test/emulators/registry.spec.ts index f5c3603a3e6..f4c6cc52bf9 100644 --- a/src/test/emulators/registry.spec.ts +++ b/src/test/emulators/registry.spec.ts @@ -3,6 +3,7 @@ import { EmulatorRegistry } from "../../emulator/registry"; import { expect } from "chai"; import { FakeEmulator } from "./fakeEmulator"; import { findAvailablePort } from "../../emulator/portUtils"; +import * as express from "express"; describe("EmulatorRegistry", () => { afterEach(async () => { @@ -29,7 +30,7 @@ describe("EmulatorRegistry", () => { expect(EmulatorRegistry.isRunning(name)).to.be.true; expect(EmulatorRegistry.listRunning()).to.eql([name]); expect(EmulatorRegistry.get(name)).to.eql(emu); - expect(EmulatorRegistry.getPort(name)).to.eql(port); + expect(EmulatorRegistry.getInfo(name)!.port).to.eql(port); }); it("once stopped, an emulator is no longer running", async () => { @@ -43,4 +44,59 @@ describe("EmulatorRegistry", () => { await EmulatorRegistry.stop(name); expect(EmulatorRegistry.isRunning(name)).to.be.false; }); + + describe("#url", () => { + const name = Emulators.FUNCTIONS; + afterEach(() => { + return EmulatorRegistry.stopAll(); + }); + + it("should craft URL from host and port in registry", async () => { + const port = await findAvailablePort("localhost", 5000); + await EmulatorRegistry.start(new FakeEmulator(name, "localhost", port)); + + expect(EmulatorRegistry.url(name).host).to.eql(`localhost:${port}`); + }); + + it("should quote IPv6 addresses", async () => { + const port = await findAvailablePort("::1", 5000); + await EmulatorRegistry.start(new FakeEmulator(name, "::1", port)); + + expect(EmulatorRegistry.url(name).host).to.eql(`[::1]:${port}`); + }); + + it("should use 127.0.0.1 instead of 0.0.0.0", async () => { + const port = await findAvailablePort("0.0.0.0", 5000); + await EmulatorRegistry.start(new FakeEmulator(name, "0.0.0.0", port)); + + expect(EmulatorRegistry.url(name).host).to.eql(`127.0.0.1:${port}`); + }); + + it("should use ::1 instead of ::", async () => { + const port = await findAvailablePort("::", 5000); + await EmulatorRegistry.start(new FakeEmulator(name, "::", port)); + + expect(EmulatorRegistry.url(name).host).to.eql(`[::1]:${port}`); + }); + + it("should use protocol from request if available", async () => { + const port = await findAvailablePort("localhost", 5000); + await EmulatorRegistry.start(new FakeEmulator(name, "localhost", port)); + + const req = { protocol: "https", headers: {} } as express.Request; + expect(EmulatorRegistry.url(name, req).protocol).to.eql(`https:`); + expect(EmulatorRegistry.url(name, req).host).to.eql(`localhost:${port}`); + }); + + it("should use host from request if available", async () => { + const port = await findAvailablePort("localhost", 5000); + await EmulatorRegistry.start(new FakeEmulator(name, "localhost", port)); + + const req = { + protocol: "http", + headers: { host: "mydomain.example.test:9999" }, + } as express.Request; + expect(EmulatorRegistry.url(name, req).host).to.eql(`${req.headers.host}`); + }); + }); }); From a1732c7d3d07374b073b9fc19648583baa6acae5 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Wed, 6 Apr 2022 15:10:07 -0400 Subject: [PATCH 0222/1699] When channel is defined in the CF3 trigger pass it along to GCF CP (#4362) --- .../functions/runtimes/discovery/v1alpha1.ts | 34 +++++- src/gcp/cloudfunctionsv2.ts | 6 + .../runtimes/discovery/v1alpha1.spec.ts | 111 ++++++++++++++++-- src/test/gcp/cloudfunctionsv2.spec.ts | 31 +++++ 4 files changed, 171 insertions(+), 11 deletions(-) diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 89f78aa9523..00c459547f8 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -1,9 +1,18 @@ import * as backend from "../../backend"; import * as runtimes from ".."; -import { copyIfPresent } from "../../../../gcp/proto"; +import { copyIfPresent, renameIfPresent } from "../../../../gcp/proto"; import { assertKeyTypes, requireKeys } from "./parsing"; import { FirebaseError } from "../../../../error"; +const CHANNEL_NAME_REGEX = new RegExp( + "(projects\\/" + + "(?(?:\\d+)|(?:[A-Za-z]+[A-Za-z\\d-]*[A-Za-z\\d]?))\\/)?" + + "locations\\/" + + "(?[A-Za-z\\d\\-_]+)\\/" + + "channels\\/" + + "(?[A-Za-z\\d\\-_]+)" +); + export type ManifestEndpoint = backend.ServiceConfiguration & backend.Triggered & Partial & @@ -130,6 +139,9 @@ function parseEndpoints( channel: "string", }); triggered = { eventTrigger: ep.eventTrigger }; + renameIfPresent(triggered.eventTrigger, ep.eventTrigger, "channel", "channel", (c) => + resolveChannelName(project, c, defaultRegion) + ); for (const [k, v] of Object.entries(triggered.eventTrigger.eventFilters)) { if (k === "topic" && !v.startsWith("projects/")) { // Construct full pubsub topic name. @@ -216,3 +228,23 @@ function parseEndpoints( return allParsed; } + +function resolveChannelName(projectId: string, channel: string, defaultRegion: string): string { + if (!channel.includes("/")) { + const location = defaultRegion; + const channelId = channel; + return "projects/" + projectId + "/locations/" + location + "/channels/" + channelId; + } + const match = CHANNEL_NAME_REGEX.exec(channel); + if (!match?.groups) { + throw new FirebaseError("Invalid channel name format."); + } + const matchedProjectId = match.groups.project; + const location = match.groups.location; + const channelId = match.groups.channel; + if (matchedProjectId) { + return "projects/" + matchedProjectId + "/locations/" + location + "/channels/" + channelId; + } else { + return "projects/" + projectId + "/locations/" + location + "/channels/" + channelId; + } +} diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index f90dad24a61..03b3457fd0b 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -119,6 +119,10 @@ export interface EventTrigger { // run.routes.invoke permission on the target service. Defaults // to the defualt compute service account. serviceAccountEmail?: string; + + // The name of the channel associated with the trigger in + // `projects/{project}/locations/{location}/channels/{channel}` format. + channel?: string; } export interface CloudFunction { @@ -449,6 +453,7 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage "triggerRegion", "region" ); + proto.copyIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "channel"); if (endpoint.eventTrigger.retry) { logger.warn("Cannot set a retry policy on Cloud Function", endpoint.id); @@ -509,6 +514,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger.eventTrigger.eventFilters[attribute] = value; } } + proto.copyIfPresent(trigger.eventTrigger, gcfFunction.eventTrigger, "channel"); proto.renameIfPresent( trigger.eventTrigger, gcfFunction.eventTrigger, diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 92caba9d6db..633dc7a8c4b 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -104,17 +104,15 @@ describe("backendFromV1Alpha1", () => { region: "global", serviceAccountEmail: "root@", }; - for (const key of ["eventType", "eventFilters"]) { - it(`missing event trigger key ${key}`, () => { - const eventTrigger = { ...validTrigger } as Record; - delete eventTrigger[key]; - assertParserError({ - endpoints: { - func: { ...MIN_ENDPOINT, eventTrigger }, - }, - }); + it(`missing event trigger key eventType`, () => { + const eventTrigger = { ...validTrigger } as Record; + delete eventTrigger["eventType"]; + assertParserError({ + endpoints: { + func: { ...MIN_ENDPOINT, eventTrigger }, + }, }); - } + }); const invalidEntries = { eventType: { foo: "bar" }, @@ -122,6 +120,7 @@ describe("backendFromV1Alpha1", () => { retry: {}, region: ["us-central1"], serviceAccountEmail: ["ldap"], + channel: "foo/bar/channel-id", }; for (const [key, value] of Object.entries(invalidEntries)) { it(`invalid value for event trigger key ${key}`, () => { @@ -367,6 +366,98 @@ describe("backendFromV1Alpha1", () => { expect(parsed).to.deep.equal(expected); }); + describe("channel name", () => { + it("resolves partial (channel ID only) channel resource name", () => { + const eventTrigger: backend.EventTrigger = { + eventType: "com.custom.event.type", + eventFilters: {}, + channel: "my-channel", + region: "us-central1", + serviceAccountEmail: "sa@", + retry: true, + }; + const yaml: v1alpha1.Manifest = { + specVersion: "v1alpha1", + endpoints: { + id: { + ...MIN_ENDPOINT, + eventTrigger, + }, + }, + }; + const expected = backend.of({ + ...DEFAULTED_ENDPOINT, + eventTrigger: { + ...eventTrigger, + eventFilters: {}, + channel: "projects/project/locations/region/channels/my-channel", + }, + }); + const parsed = v1alpha1.backendFromV1Alpha1(yaml, PROJECT, REGION, RUNTIME); + expect(parsed).to.deep.equal(expected); + }); + + it("resolves partial (channel ID and loaction) channel resource name", () => { + const eventTrigger: backend.EventTrigger = { + eventType: "com.custom.event.type", + eventFilters: {}, + channel: "locations/us-wildwest11/channels/my-channel", + region: "us-central1", + serviceAccountEmail: "sa@", + retry: true, + }; + const yaml: v1alpha1.Manifest = { + specVersion: "v1alpha1", + endpoints: { + id: { + ...MIN_ENDPOINT, + eventTrigger, + }, + }, + }; + const expected = backend.of({ + ...DEFAULTED_ENDPOINT, + eventTrigger: { + ...eventTrigger, + eventFilters: {}, + channel: "projects/project/locations/us-wildwest11/channels/my-channel", + }, + }); + const parsed = v1alpha1.backendFromV1Alpha1(yaml, PROJECT, REGION, RUNTIME); + expect(parsed).to.deep.equal(expected); + }); + + it("uses full channel resource name as is", () => { + const eventTrigger: backend.EventTrigger = { + eventType: "com.custom.event.type", + eventFilters: {}, + channel: "projects/newyearresolution1/locations/us-wildwest11/channels/my-channel", + region: "us-central1", + serviceAccountEmail: "sa@", + retry: true, + }; + const yaml: v1alpha1.Manifest = { + specVersion: "v1alpha1", + endpoints: { + id: { + ...MIN_ENDPOINT, + eventTrigger, + }, + }, + }; + const expected = backend.of({ + ...DEFAULTED_ENDPOINT, + eventTrigger: { + ...eventTrigger, + eventFilters: {}, + channel: "projects/newyearresolution1/locations/us-wildwest11/channels/my-channel", + }, + }); + const parsed = v1alpha1.backendFromV1Alpha1(yaml, PROJECT, REGION, RUNTIME); + expect(parsed).to.deep.equal(expected); + }); + }); + it("copies optional fields", () => { const fields: backend.ServiceConfiguration = { concurrency: 42, diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 3b3583012cb..0f3a4833e6d 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -101,6 +101,7 @@ describe("cloudfunctionsv2", () => { serviceName: "compute.googleapis.com", }, retry: false, + channel: "projects/myproject/locations/us-wildwest11/channels/mychannel", }, }; const eventGcfFunction: Omit< @@ -120,6 +121,7 @@ describe("cloudfunctionsv2", () => { value: "compute.googleapis.com", }, ], + channel: "projects/myproject/locations/us-wildwest11/channels/mychannel", }, serviceConfig: { ...CLOUD_FUNCTION_V2.serviceConfig, @@ -317,6 +319,35 @@ describe("cloudfunctionsv2", () => { ).to.deep.equal(want); }); + it("should translate custom event triggers", () => { + const want: backend.Endpoint = { + ...ENDPOINT, + platform: "gcfv2", + uri: RUN_URI, + eventTrigger: { + eventType: "com.custom.event", + eventFilters: { customattr: "customvalue" }, + channel: "projects/myproject/locations/us-wildwest11/channels/mychannel", + retry: false, + }, + }; + expect( + cloudfunctionsv2.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION_V2, + eventTrigger: { + eventType: "com.custom.event", + eventFilters: [ + { + attribute: "customattr", + value: "customvalue", + }, + ], + channel: "projects/myproject/locations/us-wildwest11/channels/mychannel", + }, + }) + ).to.deep.equal(want); + }); + it("should translate task queue functions", () => { expect( cloudfunctionsv2.endpointFromFunction({ From a8df76a5b1217ec9bf8890fc6d15b8c832829ac7 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Wed, 6 Apr 2022 13:53:54 -0700 Subject: [PATCH 0223/1699] Release Firestore Emulator v1.14.2 (#4400) --- CHANGELOG.md | 2 ++ src/emulator/downloadableEmulators.ts | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a4894efcc..4a9482ca432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ - Fix URL with wrong host returned in storage resumable upload (#4374). +- Fixes Firestore emulator transaction expiration and reused bug. +- Fixes Firestore emulator deadlock bug. [#2452](https://github.com/firebase/firebase-tools/issues/2452) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index d5fb753784e..c7d48e5016a 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -39,14 +39,14 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe }, }, firestore: { - downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.1.jar"), - version: "1.14.1", + downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.2.jar"), + version: "1.14.2", opts: { cacheDir: CACHE_DIR, remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.1.jar", - expectedSize: 60416634, - expectedChecksum: "33cffe8065d4250816f257eb19245932", + "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.2.jar", + expectedSize: 60551603, + expectedChecksum: "0930bb09c080b52b1cbc70a91377ccd8", namePrefix: "cloud-firestore-emulator", }, }, From 0c9d523391c6bdaca9a288895825d66d5b219346 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 7 Apr 2022 09:23:32 -0700 Subject: [PATCH 0224/1699] Adding fields to support local extensions in InstanceSpec and ManifestInstanceSpec (#4401) * starting to add support for local extensions * fixing tests * correcting a comment * Switching have to want - not sure why this wasnt before * Fixing ext:export` * Fixing ext:export` * Further refining InstanceSpec types * cleaning up todo --- src/commands/ext-configure.ts | 3 +- src/commands/ext-export.ts | 17 +- src/commands/ext-install.ts | 2 +- src/commands/ext-update.ts | 3 +- src/deploy/extensions/args.ts | 12 +- src/deploy/extensions/planner.ts | 49 +++- src/deploy/extensions/prepare.ts | 2 +- src/deploy/extensions/secrets.ts | 14 +- src/deploy/extensions/tasks.ts | 16 +- src/emulator/extensionsEmulator.ts | 10 +- src/extensions/emulator/optionsHelper.ts | 10 +- src/extensions/export.ts | 21 +- src/extensions/manifest.ts | 9 +- .../emulators/extensions/validation.spec.ts | 4 +- src/test/emulators/extensionsEmulator.spec.ts | 19 +- src/test/extensions/export.spec.ts | 6 +- src/test/extensions/manifest.spec.ts | 276 ++++++++++++++---- .../extensions/provisioningHelper.spec.ts | 1 - src/test/extensions/warnings.spec.ts | 4 +- 19 files changed, 341 insertions(+), 137 deletions(-) diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 3fdca966cfb..e8f899f6e9d 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -89,7 +89,8 @@ export default new Command("ext:configure ") instanceId, ref: targetRef, params: newParamOptions, - paramSpecs: extensionVersion.spec.params, + extensionSpec: extensionVersion.spec, + extensionVersion, }, ], config, diff --git a/src/commands/ext-export.ts b/src/commands/ext-export.ts index 425e50b4c9c..ae9cc1bd78c 100644 --- a/src/commands/ext-export.ts +++ b/src/commands/ext-export.ts @@ -31,14 +31,7 @@ module.exports = new Command("ext:export") // Look up the instances that already exist, // set any secrets to latest version, // and strip project IDs from the param values. - const have = await Promise.all( - ( - await planner.have(projectId) - ).map(async (i) => { - const subbed = await setSecretParamsToLatest(i); - return parameterizeProject(projectId, projectNumber, subbed); - }) - ); + const have = await Promise.all(await planner.have(projectId)); if (have.length === 0) { logger.info( @@ -49,8 +42,14 @@ module.exports = new Command("ext:export") // If an instance spec is missing a ref, that instance must have been installed from a local source. const [withRef, withoutRef] = partition(have, (s) => !!s.ref); + const withRefSubbed = await Promise.all( + withRef.map(async (i) => { + const subbed = await setSecretParamsToLatest(i); + return parameterizeProject(projectId, projectNumber, subbed); + }) + ); - displayExportInfo(withRef, withoutRef); + displayExportInfo(withRefSubbed, withoutRef); if ( !options.nonInteractive && diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 6b89a5c7a9a..a6977505da5 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -273,7 +273,7 @@ async function installToManifest(options: InstallExtensionOptions): Promise [updateSource]") instanceId, ref: refs.parse(newExtensionVersion.ref), params: newParamBindingOptions, - paramSpecs: newExtensionVersion.spec.params, + extensionSpec: newExtensionVersion.spec, + extensionVersion: newExtensionVersion, }, ], config, diff --git a/src/deploy/extensions/args.ts b/src/deploy/extensions/args.ts index ad104880706..2ff5221e1cf 100644 --- a/src/deploy/extensions/args.ts +++ b/src/deploy/extensions/args.ts @@ -1,13 +1,13 @@ import * as planner from "./planner"; export interface Payload { - instancesToCreate?: planner.InstanceSpec[]; - instancesToConfigure?: planner.InstanceSpec[]; - instancesToUpdate?: planner.InstanceSpec[]; - instancesToDelete?: planner.InstanceSpec[]; + instancesToCreate?: planner.DeploymentInstanceSpec[]; + instancesToConfigure?: planner.DeploymentInstanceSpec[]; + instancesToUpdate?: planner.DeploymentInstanceSpec[]; + instancesToDelete?: planner.DeploymentInstanceSpec[]; } export interface Context { - have?: planner.InstanceSpec[]; - want?: planner.InstanceSpec[]; + have?: planner.DeploymentInstanceSpec[]; + want?: planner.DeploymentInstanceSpec[]; } diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index a0d81dc42d4..d0843d4690e 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -7,6 +7,19 @@ import { getFirebaseProjectParams, substituteParams } from "../../extensions/ext import { logger } from "../../logger"; import { readInstanceParam } from "../../extensions/manifest"; import { ParamBindingOptions } from "../../extensions/paramHelper"; +import { readExtensionYaml } from "../../extensions/emulator/specHelper"; + +export interface InstanceSpec { + instanceId: string; + // OneOf: + ref?: refs.Ref; // For published extensions + localPath?: string; // For local extensions + // Used by getExtensionVersion, getExtension, and getExtensionSpec. + // You should stronly prefer accessing via those methods + extensionVersion?: extensionsApi.ExtensionVersion; + extension?: extensionsApi.Extension; + extensionSpec?: extensionsApi.ExtensionSpec; +} /** * Instance spec used by manifest. @@ -17,25 +30,17 @@ import { ParamBindingOptions } from "../../extensions/paramHelper"; * So far this is only used for writing to the manifest, but in the future * we want to read manifest into this interface. */ -export interface ManifestInstanceSpec { - instanceId: string; +export interface ManifestInstanceSpec extends InstanceSpec { params: Record; - ref?: refs.Ref; - paramSpecs?: extensionsApi.Param[]; } -// TODO(lihes): Rename this to something like DeploymentInstanceSpec. /** * Instance spec used for deploying extensions to firebase project or emulator. * * Param bindings are expected to be collapsed from ParamBindingOptions into a Record. */ -export interface InstanceSpec { - instanceId: string; - ref?: refs.Ref; +export interface DeploymentInstanceSpec extends InstanceSpec { params: Record; - extensionVersion?: extensionsApi.ExtensionVersion; - extension?: extensionsApi.Extension; } /** @@ -68,15 +73,31 @@ export async function getExtension(i: InstanceSpec): Promise { + if (!i.extensionSpec) { + if (i.ref) { + const extensionVersion = await getExtensionVersion(i); + i.extensionSpec = extensionVersion.spec; + } else if (i.localPath) { + i.extensionSpec = await readExtensionYaml(i.localPath); + } else { + throw new FirebaseError("InstanceSpec had no ref or localPath, unable to get extensionSpec"); + } + } + return i.extensionSpec; +} + /** * have checks a project for what extension instances are currently installed, * and returns them as a list of instanceSpecs. * @param projectId */ -export async function have(projectId: string): Promise { +export async function have(projectId: string): Promise { const instances = await extensionsApi.listInstances(projectId); return instances.map((i) => { - const dep: InstanceSpec = { + const dep: DeploymentInstanceSpec = { instanceId: i.name.split("/").pop()!, params: i.config.params, }; @@ -107,8 +128,8 @@ export async function want(args: { projectDir: string; extensions: Record; emulatorMode?: boolean; -}): Promise { - const instanceSpecs: InstanceSpec[] = []; +}): Promise { + const instanceSpecs: DeploymentInstanceSpec[] = []; const errors: FirebaseError[] = []; for (const e of Object.entries(args.extensions)) { try { diff --git a/src/deploy/extensions/prepare.ts b/src/deploy/extensions/prepare.ts index a2e53413fcf..f44d0d29eb4 100644 --- a/src/deploy/extensions/prepare.ts +++ b/src/deploy/extensions/prepare.ts @@ -32,7 +32,7 @@ export async function prepare(context: Context, options: Options, payload: Paylo // Check if any extension instance that we want is using secrets, // and ensure the API is enabled if so. - const usingSecrets = await Promise.all(context.have?.map(checkSpecForSecrets)); + const usingSecrets = await Promise.all(context.want?.map(checkSpecForSecrets)); if (usingSecrets.some((i) => i)) { await ensureSecretManagerApiEnabled(options); } diff --git a/src/deploy/extensions/secrets.ts b/src/deploy/extensions/secrets.ts index c86fcbf9e2a..30e37fe4e41 100644 --- a/src/deploy/extensions/secrets.ts +++ b/src/deploy/extensions/secrets.ts @@ -4,7 +4,7 @@ import * as secretUtils from "../../extensions/secretsUtils"; import * as secretManager from "../../gcp/secretManager"; import { Payload } from "./args"; -import { getExtensionVersion, InstanceSpec } from "./planner"; +import { getExtensionVersion, DeploymentInstanceSpec, InstanceSpec } from "./planner"; import { promptCreateSecret } from "../../extensions/askUserForParam"; import { ExtensionSpec, Param, ParamType } from "../../extensions/extensionsApi"; import { FirebaseError } from "../../error"; @@ -21,7 +21,7 @@ import { logLabeledBullet } from "../../utils"; */ export async function handleSecretParams( payload: Payload, - have: InstanceSpec[], + have: DeploymentInstanceSpec[], nonInteractive: boolean ) { for (const i of payload.instancesToCreate ?? []) { @@ -49,7 +49,7 @@ const secretsInSpec = (spec: ExtensionSpec): Param[] => { return spec.params.filter((p) => p.type === ParamType.SECRET); }; -async function handleSecretsCreateInstance(i: InstanceSpec, nonInteractive: boolean) { +async function handleSecretsCreateInstance(i: DeploymentInstanceSpec, nonInteractive: boolean) { const extensionVersion = await getExtensionVersion(i); const secretParams = secretsInSpec(extensionVersion.spec); for (const s of secretParams) { @@ -58,8 +58,8 @@ async function handleSecretsCreateInstance(i: InstanceSpec, nonInteractive: bool } async function handleSecretsUpdateInstance( - i: InstanceSpec, - prevSpec: InstanceSpec, + i: DeploymentInstanceSpec, + prevSpec: DeploymentInstanceSpec, nonInteractive: boolean ) { const extensionVersion = await getExtensionVersion(i); @@ -79,7 +79,7 @@ async function handleSecretsUpdateInstance( async function handleSecretParamForCreate( secretParam: Param, - i: InstanceSpec, + i: DeploymentInstanceSpec, nonInteractive: boolean ): Promise { const providedValue = i.params[secretParam.param]; @@ -141,7 +141,7 @@ async function handleSecretParamForCreate( async function handleSecretParamForUpdate( secretParam: Param, - i: InstanceSpec, + i: DeploymentInstanceSpec, prevValue: string, nonInteractive: boolean ): Promise { diff --git a/src/deploy/extensions/tasks.ts b/src/deploy/extensions/tasks.ts index 36cd96bf92f..317de7d844c 100644 --- a/src/deploy/extensions/tasks.ts +++ b/src/deploy/extensions/tasks.ts @@ -4,7 +4,7 @@ import * as extensionsApi from "../../extensions/extensionsApi"; import * as refs from "../../extensions/refs"; import * as utils from "../../utils"; import { ErrorHandler } from "./errors"; -import { InstanceSpec } from "./planner"; +import { DeploymentInstanceSpec, InstanceSpec } from "./planner"; const isRetryable = (err: any) => err.status === 429 || err.status === 409; @@ -38,11 +38,11 @@ export function extensionsDeploymentHandler( export function createExtensionInstanceTask( projectId: string, - instanceSpec: InstanceSpec, + instanceSpec: DeploymentInstanceSpec, validateOnly: boolean = false ): ExtensionDeploymentTask { const run = async () => { - const res = await extensionsApi.createInstance({ + await extensionsApi.createInstance({ projectId, instanceId: instanceSpec.instanceId, params: instanceSpec.params, @@ -61,11 +61,11 @@ export function createExtensionInstanceTask( export function updateExtensionInstanceTask( projectId: string, - instanceSpec: InstanceSpec, + instanceSpec: DeploymentInstanceSpec, validateOnly: boolean = false ): ExtensionDeploymentTask { const run = async () => { - const res = await extensionsApi.updateInstanceFromRegistry({ + await extensionsApi.updateInstanceFromRegistry({ projectId, instanceId: instanceSpec.instanceId, extRef: refs.toExtensionVersionRef(instanceSpec.ref!), @@ -84,11 +84,11 @@ export function updateExtensionInstanceTask( export function configureExtensionInstanceTask( projectId: string, - instanceSpec: InstanceSpec, + instanceSpec: DeploymentInstanceSpec, validateOnly: boolean = false ): ExtensionDeploymentTask { const run = async () => { - const res = await extensionsApi.configureInstance({ + await extensionsApi.configureInstance({ projectId, instanceId: instanceSpec.instanceId, params: instanceSpec.params, @@ -109,7 +109,7 @@ export function deleteExtensionInstanceTask( instanceSpec: InstanceSpec ): ExtensionDeploymentTask { const run = async () => { - const res = await extensionsApi.deleteInstance(projectId, instanceSpec.instanceId); + await extensionsApi.deleteInstance(projectId, instanceSpec.instanceId); printSuccess(instanceSpec.instanceId, "delete", false); return; }; diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index af7ed06aa25..7eca726f352 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -31,7 +31,7 @@ export interface ExtensionEmulatorArgs { // Note: At the moment, this doesn't really seem like it needs to be a class. However, I think the // statefulness that enables will be useful once we want to watch .env files for config changes. export class ExtensionsEmulator implements EmulatorInstance { - private want: planner.InstanceSpec[] = []; + private want: planner.DeploymentInstanceSpec[] = []; private backends: EmulatableBackend[] = []; private args: ExtensionEmulatorArgs; private logger = EmulatorLogger.forEmulator(Emulators.EXTENSIONS); @@ -194,7 +194,7 @@ export class ExtensionsEmulator implements EmulatorInstance { await this.readManifest(); await this.checkAndWarnAPIs(this.want); this.backends = await Promise.all( - this.want.map((i: planner.InstanceSpec) => { + this.want.map((i: planner.DeploymentInstanceSpec) => { return this.toEmulatableBackend(i); }) ); @@ -205,14 +205,16 @@ export class ExtensionsEmulator implements EmulatorInstance { * toEmulatableBackend turns a InstanceSpec into an EmulatableBackend which can be run by the Functions emulator. * It is exported for testing. */ - public async toEmulatableBackend(instance: planner.InstanceSpec): Promise { + public async toEmulatableBackend( + instance: planner.DeploymentInstanceSpec + ): Promise { const extensionDir = await this.ensureSourceCode(instance); // TODO: This should find package.json, then use that as functionsDir. const functionsDir = path.join(extensionDir, "functions"); // TODO(b/213335255): For local extensions, this should include extensionSpec instead of extensionVersion const env = Object.assign(this.autoPopulatedParams(instance), instance.params); const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = - await getExtensionFunctionInfo(extensionDir, instance.instanceId, env); + await getExtensionFunctionInfo(instance, env); const extension = await planner.getExtension(instance); const extensionVersion = await planner.getExtensionVersion(instance); return { diff --git a/src/extensions/emulator/optionsHelper.ts b/src/extensions/emulator/optionsHelper.ts index 8af9c752a0a..13e4712eb38 100644 --- a/src/extensions/emulator/optionsHelper.ts +++ b/src/extensions/emulator/optionsHelper.ts @@ -7,6 +7,7 @@ import * as localHelper from "../localHelper"; import * as triggerHelper from "./triggerHelper"; import { ExtensionSpec, Param, ParamType, Resource } from "../extensionsApi"; import * as extensionsHelper from "../extensionsHelper"; +import * as planner from "../../deploy/extensions/planner"; import { Config } from "../../config"; import { FirebaseError } from "../../error"; import { EmulatorLogger } from "../../emulator/emulatorLogger"; @@ -42,8 +43,7 @@ export async function buildOptions(options: any): Promise { // TODO: Better name? Also, should this be in extensionsEmulator instead? export async function getExtensionFunctionInfo( - extensionDir: string, - instanceId: string, + instance: planner.InstanceSpec, paramValues: Record ): Promise<{ nodeMajorVersion: number; @@ -51,12 +51,12 @@ export async function getExtensionFunctionInfo( nonSecretEnv: Record; secretEnvVariables: SecretEnvVar[]; }> { - const spec = await specHelper.readExtensionYaml(extensionDir); + const spec = await planner.getExtensionSpec(instance); const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, paramValues); const extensionTriggers: ParsedTriggerDefinition[] = functionResources .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r)) .map((trigger) => { - trigger.name = `ext-${instanceId}-${trigger.name}`; + trigger.name = `ext-${instance.instanceId}-${trigger.name}`; return trigger; }); const nodeMajorVersion = specHelper.getNodeVersion(functionResources); @@ -69,8 +69,10 @@ export async function getExtensionFunctionInfo( secretEnvVariables, }; } + const isSecretParam = (p: Param) => p.type === extensionsHelper.SpecParamType.SECRET || p.type === ParamType.SECRET; + /** * getNonSecretEnv checks extension spec for secret params, and returns env without those secret params * @param params A list of params to check for secret params diff --git a/src/extensions/export.ts b/src/extensions/export.ts index 94525f573d7..871f6089347 100644 --- a/src/extensions/export.ts +++ b/src/extensions/export.ts @@ -1,4 +1,8 @@ -import { getExtensionVersion, InstanceSpec } from "../deploy/extensions/planner"; +import { + getExtensionVersion, + DeploymentInstanceSpec, + InstanceSpec, +} from "../deploy/extensions/planner"; import { humanReadable } from "../deploy/extensions/deploymentSummary"; import { logger } from "../logger"; import { parseSecretVersionResourceName, toSecretVersionResourceName } from "../gcp/secretManager"; @@ -11,8 +15,8 @@ import { getActiveSecrets } from "./secretsUtils"; export function parameterizeProject( projectId: string, projectNumber: string, - spec: InstanceSpec -): InstanceSpec { + spec: DeploymentInstanceSpec +): DeploymentInstanceSpec { const newParams: Record = {}; for (const [key, val] of Object.entries(spec.params)) { const p1 = val.replace(projectId, "${param:PROJECT_ID}"); @@ -28,7 +32,9 @@ export function parameterizeProject( * setSecretParamsToLatest searches spec.params for any secret paramsthat are active, and changes their version to latest. * We do this because old secret versions are destroyed on instance update, and to ensure that cross project installs work smoothly. */ -export async function setSecretParamsToLatest(spec: InstanceSpec): Promise { +export async function setSecretParamsToLatest( + spec: DeploymentInstanceSpec +): Promise { const newParams = { ...spec.params }; const extensionVersion = await getExtensionVersion(spec); const activeSecrets = getActiveSecrets(extensionVersion.spec, newParams); @@ -42,7 +48,10 @@ export async function setSecretParamsToLatest(spec: InstanceSpec): Promise { for (const spec of specs) { - if (!spec.paramSpecs) { + const extensionSpec = await getExtensionSpec(spec); + if (!extensionSpec.params) { continue; } const writeBuffer: Record = {}; - const locallyOverridenSecretParams = spec.paramSpecs.filter( + const locallyOverridenSecretParams = extensionSpec.params.filter( (p) => p.type === ParamType.SECRET && spec.params[p.param].local ); for (const paramSpec of locallyOverridenSecretParams) { diff --git a/src/test/emulators/extensions/validation.spec.ts b/src/test/emulators/extensions/validation.spec.ts index f79ef27bbdc..aef1f6153b5 100644 --- a/src/test/emulators/extensions/validation.spec.ts +++ b/src/test/emulators/extensions/validation.spec.ts @@ -4,7 +4,7 @@ import * as sinon from "sinon"; import * as validation from "../../../emulator/extensions/validation"; import * as ensureApiEnabled from "../../../ensureApiEnabled"; import * as controller from "../../../emulator/controller"; -import { InstanceSpec } from "../../../deploy/extensions/planner"; +import { DeploymentInstanceSpec } from "../../../deploy/extensions/planner"; import { EmulatableBackend } from "../../../emulator/functionsEmulator"; import { Emulators } from "../../../emulator/types"; import { EventTrigger, ParsedTriggerDefinition } from "../../../emulator/functionsEmulatorShared"; @@ -26,7 +26,7 @@ const TEST_OPTIONS: Options = { rc: new RC(), config: new Config("."), }; -function fakeInstanceSpecWithAPI(instanceId: string, apiName: string): InstanceSpec { +function fakeInstanceSpecWithAPI(instanceId: string, apiName: string): DeploymentInstanceSpec { return { instanceId, params: {}, diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts index c76023e3e59..4aa26ec85a9 100644 --- a/src/test/emulators/extensionsEmulator.spec.ts +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -26,7 +26,22 @@ const TEST_EXTENSION_VERSION: ExtensionVersion = { hash: "abc123", spec: { name: "publishers/firebase/extensions/storage-resize-images/versions/0.1.18", - resources: [], + resources: [ + { + type: "firebaseextensions.v1beta.function", + name: "generateResizedImage", + description: `Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, + then stores the resized images in the same bucket. Optionally keeps or deletes the original images.`, + properties: { + location: "${param:LOCATION}", + runtime: "nodejs10", + eventTrigger: { + eventType: "google.storage.object.finalize", + resource: "projects/_/buckets/${param:IMG_BUCKET}", + }, + }, + }, + ], params: [], version: "0.1.18", sourceUrl: "https://fake.test", @@ -45,7 +60,7 @@ describe("Extensions Emulator", () => { }); const testCases: { desc: string; - input: planner.InstanceSpec; + input: planner.DeploymentInstanceSpec; expected: EmulatableBackend; }[] = [ { diff --git a/src/test/extensions/export.spec.ts b/src/test/extensions/export.spec.ts index 5a151ec4049..79914c28f65 100644 --- a/src/test/extensions/export.spec.ts +++ b/src/test/extensions/export.spec.ts @@ -1,9 +1,7 @@ import { expect } from "chai"; -import * as sinon from "sinon"; import { parameterizeProject, setSecretParamsToLatest } from "../../extensions/export"; -import { InstanceSpec } from "../../deploy/extensions/planner"; -import * as secretUtils from "../../extensions/secretsUtils"; +import { DeploymentInstanceSpec } from "../../deploy/extensions/planner"; import { ParamType } from "../../extensions/extensionsApi"; describe("ext:export helpers", () => { @@ -79,7 +77,7 @@ describe("ext:export helpers", () => { ]; for (const t of tests) { it(t.desc, async () => { - const testSpec: InstanceSpec = { + const testSpec: DeploymentInstanceSpec = { instanceId: "my-instance", params: t.params, extensionVersion: { diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts index 9f1520889cf..6dcd35c4f4f 100644 --- a/src/test/extensions/manifest.spec.ts +++ b/src/test/extensions/manifest.spec.ts @@ -117,6 +117,24 @@ describe("manifest", () => { version: "1.0.0", }, params: { a: { baseValue: "pikachu" }, b: { baseValue: "bulbasaur" } }, + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.STRING, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, }, { instanceId: "instance-2", @@ -126,6 +144,24 @@ describe("manifest", () => { version: "2.0.0", }, params: { a: { baseValue: "eevee" }, b: { baseValue: "squirtle" } }, + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.SECRET, + }, + ], + }, }, ], generateBaseConfig(), @@ -164,6 +200,24 @@ describe("manifest", () => { version: "1.0.0", }, params: { b: { baseValue: "bulbasaur" }, a: { baseValue: "absol" } }, + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.STRING, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, }, { instanceId: "instance-2", @@ -173,6 +227,24 @@ describe("manifest", () => { version: "2.0.0", }, params: { e: { baseValue: "eevee" }, s: { baseValue: "squirtle" } }, + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.STRING, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, }, ], generateBaseConfig(), @@ -214,6 +286,24 @@ describe("manifest", () => { version: "1.0.0", }, params: { a: { baseValue: "pikachu" }, b: { baseValue: "bulbasaur" } }, + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.STRING, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, }, { instanceId: "instance-2", @@ -223,6 +313,24 @@ describe("manifest", () => { version: "2.0.0", }, params: { a: { baseValue: "eevee" }, b: { baseValue: "squirtle" } }, + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.STRING, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, }, ], generateBaseConfig(), @@ -264,6 +372,24 @@ describe("manifest", () => { version: "1.0.0", }, params: { a: { baseValue: "pikachu" }, b: { baseValue: "" } }, + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.STRING, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, }, ], generateBaseConfig(), @@ -311,18 +437,24 @@ describe("manifest", () => { a: { baseValue: "base", local: "pikachu" }, b: { baseValue: "base", local: "bulbasaur" }, }, - paramSpecs: [ - { - param: "a", - label: "", - type: ParamType.SECRET, - }, - { - param: "b", - label: "", - type: ParamType.SECRET, - }, - ], + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.SECRET, + }, + ], + }, }, { instanceId: "instance-2", @@ -335,18 +467,24 @@ describe("manifest", () => { a: { baseValue: "base", local: "eevee" }, b: { baseValue: "base", local: "squirtle" }, }, - paramSpecs: [ - { - param: "a", - label: "", - type: ParamType.SECRET, - }, - { - param: "b", - label: "", - type: ParamType.SECRET, - }, - ], + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.SECRET, + }, + ], + }, }, ], generateBaseConfig(), @@ -380,18 +518,24 @@ describe("manifest", () => { a: { baseValue: "base", local: "pikachu" }, b: { baseValue: "base" }, }, - paramSpecs: [ - { - param: "a", - label: "", - type: ParamType.SECRET, - }, - { - param: "b", - label: "", - type: ParamType.SECRET, - }, - ], + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.SECRET, + }, + ], + }, }, ], generateBaseConfig(), @@ -420,18 +564,24 @@ describe("manifest", () => { a: { baseValue: "base", local: "pikachu" }, b: { baseValue: "base", local: "bulbasaur" }, }, - paramSpecs: [ - { - param: "a", - label: "", - type: ParamType.SECRET, - }, - { - param: "b", - label: "", - type: ParamType.STRING, - }, - ], + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, }, ], generateBaseConfig(), @@ -461,18 +611,24 @@ describe("manifest", () => { a: { baseValue: "base" }, b: { baseValue: "base" }, }, - paramSpecs: [ - { - param: "a", - label: "", - type: ParamType.SECRET, - }, - { - param: "b", - label: "", - type: ParamType.STRING, - }, - ], + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + params: [ + { + param: "a", + label: "", + type: ParamType.SECRET, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, }, ], generateBaseConfig(), diff --git a/src/test/extensions/provisioningHelper.spec.ts b/src/test/extensions/provisioningHelper.spec.ts index b62c80d47e8..ff90c5d6f44 100644 --- a/src/test/extensions/provisioningHelper.spec.ts +++ b/src/test/extensions/provisioningHelper.spec.ts @@ -6,7 +6,6 @@ import * as provisioningHelper from "../../extensions/provisioningHelper"; import * as extensionsApi from "../../extensions/extensionsApi"; import { FirebaseError } from "../../error"; -const TEST_INSTANCES_RESPONSE = {}; const PROJECT_ID = "test-project"; const SPEC_WITH_NOTHING = { diff --git a/src/test/extensions/warnings.spec.ts b/src/test/extensions/warnings.spec.ts index d86a7d04275..5edac3fecbc 100644 --- a/src/test/extensions/warnings.spec.ts +++ b/src/test/extensions/warnings.spec.ts @@ -10,7 +10,7 @@ import { RegistryLaunchStage, Visibility, } from "../../extensions/extensionsApi"; -import { InstanceSpec } from "../../deploy/extensions/planner"; +import { DeploymentInstanceSpec } from "../../deploy/extensions/planner"; const testExtensionVersion: ExtensionVersion = { name: "test", @@ -41,7 +41,7 @@ const testInstanceSpec = ( publisherId: string, instanceId: string, launchStage: RegistryLaunchStage -): InstanceSpec => { +): DeploymentInstanceSpec => { return { instanceId, ref: { From c136d6f87208408620e08bc726fd13e9344639d3 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Thu, 7 Apr 2022 14:25:19 -0400 Subject: [PATCH 0225/1699] Filters out local source extensions when deploying (#4416) --- src/deploy/extensions/planner.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index d0843d4690e..16dafef587e 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -3,7 +3,11 @@ import * as semver from "semver"; import * as extensionsApi from "../../extensions/extensionsApi"; import * as refs from "../../extensions/refs"; import { FirebaseError } from "../../error"; -import { getFirebaseProjectParams, substituteParams } from "../../extensions/extensionsHelper"; +import { + getFirebaseProjectParams, + isLocalPath, + substituteParams, +} from "../../extensions/extensionsHelper"; import { logger } from "../../logger"; import { readInstanceParam } from "../../extensions/manifest"; import { ParamBindingOptions } from "../../extensions/paramHelper"; @@ -134,6 +138,13 @@ export async function want(args: { for (const e of Object.entries(args.extensions)) { try { const instanceId = e[0]; + // TODO(lihes): Remove once firebase deploy supports ext with local source. + if (isLocalPath(e[1])) { + logger.warn( + `Unable to deploy instance ${instanceId} because it has a local source, please use "firebase ext:install" instead.` + ); + continue; + } const ref = refs.parse(e[1]); ref.version = await resolveVersion(ref); From 8e19a6fa247b7f18a09ba96dd6d3f699078c94e5 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Thu, 7 Apr 2022 14:37:34 -0400 Subject: [PATCH 0226/1699] Allow local extensions in ext:* --local commands (#4405) * add warnings and todos * Allow local source in ext:install * cleanup install * update configure * format * Update extensionsHelper.ts * Fixes * add tests * Update ext-install.ts * Update ext-update.ts * Update ext-install.ts * pr fixes --- src/commands/ext-configure.ts | 31 ++++-- src/commands/ext-install.ts | 103 ++++++++----------- src/commands/ext-update.ts | 14 ++- src/extensions/extensionsHelper.ts | 65 ++++++++---- src/extensions/manifest.ts | 36 ++++++- src/test/extensions/extensionsHelper.spec.ts | 37 ++++--- src/test/extensions/manifest.spec.ts | 45 +++++++- 7 files changed, 220 insertions(+), 111 deletions(-) diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index e8f899f6e9d..8ea557307d0 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -10,7 +10,12 @@ import { Command } from "../command"; import { FirebaseError } from "../error"; import { needProjectId, getProjectId } from "../projectUtils"; import * as extensionsApi from "../extensions/extensionsApi"; -import { logPrefix, diagnoseAndFixProject } from "../extensions/extensionsHelper"; +import { + logPrefix, + diagnoseAndFixProject, + createSourceFromLocation, + isLocalPath, +} from "../extensions/extensionsHelper"; import * as paramHelper from "../extensions/paramHelper"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; @@ -50,10 +55,18 @@ export default new Command("ext:configure ") } const config = manifest.loadConfig(options); - const targetRef = manifest.getInstanceRef(instanceId, config); - const extensionVersion = await extensionsApi.getExtensionVersion( - refs.toExtensionVersionRef(targetRef) - ); + + const refOrPath = manifest.getInstanceTarget(instanceId, config); + const isLocalSource = isLocalPath(refOrPath); + + let spec: extensionsApi.ExtensionSpec; + if (isLocalSource) { + const source = await createSourceFromLocation(needProjectId({ projectId }), refOrPath); + spec = source.spec; + } else { + const extensionVersion = await extensionsApi.getExtensionVersion(refOrPath); + spec = extensionVersion.spec; + } const oldParamValues = manifest.readInstanceParam({ instanceId, @@ -61,7 +74,7 @@ export default new Command("ext:configure ") }); const [immutableParams, tbdParams] = partition( - extensionVersion.spec.params, + spec.params, (param) => param.immutable ?? false ); infoImmutableParams(immutableParams, oldParamValues); @@ -87,10 +100,10 @@ export default new Command("ext:configure ") [ { instanceId, - ref: targetRef, + ref: !isLocalSource ? refs.parse(refOrPath) : undefined, + localPath: isLocalSource ? refOrPath : undefined, params: newParamOptions, - extensionSpec: extensionVersion.spec, - extensionVersion, + extensionSpec: spec, }, ], config, diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index a6977505da5..7245c897196 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -28,9 +28,10 @@ import { promptForOfficialExtension, promptForRepeatInstance, promptForValidInstanceId, - isLocalOrURLPath, diagnoseAndFixProject, isUrlPath, + isLocalPath, + canonicalizeRefInput, } from "../extensions/extensionsHelper"; import { update } from "../extensions/updateHelper"; import { getRandomString } from "../extensions/utils"; @@ -87,26 +88,32 @@ export default new Command("ext:install [extensionName]") } } let source; - let extVersion; + let extensionVersion; - // TODO(b/220900194): Remove tracking of url install once we remove this feature. + // TODO(b/220900194): Remove when deprecating old install flow. + // --local doesn't support urlPath so this will become dead codepath. if (isUrlPath(extensionName)) { - void track("Extension Install", "Install by url path", options.interactive ? 1 : 0); + throw new FirebaseError( + `Installing with a source url is no longer supported in the CLI. Please use Firebase Console instead.` + ); } - // If the user types in URL, or a local path (prefixed with ~/, ../, or ./), install from local/URL source. + // If the user types in a local path (prefixed with ~/, ../, or ./), install from local source. // Otherwise, treat the input as an extension reference and proceed with reference-based installation. - if (isLocalOrURLPath(extensionName)) { + if (isLocalPath(extensionName)) { + // TODO(b/228444119): Create source should happen at deploy time. + // Should parse spec locally so we don't need project ID. + source = await createSourceFromLocation(needProjectId({ projectId }), extensionName); + displayExtInfo(extensionName, "", source.spec); void track("Extension Install", "Install by Source", options.interactive ? 1 : 0); - if (options.local) { - throw new FirebaseError( - "Installing a local source locally is not supported yet, please use ext:dev:emulator commands" - ); - } - source = await infoInstallBySource(needProjectId({ projectId }), extensionName); } else { void track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); - extVersion = await infoInstallByReference(extensionName, options.interactive); + extensionName = canonicalizeRefInput(extensionName); + extensionVersion = await extensionsApi.getExtensionVersion(extensionName); + await infoExtensionVersion({ + extensionName, + extensionVersion, + }); } if ( !(await confirm({ @@ -117,12 +124,12 @@ export default new Command("ext:install [extensionName]") ) { return; } - if (!source && !extVersion) { + if (!source && !extensionVersion) { throw new FirebaseError( "Could not find a source. Please specify a valid source to continue." ); } - const spec = source?.spec || extVersion?.spec; + const spec = source?.spec ?? extensionVersion?.spec; if (!spec) { throw new FirebaseError( `Could not find the extension.yaml for extension '${clc.bold( @@ -146,7 +153,7 @@ export default new Command("ext:install [extensionName]") projectId, extensionName, source, - extVersion, + extVersion: extensionVersion, nonInteractive: options.nonInteractive, force: options.force, }); @@ -170,7 +177,7 @@ export default new Command("ext:install [extensionName]") projectId: projectId, extensionName, source, - extVersion, + extVersion: extensionVersion, nonInteractive: options.nonInteractive, force: options.force, }); @@ -184,46 +191,18 @@ export default new Command("ext:install [extensionName]") } }); -async function infoInstallBySource( - projectId: string, - extensionName: string -): Promise { - // Create a one off source to use for the install flow. - let source; - try { - source = await createSourceFromLocation(projectId, extensionName); - } catch (err: any) { - throw new FirebaseError( - `Unable to find published extension '${clc.bold(extensionName)}', ` + - `and encountered the following error when trying to create an instance of extension '${clc.bold( - extensionName - )}':\n ${err.message}` - ); - } - displayExtInfo(extensionName, "", source.spec); - return source; -} - -async function infoInstallByReference( - extensionName: string, - interactive: boolean -): Promise { - // Infer firebase if publisher ID not provided. - if (extensionName.split("/").length < 2) { - const [extensionID, version] = extensionName.split("@"); - extensionName = `firebase/${extensionID}@${version || "latest"}`; - } - // Get the correct version for a given extension reference from the Registry API. - const ref = refs.parse(extensionName); +async function infoExtensionVersion(args: { + extensionName: string; + extensionVersion: extensionsApi.ExtensionVersion; +}): Promise { + const ref = refs.parse(args.extensionName); const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref)); - if (!ref.version) { - void track("Extension Install", "Install by Extension Version Ref", interactive ? 1 : 0); - extensionName = `${extensionName}@latest`; - } - const extVersion = await extensionsApi.getExtensionVersion(extensionName); - displayExtInfo(extensionName, ref.publisherId, extVersion.spec, true); - await displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion); - return extVersion; + displayExtInfo(args.extensionName, ref.publisherId, args.extensionVersion.spec, true); + await displayWarningPrompts( + ref.publisherId, + extension.registryLaunchStage, + args.extensionVersion + ); } interface InstallExtensionOptions { @@ -243,8 +222,11 @@ interface InstallExtensionOptions { * @param options */ async function installToManifest(options: InstallExtensionOptions): Promise { - const { projectId, extensionName, extVersion, paramsEnvPath, nonInteractive, force } = options; - const spec = extVersion?.spec; + const { projectId, extensionName, extVersion, source, paramsEnvPath, nonInteractive, force } = + options; + const isLocalSource = isLocalPath(extensionName); + + const spec = extVersion?.spec ?? source?.spec; if (!spec) { throw new FirebaseError( `Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.` @@ -266,12 +248,13 @@ async function installToManifest(options: InstallExtensionOptions): Promise [updateSource]") .action(async (instanceId: string, updateSource: string, options: Options) => { if (options.local) { const projectId = getProjectId(options); - const config = manifest.loadConfig(options); + + const oldRefOrPath = manifest.getInstanceTarget(instanceId, config); + if (isLocalPath(oldRefOrPath)) { + throw new FirebaseError( + `Updating an extension with local source is not neccessary. ` + + `Rerun "firebase deploy" or restart the emulator after making changes to your local extension source. ` + + `If you've edited the extension param spec, you can edit an extension instance's params ` + + `interactively by running "firebase ext:configure --local {instance-id}"` + ); + } + const oldRef = manifest.getInstanceRef(instanceId, config); const oldExtensionVersion = await extensionsApi.getExtensionVersion( refs.toExtensionVersionRef(oldRef) ); updateSource = inferUpdateSource(updateSource, refs.toExtensionRef(oldRef)); - // TODO(b/213335255): Allow local sources after manifest supports that. const newSourceOrigin = getSourceOrigin(updateSource); if ( ![SourceOrigin.PUBLISHED_EXTENSION, SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes( diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index cabfe0428c8..61a5332c08c 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -548,31 +548,35 @@ export async function createSourceFromLocation( projectId: string, sourceUri: string ): Promise { + const extensionRoot = "/"; let packageUri: string; - let extensionRoot: string; let objectPath = ""; - if (!sourceUri.startsWith("https:")) { - const uploadSpinner = ora(" Archiving and uploading extension source code"); - try { - uploadSpinner.start(); - objectPath = await archiveAndUploadSource(sourceUri, EXTENSIONS_BUCKET_NAME); - uploadSpinner.succeed(" Uploaded extension source code"); - packageUri = storageOrigin + objectPath + "?alt=media"; - extensionRoot = "/"; - } catch (err: any) { - uploadSpinner.fail(); - throw new FirebaseError(`Failed to archive and upload extension source, ${err}`, { + + let spinner = ora(" Archiving and uploading extension source code"); + try { + spinner.start(); + objectPath = await archiveAndUploadSource(sourceUri, EXTENSIONS_BUCKET_NAME); + spinner.succeed(" Uploaded extension source code"); + + spinner = ora(" Creating an extension source based on uploaded source code"); + spinner.start(); + packageUri = storageOrigin + objectPath + "?alt=media"; + const res = await createSource(projectId, packageUri, extensionRoot); + logger.debug("Created new Extension Source %s", res.name); + spinner.succeed(" Created extension source code"); + + // if we uploaded an object to user's bucket, delete it after "createSource" copies it into extension service's bucket. + await deleteUploadedSource(objectPath); + return res; + } catch (err: any) { + spinner.fail(); + throw new FirebaseError( + `Failed to archive and upload extension source from ${sourceUri}, ${err}`, + { original: err, - }); - } - } else { - [packageUri, extensionRoot] = sourceUri.split("#"); + } + ); } - const res = await createSource(projectId, packageUri, extensionRoot); - logger.debug("Created new Extension Source %s", res.name); - // if we uploaded an object, delete it - await deleteUploadedSource(objectPath); - return res; } /** @@ -774,3 +778,22 @@ export async function diagnoseAndFixProject(options: any): Promise { throw new FirebaseError("Unable to proceed until all issues are resolved."); } } + +/** + * Canonicalize a user-inputted ref string. + * 1. Infer firebase publisher if not provided + * 2. Infer "latest" as the version if not provided + */ +export function canonicalizeRefInput(extensionName: string): string { + // Infer firebase if publisher ID not provided. + if (extensionName.split("/").length < 2) { + const [extensionID, version] = extensionName.split("@"); + extensionName = `firebase/${extensionID}@${version || "latest"}`; + } + // Get the correct version for a given extension reference from the Registry API. + const ref = refs.parse(extensionName); + if (!ref.version) { + extensionName = `${extensionName}@latest`; + } + return extensionName; +} diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index 968237173d6..a70f7d175d2 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -8,7 +8,7 @@ import { promptOnce } from "../prompt"; import { readEnvFile } from "./paramHelper"; import { FirebaseError } from "../error"; import * as utils from "../utils"; -import { logPrefix } from "./extensionsHelper"; +import { isLocalPath, logPrefix } from "./extensionsHelper"; import { ParamType } from "./extensionsApi"; export const ENV_DIRECTORY = "extensions"; @@ -150,18 +150,44 @@ export function instanceExists(instanceId: string, config: Config): boolean { return !!config.get("extensions", {})[instanceId]; } -export function getInstanceRef(instanceId: string, config: Config): refs.Ref { +/** + * Gets the instance's extension ref string or local path given an instanceId. + */ +export function getInstanceTarget(instanceId: string, config: Config): string { if (!instanceExists(instanceId, config)) { throw new FirebaseError(`Could not find extension instance ${instanceId} in firebase.json`); } - const ref = config.get("extensions", {})[instanceId]; - return refs.parse(ref); + return config.get("extensions", {})[instanceId]; +} + +/** + * Gets the instance's extension ref if exists. + */ +export function getInstanceRef(instanceId: string, config: Config): refs.Ref { + const source = getInstanceTarget(instanceId, config); + if (isLocalPath(source)) { + throw new FirebaseError( + `Extension instance ${instanceId} doesn't have a ref because it is from a local source` + ); + } + return refs.parse(source); } function writeExtensionsToFirebaseJson(specs: ManifestInstanceSpec[], config: Config): void { const extensions = config.get("extensions", {}); for (const s of specs) { - extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref!); + let target; + if (s.ref) { + target = refs.toExtensionVersionRef(s.ref!); + } else if (s.localPath) { + target = s.localPath; + } else { + throw new FirebaseError( + `Unable to resolve ManifestInstanceSpec, make sure you provide either extension ref or a local path to extension source code` + ); + } + + extensions[s.instanceId] = target; } config.set("extensions", extensions); config.writeProjectFile("firebase.json", config.src); diff --git a/src/test/extensions/extensionsHelper.spec.ts b/src/test/extensions/extensionsHelper.spec.ts index fd77dfefee8..528cd3aac4b 100644 --- a/src/test/extensions/extensionsHelper.spec.ts +++ b/src/test/extensions/extensionsHelper.spec.ts @@ -12,6 +12,7 @@ import * as prompt from "../../prompt"; import { ExtensionSource } from "../../extensions/extensionsApi"; import { Readable } from "stream"; import { ArchiveResult } from "../../archiveDirectory"; +import { canonicalizeRefInput } from "../../extensions/extensionsHelper"; describe("extensionsHelper", () => { describe("substituteParams", () => { @@ -774,18 +775,6 @@ describe("extensionsHelper", () => { ); }); - it("should create an ExtensionSource with url sources", async () => { - const url = "https://storage.com/my.zip"; - - const result = await extensionsHelper.createSourceFromLocation("test-proj", url); - - expect(result).to.equal(testSource); - expect(createSourceStub).to.have.been.calledWith("test-proj", url); - expect(archiveStub).not.to.have.been.called; - expect(uploadStub).not.to.have.been.called; - expect(deleteStub).not.to.have.been.called; - }); - it("should throw an error if one is thrown while uploading a local source", async () => { uploadStub.throws(new FirebaseError("something bad happened")); @@ -890,4 +879,28 @@ describe("extensionsHelper", () => { expect(getFirebaseConfigStub).to.have.been.called; }); }); + + describe(`${canonicalizeRefInput.name}`, () => { + it("should do nothing to a valid ref", () => { + expect(canonicalizeRefInput("firebase/bigquery-export@10.1.1")).to.equal( + "firebase/bigquery-export@10.1.1" + ); + }); + + it("should infer latest version", () => { + expect(canonicalizeRefInput("firebase/bigquery-export")).to.equal( + "firebase/bigquery-export@latest" + ); + }); + + it("should infer publisher name as firebase", () => { + expect(canonicalizeRefInput("firebase/bigquery-export")).to.equal( + "firebase/bigquery-export@latest" + ); + }); + + it("should infer publisher name as firebase and also infer latest as version", () => { + expect(canonicalizeRefInput("bigquery-export")).to.equal("firebase/bigquery-export@latest"); + }); + }); }); diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts index 6dcd35c4f4f..8f6e228f40a 100644 --- a/src/test/extensions/manifest.spec.ts +++ b/src/test/extensions/manifest.spec.ts @@ -27,6 +27,18 @@ function generateBaseConfig(): Config { {} ); } +function generateConfigWithLocal(): Config { + return new Config( + { + extensions: { + "delete-user-data": "firebase/delete-user-data@0.1.12", + "delete-user-data-gm2h": "firebase/delete-user-data@0.1.12", + "delete-user-data-local": "./delete-user-data", + }, + }, + {} + ); +} describe("manifest", () => { const sandbox: sinon.SinonSandbox = sinon.createSandbox(); @@ -45,9 +57,32 @@ describe("manifest", () => { }); }); + describe(`${manifest.getInstanceTarget.name}`, () => { + it("should return the correct source for a local instance", () => { + const result = manifest.getInstanceTarget( + "delete-user-data-local", + generateConfigWithLocal() + ); + + expect(result).to.equal("./delete-user-data"); + }); + + it("should return the correct source for an instance with ref", () => { + const result = manifest.getInstanceTarget("delete-user-data", generateConfigWithLocal()); + + expect(result).to.equal("firebase/delete-user-data@0.1.12"); + }); + + it("should throw when looking for a non-existing instance", () => { + expect(() => + manifest.getInstanceTarget("does-not-exist", generateConfigWithLocal()) + ).to.throw(FirebaseError); + }); + }); + describe(`${manifest.getInstanceRef.name}`, () => { it("should return the correct ref for an existing instance", () => { - const result = manifest.getInstanceRef("delete-user-data", generateBaseConfig()); + const result = manifest.getInstanceRef("delete-user-data", generateConfigWithLocal()); expect(refs.toExtensionVersionRef(result)).to.equal( refs.toExtensionVersionRef({ @@ -59,10 +94,16 @@ describe("manifest", () => { }); it("should throw when looking for a non-existing instance", () => { - expect(() => manifest.getInstanceRef("does-not-exist", generateBaseConfig())).to.throw( + expect(() => manifest.getInstanceRef("does-not-exist", generateConfigWithLocal())).to.throw( FirebaseError ); }); + + it("should throw when looking for a instance with local source", () => { + expect(() => + manifest.getInstanceRef("delete-user-data-local", generateConfigWithLocal()) + ).to.throw(FirebaseError); + }); }); describe(`${manifest.removeFromManifest.name}`, () => { From 3d43bae397d86462d2df09d9cfb9967b64d1f3d7 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:00:51 -0400 Subject: [PATCH 0227/1699] Fix cross-platform incompatibility with Storage Emulator exports (#4411) * Fix cross-platform incompatibility with Storage Emulator exports * Add CHANGELOG entries * Check separator after bucket name instead; add comments * Check separator using bucket naming guidelines * Add test case for nested directory --- CHANGELOG.md | 2 ++ .../import/tests.ts | 20 +++++++++++++++++++ .../firebase-export-metadata.json | 7 +++++++ ...%5Ctest-directory%5Ctest_nested_upload.jpg | 0 ...e-project-id.appspot.com%5Ctest_upload.jpg | 0 .../storage_export/buckets.json | 7 +++++++ ...st-directory%5Ctest_nested_upload.jpg.json | 20 +++++++++++++++++++ ...ject-id.appspot.com%5Ctest_upload.jpg.json | 20 +++++++++++++++++++ src/emulator/storage/files.ts | 18 ++++++++++++++++- 9 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 scripts/storage-emulator-integration/import/windows-emulator-data/firebase-export-metadata.json create mode 100644 scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/blobs/fake-project-id.appspot.com%5Ctest-directory%5Ctest_nested_upload.jpg create mode 100644 scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/blobs/fake-project-id.appspot.com%5Ctest_upload.jpg create mode 100644 scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/buckets.json create mode 100644 scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/metadata/fake-project-id.appspot.com%5Ctest-directory%5Ctest_nested_upload.jpg.json create mode 100644 scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/metadata/fake-project-id.appspot.com%5Ctest_upload.jpg.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9482ca432..fc76487621e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ - Fix URL with wrong host returned in storage resumable upload (#4374). - Fixes Firestore emulator transaction expiration and reused bug. - Fixes Firestore emulator deadlock bug. [#2452](https://github.com/firebase/firebase-tools/issues/2452) +- Fixes console error on large uploads to Storage Emulator (#4407). +- Fixes cross-platform incompatibility with Storage Emulator exports (#4411). diff --git a/scripts/storage-emulator-integration/import/tests.ts b/scripts/storage-emulator-integration/import/tests.ts index 47f413b1dab..ea331a02308 100644 --- a/scripts/storage-emulator-integration/import/tests.ts +++ b/scripts/storage-emulator-integration/import/tests.ts @@ -104,6 +104,26 @@ describe("Import Emulator Data", () => { .expect(200); }); + it("retrieves file from importing emulator data previously exported on Windows", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + await test.startEmulators([ + "--only", + Emulators.STORAGE, + "--import", + path.join(__dirname, "windows-emulator-data"), + ]); + + await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${BUCKET}/o/test_upload.jpg`) + .set({ Authorization: "Bearer owner" }) + .expect(200); + + await supertest(STORAGE_EMULATOR_HOST) + .get(`/v0/b/${BUCKET}/o/test-directory%2Ftest_nested_upload.jpg`) + .set({ Authorization: "Bearer owner" }) + .expect(200); + }); + afterEach(async function (this) { this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); await test.stopEmulators(); diff --git a/scripts/storage-emulator-integration/import/windows-emulator-data/firebase-export-metadata.json b/scripts/storage-emulator-integration/import/windows-emulator-data/firebase-export-metadata.json new file mode 100644 index 00000000000..6568db544e7 --- /dev/null +++ b/scripts/storage-emulator-integration/import/windows-emulator-data/firebase-export-metadata.json @@ -0,0 +1,7 @@ +{ + "version": "10.4.2", + "storage": { + "version": "10.4.2", + "path": "storage_export" + } +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/blobs/fake-project-id.appspot.com%5Ctest-directory%5Ctest_nested_upload.jpg b/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/blobs/fake-project-id.appspot.com%5Ctest-directory%5Ctest_nested_upload.jpg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/blobs/fake-project-id.appspot.com%5Ctest_upload.jpg b/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/blobs/fake-project-id.appspot.com%5Ctest_upload.jpg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/buckets.json b/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/buckets.json new file mode 100644 index 00000000000..5cdf3eb336f --- /dev/null +++ b/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/buckets.json @@ -0,0 +1,7 @@ +{ + "buckets": [ + { + "id": "fake-project-id.appspot.com" + } + ] +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/metadata/fake-project-id.appspot.com%5Ctest-directory%5Ctest_nested_upload.jpg.json b/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/metadata/fake-project-id.appspot.com%5Ctest-directory%5Ctest_nested_upload.jpg.json new file mode 100644 index 00000000000..3afcc462964 --- /dev/null +++ b/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/metadata/fake-project-id.appspot.com%5Ctest-directory%5Ctest_nested_upload.jpg.json @@ -0,0 +1,20 @@ +{ + "name": "test-directory\\test_nested_upload.jpg", + "bucket": "fake-project-id.appspot.com", + "contentType": "application/octet-stream", + "metageneration": 1, + "generation": 1648084940926, + "storageClass": "STANDARD", + "contentDisposition": "inline", + "cacheControl": "public, max-age=3600", + "contentEncoding": "identity", + "downloadTokens": [ + "c3c71086-95a8-445d-96e7-f625972de4b0" + ], + "etag": "PQJQBXRweACX9yRsBEInQjOJ/0s", + "timeCreated": "2022-03-24T01:22:20.926Z", + "updated": "2022-03-24T01:22:20.926Z", + "size": 0, + "md5Hash": "1B2M2Y8AsgTpgAmY7PhCfg==", + "crc32c": "0" +} \ No newline at end of file diff --git a/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/metadata/fake-project-id.appspot.com%5Ctest_upload.jpg.json b/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/metadata/fake-project-id.appspot.com%5Ctest_upload.jpg.json new file mode 100644 index 00000000000..bc5caae9e79 --- /dev/null +++ b/scripts/storage-emulator-integration/import/windows-emulator-data/storage_export/metadata/fake-project-id.appspot.com%5Ctest_upload.jpg.json @@ -0,0 +1,20 @@ +{ + "name": "test_upload.jpg", + "bucket": "fake-project-id.appspot.com", + "contentType": "application/octet-stream", + "metageneration": 1, + "generation": 1648084940926, + "storageClass": "STANDARD", + "contentDisposition": "inline", + "cacheControl": "public, max-age=3600", + "contentEncoding": "identity", + "downloadTokens": [ + "c3c71086-95a8-445d-96e7-f625972de4b0" + ], + "etag": "PQJQBXRweACX9yRsBEInQjOJ/0s", + "timeCreated": "2022-03-24T01:22:20.926Z", + "updated": "2022-03-24T01:22:20.926Z", + "size": 0, + "md5Hash": "1B2M2Y8AsgTpgAmY7PhCfg==", + "crc32c": "0" +} \ No newline at end of file diff --git a/src/emulator/storage/files.ts b/src/emulator/storage/files.ts index ec58b50a4b5..f6456bdf384 100644 --- a/src/emulator/storage/files.ts +++ b/src/emulator/storage/files.ts @@ -525,6 +525,8 @@ export class StorageLayer { for (const b of await this.listBuckets()) { bucketsList.buckets.push({ id: b.id }); } + // Resulting path is platform-specific, e.g. foo%5Cbar on Windows, foo%2Fbar on Linux + // after URI encoding. Similarly for metadata paths below. const bucketsFilePath = path.join(storageExportPath, "buckets.json"); await fse.writeFile(bucketsFilePath, JSON.stringify(bucketsList, undefined, 2)); @@ -583,7 +585,13 @@ export class StorageLayer { continue; } - const decodedBlobPath = decodeURIComponent(blobPath); + let decodedBlobPath = decodeURIComponent(blobPath); + const decodedBlobPathSep = getPathSep(decodedBlobPath); + // Replace all file separators with that of current platform for compatibility + if (decodedBlobPathSep !== path.sep) { + decodedBlobPath = decodedBlobPath.split(decodedBlobPathSep).join(path.sep); + } + const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath); const file = new StoredFile(metadata, blobDiskPath); @@ -605,3 +613,11 @@ export class StorageLayer { } } } + +/** Returns file separator used in given path, either '\\' or '/'. */ +function getPathSep(decodedPath: string): string { + // Suffices to check first separator, which occurs immediately after bucket name. + // Bucket naming guidelines: https://cloud.google.com/storage/docs/naming-buckets + const firstSepIndex = decodedPath.search(/[^a-z0-9-_.]/g); + return decodedPath[firstSepIndex]; +} From 418bf367c1b279b2effcb503aab4bdf4b09b3df2 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 8 Apr 2022 12:57:44 -0700 Subject: [PATCH 0228/1699] Correctly detect changelog entries for prerelease versions (#4244) * Fix bug where changelogs would not been detected correctl for prerelease version, and skips changleog check for prerelease versions * typo fix * wording fix --- CHANGELOG.md | 1 + src/extensions/changelog.ts | 4 +++- src/extensions/extensionsHelper.ts | 3 ++- src/test/extensions/changelog.spec.ts | 8 ++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc76487621e..2e5498104fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ - Fix URL with wrong host returned in storage resumable upload (#4374). - Fixes Firestore emulator transaction expiration and reused bug. - Fixes Firestore emulator deadlock bug. [#2452](https://github.com/firebase/firebase-tools/issues/2452) +- Improves support for prerelease versions in `ext:dev:publish` (#4244). - Fixes console error on large uploads to Storage Emulator (#4407). - Fixes cross-platform incompatibility with Storage Emulator exports (#4411). diff --git a/src/extensions/changelog.ts b/src/extensions/changelog.ts index 28cdde73d53..7bfe31c7507 100644 --- a/src/extensions/changelog.ts +++ b/src/extensions/changelog.ts @@ -17,7 +17,9 @@ marked.setOptions({ }); const EXTENSIONS_CHANGELOG = "CHANGELOG.md"; -const VERSION_LINE_REGEX = /##.*(\d+\.\d+\.\d+).*/; +// Simplifed version of https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string +const VERSION_LINE_REGEX = + /##.*(\d+\.\d+\.\d+(?:-((\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?).*/; /* * getReleaseNotesForUpdate fetches all version between toVersion and fromVersion and returns the relase notes diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 61a5332c08c..0d40b03babd 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -446,7 +446,8 @@ export async function publishExtensionVersionFromLocalSource(args: { ) ); } - if (!notes && extension) { + // Skip this check for prerelease versions + if (!notes && !semver.prerelease(extensionSpec.version) && extension) { // If this is not the first version of this extension, we require release notes throw new FirebaseError( `No entry for version ${extensionSpec.version} found in CHANGELOG.md. ` + diff --git a/src/test/extensions/changelog.spec.ts b/src/test/extensions/changelog.spec.ts index a2879547efe..c2d13ea020d 100644 --- a/src/test/extensions/changelog.spec.ts +++ b/src/test/extensions/changelog.spec.ts @@ -141,6 +141,14 @@ describe("changelog", () => { "0.1.1": "New notes", }, }, + { + description: "should handle prerelease versions", + in: "Some random words\n## Version 0.1.0-rc.1\nNotes\n## Version 0.1.1-release-candidate.1.2\nNew notes", + want: { + "0.1.0-rc.1": "Notes", + "0.1.1-release-candidate.1.2": "New notes", + }, + }, ]; for (const testCase of testCases) { it(testCase.description, () => { From de4d7150c2bc95e04dd7cc00b38c3b457f51f496 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Fri, 8 Apr 2022 16:20:05 -0400 Subject: [PATCH 0229/1699] Catch OSX port 5000 in-use (#4415) --- CHANGELOG.md | 1 + src/serve/hosting.ts | 42 ++++++++++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e5498104fa..73c65447263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ - Fix URL with wrong host returned in storage resumable upload (#4374). - Fixes Firestore emulator transaction expiration and reused bug. - Fixes Firestore emulator deadlock bug. [#2452](https://github.com/firebase/firebase-tools/issues/2452) +- Ensure that the hosting emulator port is not claimed by OSX (#4415). - Improves support for prerelease versions in `ext:dev:publish` (#4244). - Fixes console error on large uploads to Storage Emulator (#4407). - Fixes cross-platform incompatibility with Storage Emulator exports (#4411). diff --git a/src/serve/hosting.ts b/src/serve/hosting.ts index 4dc009ea00f..a7942dc10ba 100644 --- a/src/serve/hosting.ts +++ b/src/serve/hosting.ts @@ -15,6 +15,7 @@ import { Writable } from "stream"; import { EmulatorLogger } from "../emulator/emulatorLogger"; import { Emulators } from "../emulator/types"; import { createDestroyer } from "../utils"; +import { execSync } from "child_process"; const MAX_PORT_ATTEMPTS = 10; let attempts = 0; @@ -44,6 +45,34 @@ function startServer(options: any, config: any, port: number, init: TemplateServ stream: morganStream, }); + const portInUse = () => { + const message = "Port " + options.port + " is not available."; + logger.log("WARN", clc.yellow("hosting: ") + message + " Trying another port..."); + if (attempts < MAX_PORT_ATTEMPTS) { + // Another project that's running takes up to 4 ports: 1 hosting port and 3 functions ports + attempts++; + startServer(options, config, port + 5, init); + } else { + logger.log("WARN", message); + throw new FirebaseError("Could not find an open port for hosting development server.", { + exit: 1, + }); + } + }; + + // On OSX, some ports may be reserved by the OS in a way that node http doesn't detect. + // Starting in MacOS 12.3 it does this with port 5000 our default port. This is a bad + // enough devexp that we should special case and ensure it's available. + if (process.platform === "darwin") { + try { + execSync(`lsof -i :${port}`); + portInUse(); + return; + } catch (e) { + // if lsof errored the port is NOT in use, continue + } + } + const server = superstatic({ debug: false, port: port, @@ -85,18 +114,7 @@ function startServer(options: any, config: any, port: number, init: TemplateServ // eslint-disable-next-line @typescript-eslint/no-explicit-any server.on("error", (err: any) => { if (err.code === "EADDRINUSE") { - const message = "Port " + options.port + " is not available."; - logger.log("WARN", clc.yellow("hosting: ") + message + " Trying another port..."); - if (attempts < MAX_PORT_ATTEMPTS) { - // Another project that's running takes up to 4 ports: 1 hosting port and 3 functions ports - attempts++; - startServer(options, config, port + 5, init); - } else { - logger.log("WARN", message); - throw new FirebaseError("Could not find an open port for hosting development server.", { - exit: 1, - }); - } + portInUse(); } else { throw new FirebaseError( "An error occurred while starting the hosting development server:\n\n" + err.toString(), From 2abaa0226a262ed5096dd6b06693f51fe732c645 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 8 Apr 2022 17:18:11 -0700 Subject: [PATCH 0230/1699] Handle CSM response without secrets (#4425) --- CHANGELOG.md | 1 + src/gcp/secretManager.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73c65447263..e72d1b95f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,3 +5,4 @@ - Improves support for prerelease versions in `ext:dev:publish` (#4244). - Fixes console error on large uploads to Storage Emulator (#4407). - Fixes cross-platform incompatibility with Storage Emulator exports (#4411). +- Fixes issue where function deployment errored on projects without secrets (#4425). diff --git a/src/gcp/secretManager.ts b/src/gcp/secretManager.ts index 121cdf398b5..4019565740d 100644 --- a/src/gcp/secretManager.ts +++ b/src/gcp/secretManager.ts @@ -91,7 +91,7 @@ export async function listSecrets(projectId: string, filter?: string): Promise(path, opts); - for (const s of res.body.secrets) { + for (const s of res.body.secrets || []) { secrets.push({ ...parseSecretResourceName(s.name), labels: s.labels ?? {}, From 9876c0f60d57ea379b6972a31d35167401ea7d54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 20:29:58 +0000 Subject: [PATCH 0231/1699] Bump ansi-regex from 4.1.0 to 4.1.1 in /scripts/firepit-builder (#4390) Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/chalk/ansi-regex/releases) - [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: ansi-regex dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: joehan --- scripts/firepit-builder/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/firepit-builder/package-lock.json b/scripts/firepit-builder/package-lock.json index 4d6984bc9b2..a58efd9942f 100644 --- a/scripts/firepit-builder/package-lock.json +++ b/scripts/firepit-builder/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "ansi-styles": { "version": "3.2.1", From 424f97fce2513499109fd277af0b6a5d575b815c Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 12 Apr 2022 01:19:00 -0700 Subject: [PATCH 0232/1699] Minor refactoring of function deploy (#4406) Just moving around code in `prepare.ts` phase of the function deploy to make it easy to support multi-codebase deploys. This is a simple refactor that should introduces no change to deploy behavior. I've added annotation to each section to make it slightly easier to understand what'a going on in each section. There are few light changes: * We reduce scope of `backend.checkAvailability` check to just the endpoints that's being deployed (to allow deploy to proceed even if region of endpoint NOT being deployed is down). * We reduce scope `ensureServiceAgentRoles` check to just the endpoints that's being deployed (to reduce IAM calls to bare minimum) --- src/deploy/functions/args.ts | 28 ++-- src/deploy/functions/deploy.ts | 37 ++--- src/deploy/functions/prepare.ts | 140 ++++++++++-------- .../functions/prepareFunctionsUpload.ts | 42 +++--- src/deploy/functions/release/index.ts | 6 +- 5 files changed, 130 insertions(+), 123 deletions(-) diff --git a/src/deploy/functions/args.ts b/src/deploy/functions/args.ts index 5391d050167..4f9e1442d69 100644 --- a/src/deploy/functions/args.ts +++ b/src/deploy/functions/args.ts @@ -5,12 +5,24 @@ import * as deployHelper from "./functionsDeployHelper"; // These types should probably be in a root deploy.ts, but we can only boil the ocean one bit at a time. +interface CodebasePayload { + wantBackend: backend.Backend; + haveBackend: backend.Backend; +} + // Payload holds the output of what we want to build + what we already have. export interface Payload { - functions?: { - wantBackend: backend.Backend; - haveBackend: backend.Backend; - }; + functions?: CodebasePayload; +} + +export interface Source { + // Filled in the "prepare" phase. + functionsSourceV1?: string; + functionsSourceV2?: string; + + // Filled in the "deploy" phase. + sourceUrl?: string; + storage?: Record; } // Context holds cached values of what we've looked up in handling this request. @@ -22,15 +34,11 @@ export interface Context { // Filled in the "prepare" phase. config?: projectConfig.ValidatedSingle; - functionsSourceV1?: string; - functionsSourceV2?: string; - runtimeConfigEnabled?: boolean; artifactRegistryEnabled?: boolean; firebaseConfig?: FirebaseConfig; - // Filled in the "deploy" phase. - sourceUrl?: string; - storage?: Record; + // Filled in the "prepare" and "deploy" phase. + source?: Source; } export interface FirebaseConfig { diff --git a/src/deploy/functions/deploy.ts b/src/deploy/functions/deploy.ts index 10c43ad18b3..e4d36b87054 100644 --- a/src/deploy/functions/deploy.ts +++ b/src/deploy/functions/deploy.ts @@ -3,23 +3,23 @@ import * as clc from "cli-color"; import * as fs from "fs"; import { checkHttpIam } from "./checkIam"; -import { logSuccess, logWarning } from "../../utils"; +import { logSuccess, logWarning, groupBy, endpoint } from "../../utils"; import { Options } from "../../options"; import * as args from "./args"; import * as gcs from "../../gcp/storage"; import * as gcf from "../../gcp/cloudfunctions"; import * as gcfv2 from "../../gcp/cloudfunctionsv2"; -import * as utils from "../../utils"; import * as backend from "./backend"; +import { FirebaseError } from "../../error"; setGracefulCleanup(); async function uploadSourceV1(context: args.Context, region: string): Promise { const uploadUrl = await gcf.generateUploadUrl(context.projectId, region); - context.sourceUrl = uploadUrl; + context.source!.sourceUrl = uploadUrl; const uploadOpts = { - file: context.functionsSourceV1!, - stream: fs.createReadStream(context.functionsSourceV1!), + file: context.source!.functionsSourceV1!, + stream: fs.createReadStream(context.source!.functionsSourceV1!), }; await gcs.upload(uploadOpts, uploadUrl, { "x-goog-content-length-range": "0,104857600", @@ -29,11 +29,11 @@ async function uploadSourceV1(context: args.Context, region: string): Promise { const res = await gcfv2.generateUploadUrl(context.projectId, region); const uploadOpts = { - file: context.functionsSourceV2!, - stream: fs.createReadStream(context.functionsSourceV2!), + file: context.source!.functionsSourceV2!, + stream: fs.createReadStream(context.source!.functionsSourceV2!), }; await gcs.upload(uploadOpts, res.uploadUrl); - context.storage = { ...context.storage, [region]: res.storageSource }; + context.source!.storage = { ...context.source!.storage, [region]: res.storageSource }; } /** @@ -51,7 +51,7 @@ export async function deploy( return; } - if (!context.functionsSourceV1 && !context.functionsSourceV2) { + if (!context.source?.functionsSourceV1 && !context.source?.functionsSourceV2) { return; } @@ -61,19 +61,12 @@ export async function deploy( const want = payload.functions!.wantBackend; const uploads: Promise[] = []; - const v1Endpoints = backend.allEndpoints(want).filter((e) => e.platform === "gcfv1"); - if (v1Endpoints.length > 0) { - // Choose one of the function region for source upload. - uploads.push(uploadSourceV1(context, v1Endpoints[0].region)); - } - - for (const region of Object.keys(want.endpoints)) { - // GCFv2 cares about data residency and will possibly block deploys coming from other - // regions. At minimum, the implementation would consider it user-owned source and - // would break download URLs + console source viewing. - if (backend.regionalEndpoints(want, region).some((e) => e.platform === "gcfv2")) { - uploads.push(uploadSourceV2(context, region)); - } + // Choose one of the function region for source upload. + const byPlatform = groupBy(backend.allEndpoints(want), (e) => e.platform); + if (byPlatform.gcfv1.length > 0) { + uploads.push(uploadSourceV1(context, byPlatform.gcfv1[0].region)); + } else if (byPlatform.gcfv2.length > 0) { + uploads.push(uploadSourceV2(context, byPlatform.gcfv2[0].region)); } await Promise.all(uploads); diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index ba68adb95a6..cf05777b843 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -10,7 +10,7 @@ import * as validate from "./validate"; import * as ensure from "./ensure"; import { Options } from "../../options"; import { endpointMatchesAnyFilter, getEndpointFilters } from "./functionsDeployHelper"; -import { logLabeledBullet, logLabeledError } from "../../utils"; +import { logLabeledBullet } from "../../utils"; import { getFunctionsConfig, prepareFunctionsUpload } from "./prepareFunctionsUpload"; import { promptForFailurePolicies, promptForMinInstances } from "./prompts"; import { needProjectId, needProjectNumber } from "../../projectUtils"; @@ -32,6 +32,9 @@ function hasDotenv(opts: functionsEnv.UserEnvsOpts): boolean { return functionsEnv.hasUserEnvs(opts); } +/** + * + */ export async function prepare( context: args.Context, options: Options, @@ -50,11 +53,34 @@ export async function prepare( throw new FirebaseError("No function matches given --only filters. Aborting deployment."); } + // ===Phase 0. Check that minimum APIs required for function deploys are enabled. + const checkAPIsEnabled = await Promise.all([ + ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"), + ensureApiEnabled.check( + projectId, + "runtimeconfig.googleapis.com", + "runtimeconfig", + /* silent=*/ true + ), + ensure.cloudBuildEnabled(projectId), + ensure.maybeEnableAR(projectId), + ]); + context.artifactRegistryEnabled = checkAPIsEnabled[3]; + + // Get the Firebase Config, and set it on each function in the deployment. + const firebaseConfig = await functionsConfig.getFirebaseConfig(options); + context.firebaseConfig = firebaseConfig; + let runtimeConfig: Record = { firebase: firebaseConfig }; + if (checkAPIsEnabled[1]) { + // If runtime config API is enabled, load the runtime config. + runtimeConfig = { ...runtimeConfig, ...(await getFunctionsConfig(projectId)) }; + } + + // ===Phase 1. Load codebase from source. logLabeledBullet( "functions", `preparing codebase ${clc.bold(context.config.codebase)} for deployment` ); - const sourceDirName = context.config.source; if (!sourceDirName) { throw new FirebaseError( @@ -62,7 +88,6 @@ export async function prepare( ); } const sourceDir = options.config.path(sourceDirName); - const delegateContext: runtimes.DelegateContext = { projectId, sourceDir, @@ -75,26 +100,6 @@ export async function prepare( logger.debug(`Building ${runtimeDelegate.name} source`); await runtimeDelegate.build(); - // Check that all necessary APIs are enabled. - const checkAPIsEnabled = await Promise.all([ - ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"), - ensureApiEnabled.check( - projectId, - "runtimeconfig.googleapis.com", - "runtimeconfig", - /* silent=*/ true - ), - ensure.cloudBuildEnabled(projectId), - ensure.maybeEnableAR(projectId), - ]); - context.runtimeConfigEnabled = checkAPIsEnabled[1]; - context.artifactRegistryEnabled = checkAPIsEnabled[3]; - - // Get the Firebase Config, and set it on each function in the deployment. - const firebaseConfig = await functionsConfig.getFirebaseConfig(options); - context.firebaseConfig = firebaseConfig; - const runtimeConfig = await getFunctionsConfig(context); - const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); const userEnvOpt: functionsEnv.UserEnvsOpts = { functionsSource: sourceDir, @@ -120,23 +125,8 @@ export async function prepare( endpoint.codebase = context.config.codebase; } - // Note: Some of these are premium APIs that require billing to be enabled. - // We'd eventually have to add special error handling for billing APIs, but - // enableCloudBuild is called above and has this special casing already. - if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { - const V2_APIS = [ - "artifactregistry.googleapis.com", - "run.googleapis.com", - "eventarc.googleapis.com", - "pubsub.googleapis.com", - "storage.googleapis.com", - ]; - const enablements = V2_APIS.map((api) => { - return ensureApiEnabled.ensure(context.projectId, api, "functions"); - }); - await Promise.all(enablements); - } - + // ===Phase 2. Prepare source for upload. + const source: args.Source = {}; if (backend.someEndpoint(wantBackend, () => true)) { logLabeledBullet( "functions", @@ -155,37 +145,25 @@ export async function prepare( "version of Firebse Tools with `npm i -g firebase-tools@latest`" ); } - context.functionsSourceV2 = await prepareFunctionsUpload(sourceDir, context.config); + source.functionsSourceV2 = await prepareFunctionsUpload(sourceDir, context.config); } if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) { - context.functionsSourceV1 = await prepareFunctionsUpload( + source.functionsSourceV1 = await prepareFunctionsUpload( sourceDir, context.config, runtimeConfig ); } + context.source = source; - // Enable required APIs. This may come implicitly from triggers (e.g. scheduled triggers - // require cloudscheudler and, in v1, require pub/sub), or can eventually come from - // explicit dependencies. - await Promise.all( - Object.values(wantBackend.requiredAPIs).map(({ api }) => { - return ensureApiEnabled.ensure(projectId, api, "functions", /* silent=*/ false); - }) - ); - - // Validate the function code that is being deployed. - validate.endpointsAreValid(wantBackend); + // ===Phase 3. Fill in details and validate endpoints. We run the check for ALL endpoints - we think it's useful for + // validations to fail even for endpoints that aren't being deployed so any errors are caught early. - const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => { - return endpointMatchesAnyFilter(endpoint, context.filters); - }); - - // Load all endpoints for the project, then filter out functions from other codebases. + // Load all existing endpoints for the project, then filter out functions from other codebases. // // An endpoint is part a codebase if: // 1. Endpoint is associated w/ the current codebase (duh). - // 2. Endpoint name matches name of an endoint we want to deploy + // 2. Endpoint name matches name of an endpoint we want to deploy // // Condition (2) might feel wrong but is a practical conflict resolution strategy. It allows user to "claim" an // endpoint for current codebase without much hassel. @@ -199,17 +177,51 @@ export async function prepare( return wantEndpointNames.includes(backend.functionName(endpoint)); } ); + inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv); + await ensureTriggerRegions(wantBackend); + validate.endpointsAreValid(wantBackend); payload.functions = { wantBackend: wantBackend, haveBackend: haveBackend }; - await ensureServiceAgentRoles(projectNumber, wantBackend, haveBackend); - inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv); - await ensureTriggerRegions(wantBackend); + // ===Phase 4. Enable APIs required by the deploying backend. + + // Enable required APIs. This may come implicitly from triggers (e.g. scheduled triggers + // require cloudscheudler and, in v1, require pub/sub), or can eventually come from + // explicit dependencies. + await Promise.all( + Object.values(wantBackend.requiredAPIs).map(({ api }) => { + return ensureApiEnabled.ensure(projectId, api, "functions", /* silent=*/ false); + }) + ); + // Note: Some of these are premium APIs that require billing to be enabled. + // We'd eventually have to add special error handling for billing APIs, but + // enableCloudBuild is called above and has this special casing already. + if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { + const V2_APIS = [ + "artifactregistry.googleapis.com", + "run.googleapis.com", + "eventarc.googleapis.com", + "pubsub.googleapis.com", + "storage.googleapis.com", + ]; + const enablements = V2_APIS.map((api) => { + return ensureApiEnabled.ensure(context.projectId, api, "functions"); + }); + await Promise.all(enablements); + } - // Display a warning and prompt if any functions in the release have failurePolicies. + // ===Phase 5. Ask for user prompts for things might warrant user attentions. + // We limit the scope endpoints being deployed. + const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => { + return endpointMatchesAnyFilter(endpoint, context.filters); + }); await promptForFailurePolicies(options, matchingBackend, haveBackend); await promptForMinInstances(options, matchingBackend, haveBackend); - await backend.checkAvailability(context, wantBackend); + + // ===Phase 6. Finalize preparation by "fixing" all extraneous environment issues like IAM policies. + // We limit the scope endpoints being deployed. + await backend.checkAvailability(context, matchingBackend); + await ensureServiceAgentRoles(projectNumber, matchingBackend, haveBackend); await validate.secretsAreValid(projectId, matchingBackend); await ensure.secretAccess(projectId, matchingBackend, haveBackend); } diff --git a/src/deploy/functions/prepareFunctionsUpload.ts b/src/deploy/functions/prepareFunctionsUpload.ts index d44c4445ac6..85b762581f3 100644 --- a/src/deploy/functions/prepareFunctionsUpload.ts +++ b/src/deploy/functions/prepareFunctionsUpload.ts @@ -18,32 +18,26 @@ import * as projectConfig from "../../functions/projectConfig"; const CONFIG_DEST_FILE = ".runtimeconfig.json"; // TODO(inlined): move to a file that's not about uploading source code -export async function getFunctionsConfig(context: args.Context): Promise<{ [key: string]: any }> { - let config: Record = {}; - if (context.runtimeConfigEnabled) { - try { - config = await functionsConfig.materializeAll(context.firebaseConfig!.projectId); - } catch (err: any) { - logger.debug(err); - let errorCode = err?.context?.response?.statusCode; - if (!errorCode) { - logger.debug("Got unexpected error from Runtime Config; it has no status code:", err); - errorCode = 500; - } - if (errorCode === 500 || errorCode === 503) { - throw new FirebaseError( - "Cloud Runtime Config is currently experiencing issues, " + - "which is preventing your functions from being deployed. " + - "Please wait a few minutes and then try to deploy your functions again." + - "\nRun `firebase deploy --except functions` if you want to continue deploying the rest of your project." - ); - } - config = {}; +export async function getFunctionsConfig(projectId: string): Promise> { + try { + return await functionsConfig.materializeAll(projectId); + } catch (err: any) { + logger.debug(err); + let errorCode = err?.context?.response?.statusCode; + if (!errorCode) { + logger.debug("Got unexpected error from Runtime Config; it has no status code:", err); + errorCode = 500; + } + if (errorCode === 500 || errorCode === 503) { + throw new FirebaseError( + "Cloud Runtime Config is currently experiencing issues, " + + "which is preventing your functions from being deployed. " + + "Please wait a few minutes and then try to deploy your functions again." + + "\nRun `firebase deploy --except functions` if you want to continue deploying the rest of your project." + ); } } - - config.firebase = context.firebaseConfig; - return config; + return {}; } async function pipeAsync(from: archiver.Archiver, to: fs.WriteStream) { diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index efab023be3b..ff470b66cd7 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -31,7 +31,7 @@ export async function release( return; } - const { wantBackend, haveBackend } = payload.functions; + const { wantBackend, haveBackend } = payload.functions!; const plan = planner.createDeploymentPlan(wantBackend, haveBackend, context.filters); const fnsToDelete = Object.values(plan) @@ -58,8 +58,8 @@ export async function release( const fab = new fabricator.Fabricator({ functionExecutor, executor: new executor.QueueExecutor({}), - sourceUrl: context.sourceUrl!, - storage: context.storage!, + sourceUrl: context.source!.sourceUrl!, + storage: context.source!.storage!, appEngineLocation: getAppEngineLocation(context.firebaseConfig), }); From b2327da886ded6c71b991aee461c9fbd06003538 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 12 Apr 2022 09:33:48 -0700 Subject: [PATCH 0233/1699] Adding support for emulating local extensions (#4430) * Adding support for emulating local extensions via emulators:start * formats * Fixing tests --- src/deploy/extensions/planner.ts | 43 +++++++---- src/emulator/extensions/validation.ts | 4 +- src/emulator/extensionsEmulator.ts | 73 +++++++++++-------- src/extensions/emulator/specHelper.ts | 12 ++- src/extensions/refs.ts | 2 +- .../emulators/extensions/validation.spec.ts | 5 ++ 6 files changed, 87 insertions(+), 52 deletions(-) diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 16dafef587e..ee0e9facbcc 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -11,7 +11,7 @@ import { import { logger } from "../../logger"; import { readInstanceParam } from "../../extensions/manifest"; import { ParamBindingOptions } from "../../extensions/paramHelper"; -import { readExtensionYaml } from "../../extensions/emulator/specHelper"; +import { readExtensionYaml, readPostinstall } from "../../extensions/emulator/specHelper"; export interface InstanceSpec { instanceId: string; @@ -69,7 +69,7 @@ export async function getExtensionVersion( */ export async function getExtension(i: InstanceSpec): Promise { if (!i.ref) { - throw new FirebaseError(`Can't get Extensionfor ${i.instanceId} because it has no ref`); + throw new FirebaseError(`Can't get Extension for ${i.instanceId} because it has no ref`); } if (!i.extension) { i.extension = await extensionsApi.getExtension(refs.toExtensionRef(i.ref)); @@ -86,6 +86,7 @@ export async function getExtensionSpec(i: InstanceSpec): Promise { const unemulatedAPIs: Record = {}; for (const i of instances) { - const extensionVersion = await planner.getExtensionVersion(i); - for (const api of extensionVersion.spec.apis ?? []) { + const extensionSpec = await planner.getExtensionSpec(i); + for (const api of extensionSpec.apis ?? []) { if (!EMULATED_APIS.includes(api.apiName)) { if (unemulatedAPIs[api.apiName]) { unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId); diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 7eca726f352..afeb080c888 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -93,33 +93,39 @@ export class ExtensionsEmulator implements EmulatorInstance { // downloads and builds it if it is not found, then returns the path to that source code. private async ensureSourceCode(instance: planner.InstanceSpec): Promise { // TODO(b/213335255): Handle local extensions. - if (!instance.ref) { - throw new FirebaseError( - `No ref found for ${instance.instanceId}. Emulating local extensions is not yet supported.` - ); - } - // TODO: If ref contains 'latest', we need to resolve that to a real version. - - const ref = toExtensionVersionRef(instance.ref); - const cacheDir = - process.env.FIREBASE_EXTENSIONS_CACHE_PATH || - path.join(os.homedir(), ".cache", "firebase", "extensions"); - const sourceCodePath = path.join(cacheDir, ref); + if (instance.localPath) { + if (!this.hasValidSource({ path: instance.localPath, extTarget: instance.localPath })) { + throw new FirebaseError( + `Tried to emulate local extension at ${instance.localPath}, but it was missing required files.` + ); + } + return instance.localPath; + } else if (instance.ref) { + const ref = toExtensionVersionRef(instance.ref); + const cacheDir = + process.env.FIREBASE_EXTENSIONS_CACHE_PATH || + path.join(os.homedir(), ".cache", "firebase", "extensions"); + const sourceCodePath = path.join(cacheDir, ref); - // Wait for previous download promise to resolve before we check source validity. - // This avoids racing to download the same source multiple times. - // Note: The below will not work because it throws the thread to the back of the message queue. - // await (this.pendingDownloads.get(ref) ?? Promise.resolve()); - if (this.pendingDownloads.get(ref)) { - await this.pendingDownloads.get(ref); - } + // Wait for previous download promise to resolve before we check source validity. + // This avoids racing to download the same source multiple times. + // Note: The below will not work because it throws the thread to the back of the message queue. + // await (this.pendingDownloads.get(ref) ?? Promise.resolve()); + if (this.pendingDownloads.get(ref)) { + await this.pendingDownloads.get(ref); + } - if (!this.hasValidSource({ path: sourceCodePath, extRef: ref })) { - const promise = this.downloadSource(instance, ref, sourceCodePath); - this.pendingDownloads.set(ref, promise); - await promise; + if (!this.hasValidSource({ path: sourceCodePath, extTarget: ref })) { + const promise = this.downloadSource(instance, ref, sourceCodePath); + this.pendingDownloads.set(ref, promise); + await promise; + } + return sourceCodePath; + } else { + throw new FirebaseError( + "Tried to emulate an extension instance without a ref or localPath. This should never happen." + ); } - return sourceCodePath; } private async downloadSource( @@ -137,7 +143,7 @@ export class ExtensionsEmulator implements EmulatorInstance { * * Checks against a list of required files or directories that need to be present. */ - private hasValidSource(args: { path: string; extRef: string }): boolean { + private hasValidSource(args: { path: string; extTarget: string }): boolean { // TODO(lihes): Source code can technically exist in other than "functions" dir. // https://source.corp.google.com/piper///depot/google3/firebase/mods/go/worker/fetch_mod_source.go;l=451 const requiredFiles = [ @@ -152,10 +158,10 @@ export class ExtensionsEmulator implements EmulatorInstance { for (const requiredFile of requiredFiles) { const f = path.join(args.path, requiredFile); if (!fs.existsSync(f)) { - EmulatorLogger.forExtension({ ref: args.extRef }).logLabeled( + EmulatorLogger.forExtension({ ref: args.extTarget }).logLabeled( "BULLET", "extensions", - `Detected invalid source code for ${args.extRef}, expected to find ${f}` + `Detected invalid source code for ${args.extTarget}, expected to find ${f}` ); return false; } @@ -215,18 +221,21 @@ export class ExtensionsEmulator implements EmulatorInstance { const env = Object.assign(this.autoPopulatedParams(instance), instance.params); const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await getExtensionFunctionInfo(instance, env); - const extension = await planner.getExtension(instance); - const extensionVersion = await planner.getExtensionVersion(instance); - return { + const emulatableBackend: EmulatableBackend = { functionsDir, env: nonSecretEnv, secretEnv: secretEnvVariables, predefinedTriggers: extensionTriggers, nodeMajorVersion: nodeMajorVersion, extensionInstanceId: instance.instanceId, - extension, - extensionVersion, }; + if (instance.ref) { + emulatableBackend.extension = await planner.getExtension(instance); + emulatableBackend.extensionVersion = await planner.getExtensionVersion(instance); + } else if (instance.localPath) { + emulatableBackend.extensionSpec = await planner.getExtensionSpec(instance); + } + return emulatableBackend; } private autoPopulatedParams(instance: planner.InstanceSpec): Record { diff --git a/src/extensions/emulator/specHelper.ts b/src/extensions/emulator/specHelper.ts index 6662676edae..b3525194ced 100644 --- a/src/extensions/emulator/specHelper.ts +++ b/src/extensions/emulator/specHelper.ts @@ -9,6 +9,7 @@ import { substituteParams } from "../extensionsHelper"; import { parseRuntimeVersion } from "../../emulator/functionsEmulatorUtils"; const SPEC_FILE = "extension.yaml"; +const POSTINSTALL_FILE = "POSTINSTALL.md"; const validFunctionTypes = [ "firebaseextensions.v1beta.function", "firebaseextensions.v1beta.scheduledFunction", @@ -39,13 +40,22 @@ export async function readExtensionYaml(directory: string): Promise { + const content = await readFileFromDirectory(directory, POSTINSTALL_FILE); + return content.source; +} + /** * Retrieves a file from the directory. */ export function readFileFromDirectory( directory: string, file: string -): Promise<{ [key: string]: any }> { +): Promise<{ source: string; sourceDirectory: string }> { return new Promise((resolve, reject) => { fs.readFile(path.resolve(directory, file), "utf8", (err, data) => { if (err) { diff --git a/src/extensions/refs.ts b/src/extensions/refs.ts index 40adf890353..10ea0aad26c 100644 --- a/src/extensions/refs.ts +++ b/src/extensions/refs.ts @@ -65,7 +65,7 @@ export function toExtensionRef(ref: Ref): string { } /** - * To an extension bersion ref: publisherId/extensionId@version + * To an extension version ref: publisherId/extensionId@version */ export function toExtensionVersionRef(ref: Ref): string { if (!ref.version) { diff --git a/src/test/emulators/extensions/validation.spec.ts b/src/test/emulators/extensions/validation.spec.ts index aef1f6153b5..caae0d80e05 100644 --- a/src/test/emulators/extensions/validation.spec.ts +++ b/src/test/emulators/extensions/validation.spec.ts @@ -30,6 +30,11 @@ function fakeInstanceSpecWithAPI(instanceId: string, apiName: string): Deploymen return { instanceId, params: {}, + ref: { + publisherId: "test", + extensionId: "test", + version: "0.1.0", + }, extensionVersion: { name: "publishers/test/extensions/test/versions/0.1.0", ref: "test/test@0.1.0", From 908caac353a4ef0dd6848f3fad5d7c9b09f7c0cd Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Tue, 12 Apr 2022 11:06:11 -0700 Subject: [PATCH 0234/1699] Add getConfig (#4424) Adds projects.getConfig, which corresponds to https://cloud.google.com/identity-platform/docs/reference/rest/v2/projects/getConfig. Note that the auth emulator only stores a subset of the Config fields (i.e. the configurable ones). --- src/emulator/auth/operations.ts | 14 ++++++++ src/test/emulators/auth/config.spec.ts | 45 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index ba38d26f3d1..e56de93f214 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -74,6 +74,7 @@ export const authOperations: AuthOps = { projects: { createSessionCookie, queryAccounts, + getConfig, updateConfig, accounts: { _: signUp, @@ -2000,6 +2001,19 @@ function mfaSignInFinalize( }; } +function getConfig( + state: ProjectState, + reqBody: unknown, + ctx: ExegesisContext +): Schemas["GoogleCloudIdentitytoolkitAdminV2Config"] { + // Shouldn't error on this but need assertion for type checking + assert( + state instanceof AgentProjectState, + "((Can only get top-level configurations on agent projects.))" + ); + return state.config; +} + function updateConfig( state: ProjectState, reqBody: Schemas["GoogleCloudIdentitytoolkitAdminV2Config"], diff --git a/src/test/emulators/auth/config.spec.ts b/src/test/emulators/auth/config.spec.ts index 490fd74e6eb..b5c9d5a11c0 100644 --- a/src/test/emulators/auth/config.spec.ts +++ b/src/test/emulators/auth/config.spec.ts @@ -156,4 +156,49 @@ describeAuthEmulator("config management", ({ authApi }) => { }); }); }); + + describe("getConfig", () => { + it("should return the project level config", async () => { + await authApi() + .get(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send() + .then((res) => { + expectStatusCode(200, res); + expect(res.body).to.have.property("signIn").eql({ + allowDuplicateEmails: false /* default value */, + }); + expect(res.body).to.have.property("usageMode").eql("DEFAULT"); + expect(res.body).to.have.property("blockingFunctions").eql({}); + }); + }); + + it("should return updated config fields", async () => { + await authApi() + .patch(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send({ + signIn: { allowDuplicateEmails: true }, + blockingFunctions: { forwardInboundCredentials: { idToken: true } }, + usageMode: "PASSTHROUGH", + }); + + await authApi() + .get(`/identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/config`) + .set("Authorization", "Bearer owner") + .send() + .then((res) => { + expectStatusCode(200, res); + expect(res.body).to.have.property("signIn").eql({ + allowDuplicateEmails: true, + }); + expect(res.body).to.have.property("usageMode").eql("PASSTHROUGH"); + expect(res.body) + .to.have.property("blockingFunctions") + .eql({ + forwardInboundCredentials: { idToken: true }, + }); + }); + }); + }); }); From 0ea962993d6359b295ac88c92326ca30d4395371 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Tue, 12 Apr 2022 12:24:44 -0700 Subject: [PATCH 0235/1699] Add blocking trigger type (#4395) * tinkering with implementations * adding in cache for discovery * adding in skeleton for service object and identity platform api calls * adding in all the service code * formatter * rename * removing the lookup before endpointToFunction & adding v2 stuff * add tests & changelog * adding tsdoc comments * addressing pr comments * removing the empty object from gcp/identityPlatform * added typing & fixing endpointFromFunction to set securityLevel and uri for every httpsTrigger * removing changelog entries * cleaning up & set default endpoint options to false * adding in inferBlockingDetails * checking if config changed before calling set blocking config * formatter * cleaning up * adding event type if in fab & changing api to match prod * adding serviceForEndpoint in fab setTrigger * converting to options record & yanking auth blocking events from events/v2 * removing old comments * fixing delete blocking function bug * adding a promise queue for trigger registration & deletion * making auth blocking service a class & moving trigger queue into it * remove comment --- CHANGELOG.md | 1 + src/deploy/functions/backend.ts | 20 +- src/deploy/functions/prepare.ts | 34 ++ src/deploy/functions/release/fabricator.ts | 51 ++ src/deploy/functions/release/planner.ts | 2 + src/deploy/functions/release/reporter.ts | 8 +- .../functions/runtimes/discovery/v1alpha1.ts | 12 + .../functions/runtimes/node/parseTriggers.ts | 28 +- src/deploy/functions/services/auth.ts | 150 ++++++ src/deploy/functions/services/index.ts | 70 ++- src/deploy/functions/validate.ts | 13 +- src/functions/events/index.ts | 6 + src/functions/events/v1.ts | 7 + src/gcp/cloudfunctions.ts | 36 +- src/gcp/cloudfunctionsv2.ts | 27 + src/gcp/identityPlatform.ts | 221 ++++++++ src/test/deploy/functions/prepare.spec.ts | 37 ++ .../functions/release/fabricator.spec.ts | 133 +++++ .../deploy/functions/release/reporter.spec.ts | 20 + .../runtimes/discovery/v1alpha1.spec.ts | 77 +++ .../runtimes/node/parseTriggers.spec.ts | 68 +++ .../deploy/functions/services/auth.spec.ts | 495 ++++++++++++++++++ src/test/deploy/functions/validate.spec.ts | 96 ++++ src/test/gcp/cloudfunctions.spec.ts | 87 +++ src/test/gcp/cloudfunctionsv2.spec.ts | 82 ++- 25 files changed, 1745 insertions(+), 36 deletions(-) create mode 100644 src/deploy/functions/services/auth.ts create mode 100644 src/functions/events/index.ts create mode 100644 src/functions/events/v1.ts create mode 100644 src/gcp/identityPlatform.ts create mode 100644 src/test/deploy/functions/services/auth.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e72d1b95f99..b47882cf1f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ - Fixes console error on large uploads to Storage Emulator (#4407). - Fixes cross-platform incompatibility with Storage Emulator exports (#4411). - Fixes issue where function deployment errored on projects without secrets (#4425). +- Adds a blocking trigger type (#4395). diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 497fec8cd44..91f55cbc00a 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -3,6 +3,7 @@ import * as gcf from "../../gcp/cloudfunctions"; import * as gcfV2 from "../../gcp/cloudfunctionsv2"; import * as utils from "../../utils"; import * as runtimes from "./runtimes"; +import * as events from "../../functions/events"; import { FirebaseError } from "../../error"; import { Context } from "./args"; import { previews } from "../../previews"; @@ -131,6 +132,15 @@ export interface TaskQueueTriggered { taskQueueTrigger: TaskQueueTrigger; } +export interface BlockingTrigger { + eventType: string; + options?: Record; +} + +export interface BlockingTriggered { + blockingTrigger: BlockingTrigger; +} + /** A user-friendly string for the kind of trigger of an endpoint. */ export function endpointTriggerType(endpoint: Endpoint): string { if (isScheduleTriggered(endpoint)) { @@ -143,6 +153,8 @@ export function endpointTriggerType(endpoint: Endpoint): string { return endpoint.eventTrigger.eventType; } else if (isTaskQueueTriggered(endpoint)) { return "taskQueue"; + } else if (isBlockingTriggered(endpoint)) { + return endpoint.blockingTrigger.eventType; } else { throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint)); } @@ -227,7 +239,8 @@ export type Triggered = | CallableTriggered | EventTriggered | ScheduleTriggered - | TaskQueueTriggered; + | TaskQueueTriggered + | BlockingTriggered; /** Whether something has an HttpsTrigger */ export function isHttpsTriggered(triggered: Triggered): triggered is HttpsTriggered { @@ -254,6 +267,11 @@ export function isTaskQueueTriggered(triggered: Triggered): triggered is TaskQue return {}.hasOwnProperty.call(triggered, "taskQueueTrigger"); } +/** Whether something has a BlockingTrigger */ +export function isBlockingTriggered(triggered: Triggered): triggered is BlockingTriggered { + return {}.hasOwnProperty.call(triggered, "blockingTrigger"); +} + /** * An endpoint that serves traffic to a stack of services. * For now, this is always a Cloud Function. Future iterations may use complex diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index cf05777b843..46525ff8bd2 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -21,6 +21,7 @@ import { ensureServiceAgentRoles } from "./checkIam"; import { FirebaseError } from "../../error"; import { normalizeAndValidate } from "../../functions/projectConfig"; import { previews } from "../../previews"; +import { AUTH_BLOCKING_EVENTS } from "../../functions/events/v1"; function hasUserConfig(config: Record): boolean { // "firebase" key is always going to exist in runtime config. @@ -180,6 +181,7 @@ export async function prepare( inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv); await ensureTriggerRegions(wantBackend); validate.endpointsAreValid(wantBackend); + inferBlockingDetails(wantBackend); payload.functions = { wantBackend: wantBackend, haveBackend: haveBackend }; @@ -283,3 +285,35 @@ function maybeCopyTriggerRegion(wantE: backend.Endpoint, haveE: backend.Endpoint } wantE.eventTrigger.region = haveE.eventTrigger.region; } + +/** Figures out the blocking endpoint options by taking the OR of every trigger option and reassigning that value back to the endpoint. */ +export function inferBlockingDetails(want: backend.Backend): void { + const authBlockingEndpoints = backend + .allEndpoints(want) + .filter( + (ep) => + backend.isBlockingTriggered(ep) && + AUTH_BLOCKING_EVENTS.includes(ep.blockingTrigger.eventType as any) + ) as (backend.Endpoint & backend.BlockingTriggered)[]; + + if (authBlockingEndpoints.length === 0) { + return; + } + + let accessToken = false; + let idToken = false; + let refreshToken = false; + for (const blockingEp of authBlockingEndpoints) { + accessToken ||= !!blockingEp.blockingTrigger.options?.accessToken; + idToken ||= !!blockingEp.blockingTrigger.options?.idToken; + refreshToken ||= !!blockingEp.blockingTrigger.options?.refreshToken; + } + for (const blockingEp of authBlockingEndpoints) { + if (!blockingEp.blockingTrigger.options) { + blockingEp.blockingTrigger.options = {}; + } + blockingEp.blockingTrigger.options.accessToken = accessToken; + blockingEp.blockingTrigger.options.idToken = idToken; + blockingEp.blockingTrigger.options.refreshToken = refreshToken; + } +} diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 63ada445807..9d83cad1ed5 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -21,6 +21,8 @@ import * as reporter from "./reporter"; import * as run from "../../../gcp/run"; import * as scheduler from "../../../gcp/cloudscheduler"; import * as utils from "../../../utils"; +import * as services from "../services"; +import { AUTH_BLOCKING_EVENTS } from "../../../functions/events/v1"; // TODO: Tune this for better performance. const gcfV1PollerOptions: Omit = { @@ -54,6 +56,7 @@ export interface FabricatorArgs { const rethrowAs = (endpoint: backend.Endpoint, op: reporter.OperationType) => (err: unknown): T => { + logger.error((err as Error).message); throw new reporter.DeploymentError(endpoint, op, err); }; @@ -248,6 +251,16 @@ export class Fabricator { }) .catch(rethrowAs(endpoint, "set invoker")); } + } else if ( + backend.isBlockingTriggered(endpoint) && + AUTH_BLOCKING_EVENTS.includes(endpoint.blockingTrigger.eventType as any) + ) { + // Auth Blocking functions should always be public + await this.executor + .run(async () => { + await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), ["public"]); + }) + .catch(rethrowAs(endpoint, "set invoker")); } } @@ -317,6 +330,14 @@ export class Fabricator { }) .catch(rethrowAs(endpoint, "set invoker")); } + } else if ( + backend.isBlockingTriggered(endpoint) && + AUTH_BLOCKING_EVENTS.includes(endpoint.blockingTrigger.eventType as any) + ) { + // Auth Blocking functions should always be public + await this.executor + .run(() => run.setInvokerCreate(endpoint.project, serviceName, ["public"])) + .catch(rethrowAs(endpoint, "set invoker")); } const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; @@ -354,6 +375,11 @@ export class Fabricator { invoker = endpoint.httpsTrigger.invoker; } else if (backend.isTaskQueueTriggered(endpoint)) { invoker = endpoint.taskQueueTrigger.invoker; + } else if ( + backend.isBlockingTriggered(endpoint) && + AUTH_BLOCKING_EVENTS.includes(endpoint.blockingTrigger.eventType as any) + ) { + invoker = ["public"]; } if (invoker) { await this.executor @@ -395,6 +421,11 @@ export class Fabricator { invoker = endpoint.httpsTrigger.invoker; } else if (backend.isTaskQueueTriggered(endpoint)) { invoker = endpoint.taskQueueTrigger.invoker; + } else if ( + backend.isBlockingTriggered(endpoint) && + AUTH_BLOCKING_EVENTS.includes(endpoint.blockingTrigger.eventType as any) + ) { + invoker = ["public"]; } if (invoker) { await this.executor @@ -472,6 +503,8 @@ export class Fabricator { assertExhaustive(endpoint.platform); } else if (backend.isTaskQueueTriggered(endpoint)) { await this.upsertTaskQueue(endpoint); + } else if (backend.isBlockingTriggered(endpoint)) { + await this.registerBlockingTrigger(endpoint); } } @@ -487,6 +520,8 @@ export class Fabricator { assertExhaustive(endpoint.platform); } else if (backend.isTaskQueueTriggered(endpoint)) { await this.disableTaskQueue(endpoint); + } else if (backend.isBlockingTriggered(endpoint)) { + await this.unregisterBlockingTrigger(endpoint); } } @@ -519,6 +554,14 @@ export class Fabricator { } } + async registerBlockingTrigger( + endpoint: backend.Endpoint & backend.BlockingTriggered + ): Promise { + await this.executor + .run(() => services.serviceForEndpoint(endpoint).registerTrigger(endpoint)) + .catch(rethrowAs(endpoint, "register blocking trigger")); + } + async deleteScheduleV1(endpoint: backend.Endpoint & backend.ScheduleTriggered): Promise { const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation); await this.executor @@ -546,6 +589,14 @@ export class Fabricator { .catch(rethrowAs(endpoint, "disable task queue")); } + async unregisterBlockingTrigger( + endpoint: backend.Endpoint & backend.BlockingTriggered + ): Promise { + await this.executor + .run(() => services.serviceForEndpoint(endpoint).unregisterTrigger(endpoint)) + .catch(rethrowAs(endpoint, "unregister blocking trigger")); + } + logOpStart(op: string, endpoint: backend.Endpoint): void { const runtime = getHumanFriendlyRuntimeName(endpoint.runtime); const label = helper.getFunctionLabel(endpoint); diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index 1bb6646d49a..541be7578d7 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -235,6 +235,8 @@ export function checkForIllegalUpdate(want: backend.Endpoint, have: backend.Endp return "a scheduled"; } else if (backend.isTaskQueueTriggered(e)) { return "a task queue"; + } else if (backend.isBlockingTriggered(e)) { + return e.blockingTrigger.eventType; } // Unfortunately TypeScript isn't like Scala and I can't prove to it // that all cases have been handled diff --git a/src/deploy/functions/release/reporter.ts b/src/deploy/functions/release/reporter.ts index 35c219ada8c..d05351f1957 100644 --- a/src/deploy/functions/release/reporter.ts +++ b/src/deploy/functions/release/reporter.ts @@ -28,7 +28,9 @@ export type OperationType = | "create topic" | "delete topic" | "set invoker" - | "set concurrency"; + | "set concurrency" + | "register blocking trigger" + | "unregister blocking trigger"; /** An error with a deployment phase. */ export class DeploymentError extends Error { @@ -245,5 +247,9 @@ export function triggerTag(endpoint: backend.Endpoint): string { return `${prefix}.https`; } + if (backend.isBlockingTriggered(endpoint)) { + return `${prefix}.blocking`; + } + return endpoint.eventTrigger.eventType; } diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 00c459547f8..e690a182d2a 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -19,6 +19,7 @@ export type ManifestEndpoint = backend.ServiceConfiguration & Partial & Partial & Partial & + Partial & Partial & { region?: string[]; entryPoint: string; @@ -102,6 +103,7 @@ function parseEndpoints( eventTrigger: "object", scheduleTrigger: "object", taskQueueTrigger: "object", + blockingTrigger: "object", }); let triggerCount = 0; if (ep.httpsTrigger) { @@ -119,6 +121,9 @@ function parseEndpoints( if (ep.taskQueueTrigger) { triggerCount++; } + if (ep.blockingTrigger) { + triggerCount++; + } if (!triggerCount) { throw new FirebaseError("Expected trigger in endpoint " + id); } @@ -192,6 +197,13 @@ function parseEndpoints( }); } triggered = { taskQueueTrigger: ep.taskQueueTrigger }; + } else if (backend.isBlockingTriggered(ep)) { + requireKeys(prefix + ".blockingTrigger", ep.blockingTrigger, "eventType"); + assertKeyTypes(prefix + ".blockingTrigger", ep.blockingTrigger, { + eventType: "string", + options: "object", + }); + triggered = { blockingTrigger: ep.blockingTrigger }; } else { throw new FirebaseError( `Do not recognize trigger type for endpoint ${id}. Try upgrading ` + diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 4c5d0ae1a4f..463828f5d3d 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -9,7 +9,7 @@ import * as api from "../../../../api"; import * as proto from "../../../../gcp/proto"; import * as args from "../../args"; import * as runtimes from "../../runtimes"; -import * as v2events from "../../../../functions/events/v2"; +import * as events from "../../../../functions/events"; const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js"); @@ -68,6 +68,10 @@ export interface TriggerAnnotation { }; invoker?: string[]; }; + blockingTrigger?: { + eventType: string; + options?: Record; + }; failurePolicy?: {}; schedule?: ScheduleAnnotation; timeZone?: string; @@ -180,7 +184,10 @@ export function addResourcesToBackend( // +!! is 1 for truthy values and 0 for falsy values const triggerCount = - +!!annotation.httpsTrigger + +!!annotation.eventTrigger + +!!annotation.taskQueueTrigger; + +!!annotation.httpsTrigger + + +!!annotation.eventTrigger + + +!!annotation.taskQueueTrigger + + +!!annotation.blockingTrigger; if (triggerCount !== 1) { throw new FirebaseError( "Unexpected annotation generated by the Firebase Functions SDK. This should never happen." @@ -211,6 +218,19 @@ export function addResourcesToBackend( reason: "Needed for scheduled functions.", }); triggered = { scheduleTrigger: annotation.schedule }; + } else if (annotation.blockingTrigger) { + if (events.v1.AUTH_BLOCKING_EVENTS.includes(annotation.blockingTrigger.eventType as any)) { + want.requiredAPIs.push({ + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions.", + }); + } + triggered = { + blockingTrigger: { + eventType: annotation.blockingTrigger.eventType, + options: annotation.blockingTrigger.options, + }, + }; } else { triggered = { eventTrigger: { @@ -223,12 +243,12 @@ export function addResourcesToBackend( // TODO: yank this edge case for a v2 trigger on the pre-container contract // once we use container contract for the functionsv2 experiment. if (annotation.platform === "gcfv2") { - if (annotation.eventTrigger!.eventType === v2events.PUBSUB_PUBLISH_EVENT) { + if (annotation.eventTrigger!.eventType === events.v2.PUBSUB_PUBLISH_EVENT) { triggered.eventTrigger.eventFilters = { topic: annotation.eventTrigger!.resource }; } if ( - v2events.STORAGE_EVENTS.find( + events.v2.STORAGE_EVENTS.find( (event) => event === (annotation.eventTrigger?.eventType || "") ) ) { diff --git a/src/deploy/functions/services/auth.ts b/src/deploy/functions/services/auth.ts new file mode 100644 index 00000000000..9940e6224b1 --- /dev/null +++ b/src/deploy/functions/services/auth.ts @@ -0,0 +1,150 @@ +import * as backend from "../backend"; +import * as identityPlatform from "../../../gcp/identityPlatform"; +import * as events from "../../../functions/events"; +import { FirebaseError } from "../../../error"; +import { cloneDeep } from "../../../utils"; +import { Name, noop, Service } from "./index"; + +export class AuthBlockingService implements Service { + name: Name; + api: string; + triggerQueue: Promise; + unregisterQueue: Promise; + + constructor() { + this.name = "authblocking"; + this.api = "identitytoolkit.googleapis.com"; + this.triggerQueue = Promise.resolve(); + this.unregisterQueue = Promise.resolve(); + this.ensureTriggerRegion = noop; + } + + ensureTriggerRegion: (ep: backend.Endpoint & backend.EventTriggered) => Promise; + + /** + * Ensure that at most one blocking function of that type exists and merges identity platform options on our backend to deploy. + * @param endpoint the Auth Blocking endpoint + * @param wantBackend the backend we are deploying + */ + validateTrigger( + endpoint: backend.Endpoint & backend.BlockingTriggered, + wantBackend: backend.Backend + ): void { + const blockingEndpoints = backend + .allEndpoints(wantBackend) + .filter((ep) => backend.isBlockingTriggered(ep)) as (backend.Endpoint & + backend.BlockingTriggered)[]; + if ( + blockingEndpoints.find( + (ep) => + ep.blockingTrigger.eventType === endpoint.blockingTrigger.eventType && + ep.id !== endpoint.id + ) + ) { + throw new FirebaseError( + `Can only create at most one Auth Blocking Trigger for ${endpoint.blockingTrigger.eventType} events` + ); + } + } + + private configChanged( + newConfig: identityPlatform.BlockingFunctionsConfig, + config: identityPlatform.BlockingFunctionsConfig + ) { + if ( + newConfig.triggers?.beforeCreate?.functionUri !== + config.triggers?.beforeCreate?.functionUri || + newConfig.triggers?.beforeSignIn?.functionUri !== config.triggers?.beforeSignIn?.functionUri + ) { + return true; + } + if ( + !!newConfig.forwardInboundCredentials?.accessToken !== + !!config.forwardInboundCredentials?.accessToken || + !!newConfig.forwardInboundCredentials?.idToken !== + !!config.forwardInboundCredentials?.idToken || + !!newConfig.forwardInboundCredentials?.refreshToken !== + !!config.forwardInboundCredentials?.refreshToken + ) { + return true; + } + return false; + } + + private async registerTriggerLocked( + endpoint: backend.Endpoint & backend.BlockingTriggered + ): Promise { + const newBlockingConfig = await identityPlatform.getBlockingFunctionsConfig(endpoint.project); + const oldBlockingConfig = cloneDeep(newBlockingConfig); + + if (endpoint.blockingTrigger.eventType === events.v1.BEFORE_CREATE_EVENT) { + newBlockingConfig.triggers = { + ...newBlockingConfig.triggers, + beforeCreate: { + functionUri: endpoint.uri!, + }, + }; + } else { + newBlockingConfig.triggers = { + ...newBlockingConfig.triggers, + beforeSignIn: { + functionUri: endpoint.uri!, + }, + }; + } + + newBlockingConfig.forwardInboundCredentials = { + idToken: endpoint.blockingTrigger.options?.idToken || false, + accessToken: endpoint.blockingTrigger.options?.accessToken || false, + refreshToken: endpoint.blockingTrigger.options?.refreshToken || false, + }; + + if (!this.configChanged(newBlockingConfig, oldBlockingConfig)) { + return; + } + + await identityPlatform.setBlockingFunctionsConfig(endpoint.project, newBlockingConfig); + } + + /** + * Registers the auth blocking trigger to identity platform. + * @param ep the blocking endpoint + */ + registerTrigger(ep: backend.Endpoint & backend.BlockingTriggered): Promise { + this.triggerQueue = this.triggerQueue.then(() => this.registerTriggerLocked(ep)); + return this.triggerQueue; + } + + private async unregisterTriggerLocked( + endpoint: backend.Endpoint & backend.BlockingTriggered + ): Promise { + const blockingConfig = await identityPlatform.getBlockingFunctionsConfig(endpoint.project); + if ( + endpoint.uri !== blockingConfig.triggers?.beforeCreate?.functionUri && + endpoint.uri !== blockingConfig.triggers?.beforeSignIn?.functionUri + ) { + return; + } + + // There is a possibility that the user changed the registration on identity platform, + // to prevent 400 errors on every create and/or sign in on the app, we will treat + // the blockingConfig as the source of truth and only delete matching uri's. + if (endpoint.uri === blockingConfig.triggers?.beforeCreate?.functionUri) { + delete blockingConfig.triggers?.beforeCreate; + } + if (endpoint.uri === blockingConfig.triggers?.beforeSignIn?.functionUri) { + delete blockingConfig.triggers?.beforeSignIn; + } + + await identityPlatform.setBlockingFunctionsConfig(endpoint.project, blockingConfig); + } + + /** + * Un-registers the auth blocking trigger from identity platform. If the endpoint uri is not on the resource, we do nothing. + * @param ep the blocking endpoint + */ + unregisterTrigger(ep: backend.Endpoint & backend.BlockingTriggered): Promise { + this.triggerQueue = this.triggerQueue.then(() => this.unregisterTriggerLocked(ep)); + return this.triggerQueue; + } +} diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 7fe35c95619..f832e976d91 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -1,64 +1,94 @@ import * as backend from "../backend"; import * as iam from "../../../gcp/iam"; -import * as v2events from "../../../functions/events/v2"; +import * as events from "../../../functions/events"; +import { AuthBlockingService } from "./auth"; import { obtainStorageBindings, ensureStorageTriggerRegion } from "./storage"; import { ensureFirebaseAlertsTriggerRegion } from "./firebaseAlerts"; /** A standard void No Op */ -const noop = (): Promise => Promise.resolve(); +export const noop = (): Promise => Promise.resolve(); /** A No Op that's useful for Services that don't have specific bindings but should still try to set default bindings */ -const noopProjectBindings = (): Promise> => Promise.resolve([]); +export const noopProjectBindings = (): Promise> => Promise.resolve([]); + +/** A name of a service */ +export type Name = "noop" | "pubsub" | "storage" | "firebasealerts" | "authblocking"; /** A service interface for the underlying GCP event services */ export interface Service { - readonly name: string; + readonly name: Name; readonly api: string; // dispatch functions - requiredProjectBindings: - | ((projectNumber: string, policy: iam.Policy) => Promise>) - | undefined; + requiredProjectBindings?: ( + projectNumber: string, + policy: iam.Policy + ) => Promise>; ensureTriggerRegion: (ep: backend.Endpoint & backend.EventTriggered) => Promise; + validateTrigger: ( + ep: backend.Endpoint & backend.BlockingTriggered, + want: backend.Backend + ) => void; + registerTrigger: (ep: backend.Endpoint & backend.BlockingTriggered) => Promise; + unregisterTrigger: (ep: backend.Endpoint & backend.BlockingTriggered) => Promise; } /** A noop service object, useful for v1 events */ -export const NoOpService: Service = { +const NoOpService: Service = { name: "noop", api: "", - requiredProjectBindings: undefined, ensureTriggerRegion: noop, + validateTrigger: noop, + registerTrigger: noop, + unregisterTrigger: noop, }; + /** A pubsub service object */ -export const PubSubService: Service = { +const PubSubService: Service = { name: "pubsub", api: "pubsub.googleapis.com", - requiredProjectBindings: undefined, + requiredProjectBindings: noopProjectBindings, ensureTriggerRegion: noop, + validateTrigger: noop, + registerTrigger: noop, + unregisterTrigger: noop, }; + /** A storage service object */ -export const StorageService: Service = { +const StorageService: Service = { name: "storage", api: "storage.googleapis.com", requiredProjectBindings: obtainStorageBindings, ensureTriggerRegion: ensureStorageTriggerRegion, + validateTrigger: noop, + registerTrigger: noop, + unregisterTrigger: noop, }; + /** A firebase alerts service object */ -export const FirebaseAlertsService: Service = { +const FirebaseAlertsService: Service = { name: "firebasealerts", - api: "logging.googleapis.com", + api: "firebasealerts.googleapis.com", requiredProjectBindings: noopProjectBindings, ensureTriggerRegion: ensureFirebaseAlertsTriggerRegion, + validateTrigger: noop, + registerTrigger: noop, + unregisterTrigger: noop, }; +/** A auth blocking service object */ +const authBlockingService = new AuthBlockingService(); + /** Mapping from event type string to service object */ -export const EVENT_SERVICE_MAPPING: Record = { +const EVENT_SERVICE_MAPPING: Record = { "google.cloud.pubsub.topic.v1.messagePublished": PubSubService, "google.cloud.storage.object.v1.finalized": StorageService, "google.cloud.storage.object.v1.archived": StorageService, "google.cloud.storage.object.v1.deleted": StorageService, "google.cloud.storage.object.v1.metadataUpdated": StorageService, "google.firebase.firebasealerts.alerts.v1.published": FirebaseAlertsService, + "providers/cloud.auth/eventTypes/user.beforeCreate": authBlockingService, + "providers/cloud.auth/eventTypes/user.beforeSignIn": authBlockingService, }; /** @@ -67,9 +97,13 @@ export const EVENT_SERVICE_MAPPING: Record = { * @return a Service object that corresponds to the event type of the endpoint or noop */ export function serviceForEndpoint(endpoint: backend.Endpoint): Service { - if (!backend.isEventTriggered(endpoint)) { - return NoOpService; + if (backend.isEventTriggered(endpoint)) { + return EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType as events.Event] || NoOpService; + } + + if (backend.isBlockingTriggered(endpoint)) { + return EVENT_SERVICE_MAPPING[endpoint.blockingTrigger.eventType as events.Event] || NoOpService; } - return EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType as v2events.Event] || NoOpService; + return NoOpService; } diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index b9f8b8769a9..ebae6e77901 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -8,14 +8,18 @@ import * as fsutils from "../../fsutils"; import * as backend from "./backend"; import * as utils from "../../utils"; import * as secrets from "../../functions/secrets"; +import { serviceForEndpoint } from "./services"; /** Validate that the configuration for endpoints are valid. */ export function endpointsAreValid(wantBackend: backend.Backend): void { - functionIdsAreValid(backend.allEndpoints(wantBackend)); + const endpoints = backend.allEndpoints(wantBackend); + functionIdsAreValid(endpoints); + for (const ep of endpoints) { + serviceForEndpoint(ep).validateTrigger(ep as any, wantBackend); + } // Our SDK doesn't let people articulate this, but it's theoretically possible in the manifest syntax. - const gcfV1WithConcurrency = backend - .allEndpoints(wantBackend) + const gcfV1WithConcurrency = endpoints .filter((endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1") .map((endpoint) => endpoint.id); if (gcfV1WithConcurrency.length) { @@ -25,8 +29,7 @@ export function endpointsAreValid(wantBackend: backend.Backend): void { throw new FirebaseError(msg); } - const tooSmallForConcurrency = backend - .allEndpoints(wantBackend) + const tooSmallForConcurrency = endpoints .filter((endpoint) => { if ((endpoint.concurrency || 1) === 1) { return false; diff --git a/src/functions/events/index.ts b/src/functions/events/index.ts new file mode 100644 index 00000000000..0eb97d89176 --- /dev/null +++ b/src/functions/events/index.ts @@ -0,0 +1,6 @@ +import * as v1 from "./v1"; +import * as v2 from "./v2"; + +export { v1, v2 }; + +export type Event = v1.Event | v2.Event; diff --git a/src/functions/events/v1.ts b/src/functions/events/v1.ts new file mode 100644 index 00000000000..f8936ac68e9 --- /dev/null +++ b/src/functions/events/v1.ts @@ -0,0 +1,7 @@ +export const BEFORE_CREATE_EVENT = "providers/cloud.auth/eventTypes/user.beforeCreate"; + +export const BEFORE_SIGN_IN_EVENT = "providers/cloud.auth/eventTypes/user.beforeSignIn"; + +export const AUTH_BLOCKING_EVENTS = [BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT] as const; + +export type Event = typeof AUTH_BLOCKING_EVENTS[number]; diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 69dddc700a4..a17eda8da8a 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -4,6 +4,7 @@ import { FirebaseError } from "../error"; import { logger } from "../logger"; import { previews } from "../previews"; import * as backend from "../deploy/functions/backend"; +import * as events from "../functions/events"; import * as utils from "../utils"; import * as proto from "./proto"; import * as runtimes from "../deploy/functions/runtimes"; @@ -11,11 +12,24 @@ import * as iam from "./iam"; import * as projectConfig from "../functions/projectConfig"; import { Client } from "../apiv2"; import { functionsOrigin } from "../api"; +import { AUTH_BLOCKING_EVENTS } from "../functions/events/v1"; export const API_VERSION = "v1"; export const CODEBASE_LABEL = "firebase-functions-codebase"; const client = new Client({ urlPrefix: functionsOrigin, apiVersion: API_VERSION }); +export const BLOCKING_LABEL = "deployment-blocking"; + +const BLOCKING_LABEL_KEY_TO_EVENT: Record = { + "before-create": "providers/cloud.auth/eventTypes/user.beforeCreate", + "before-sign-in": "providers/cloud.auth/eventTypes/user.beforeSignIn", +}; + +const BLOCKING_EVENT_TO_LABEL_KEY: Record = { + "providers/cloud.auth/eventTypes/user.beforeCreate": "before-create", + "providers/cloud.auth/eventTypes/user.beforeSignIn": "before-sign-in", +}; + interface Operation { name: string; type: string; @@ -489,10 +503,14 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { callableTrigger: {}, }; + } else if (gcfFunction.labels?.[BLOCKING_LABEL]) { + trigger = { + blockingTrigger: { + eventType: BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[BLOCKING_LABEL]], + }, + }; } else if (gcfFunction.httpsTrigger) { trigger = { httpsTrigger: {} }; - uri = gcfFunction.httpsTrigger.url; - securityLevel = gcfFunction.httpsTrigger.securityLevel; } else { trigger = { eventTrigger: { @@ -503,6 +521,11 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi }; } + if (gcfFunction.httpsTrigger) { + uri = gcfFunction.httpsTrigger.url; + securityLevel = gcfFunction.httpsTrigger.securityLevel; + } + if (!runtimes.isValidRuntime(gcfFunction.runtime)) { logger.debug("GCFv1 function has a deprecated runtime:", JSON.stringify(gcfFunction, null, 2)); } @@ -604,6 +627,15 @@ export function functionFromEndpoint( } else if (backend.isTaskQueueTriggered(endpoint)) { gcfFunction.httpsTrigger = {}; gcfFunction.labels = { ...gcfFunction.labels, "deployment-taskqueue": "true" }; + } else if (backend.isBlockingTriggered(endpoint)) { + gcfFunction.httpsTrigger = {}; + gcfFunction.labels = { + ...gcfFunction.labels, + [BLOCKING_LABEL]: + BLOCKING_EVENT_TO_LABEL_KEY[ + endpoint.blockingTrigger.eventType as typeof AUTH_BLOCKING_EVENTS[number] + ], + }; } else { gcfFunction.httpsTrigger = {}; if (backend.isCallableTriggered(endpoint)) { diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 03b3457fd0b..984dd42b16a 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -4,6 +4,7 @@ import { Client } from "../apiv2"; import { FirebaseError } from "../error"; import { functionsV2Origin } from "../api"; import { logger } from "../logger"; +import { AUTH_BLOCKING_EVENTS } from "../functions/events/v1"; import { PUBSUB_PUBLISH_EVENT } from "../functions/events/v2"; import * as backend from "../deploy/functions/backend"; import * as runtimes from "../deploy/functions/runtimes"; @@ -20,6 +21,18 @@ const client = new Client({ apiVersion: API_VERSION, }); +export const BLOCKING_LABEL = "deployment-blocking"; + +const BLOCKING_LABEL_KEY_TO_EVENT: Record = { + "before-create": "providers/cloud.auth/eventTypes/user.beforeCreate", + "before-sign-in": "providers/cloud.auth/eventTypes/user.beforeSignIn", +}; + +const BLOCKING_EVENT_TO_LABEL_KEY: Record = { + "providers/cloud.auth/eventTypes/user.beforeCreate": "before-create", + "providers/cloud.auth/eventTypes/user.beforeSignIn": "before-sign-in", +}; + export type VpcConnectorEgressSettings = "PRIVATE_RANGES_ONLY" | "ALL_TRAFFIC"; export type IngressSettings = "ALLOW_ALL" | "ALLOW_INTERNAL_ONLY" | "ALLOW_INTERNAL_AND_GCLB"; export type FunctionState = "ACTIVE" | "FAILED" | "DEPLOYING" | "DELETING" | "UNKONWN"; @@ -473,6 +486,14 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage gcfFunction.labels = { ...gcfFunction.labels, "deployment-taskqueue": "true" }; } else if (backend.isCallableTriggered(endpoint)) { gcfFunction.labels = { ...gcfFunction.labels, "deployment-callable": "true" }; + } else if (backend.isBlockingTriggered(endpoint)) { + gcfFunction.labels = { + ...gcfFunction.labels, + [BLOCKING_LABEL]: + BLOCKING_EVENT_TO_LABEL_KEY[ + endpoint.blockingTrigger.eventType as typeof AUTH_BLOCKING_EVENTS[number] + ], + }; } gcfFunction.labels = { ...gcfFunction.labels, @@ -499,6 +520,12 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi trigger = { callableTrigger: {}, }; + } else if (gcfFunction.labels?.[BLOCKING_LABEL]) { + trigger = { + blockingTrigger: { + eventType: BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[BLOCKING_LABEL]], + }, + }; } else if (gcfFunction.eventTrigger) { trigger = { eventTrigger: { diff --git a/src/gcp/identityPlatform.ts b/src/gcp/identityPlatform.ts new file mode 100644 index 00000000000..4149843c00d --- /dev/null +++ b/src/gcp/identityPlatform.ts @@ -0,0 +1,221 @@ +import * as proto from "./proto"; +import { identityOrigin } from "../api"; +import { Client } from "../apiv2"; + +const API_VERSION = "v2"; + +const adminApiClient = new Client({ + urlPrefix: identityOrigin + "/admin", + apiVersion: API_VERSION, +}); + +export type HashAlgorithm = + | "HASH_ALGORITHM_UNSPECIFIED" + | "HMAC_SHA256" + | "HMAC_SHA1" + | "HMAC_MD5" + | "SCRYPT" + | "PBKDF_SHA1" + | "MD5" + | "HMAC_SHA512" + | "SHA1" + | "BCRYPT" + | "PBKDF2_SHA256" + | "SHA256" + | "SHA512" + | "STANDARD_SCRYPT"; + +export interface EmailTemplate { + senderLocalPart: string; + subject: string; + senderDisplayName: string; + body: string; + bodyFormat: "BODY_FORMAT_UNSPECIFIED" | "PLAIN_TEXT" | "HTML"; + replyTo: string; + customized: boolean; +} + +export type Provider = "PROVIDER_UNSPECIFIED" | "PHONE_SMS"; + +export interface BlockingFunctionsConfig { + triggers?: { + beforeCreate?: BlockingFunctionsEventDetails; + beforeSignIn?: BlockingFunctionsEventDetails; + }; + forwardInboundCredentials?: BlockingFunctionsOptions; +} + +export interface BlockingFunctionsEventDetails { + functionUri?: string; + updateTime?: string; +} + +export interface BlockingFunctionsOptions { + idToken?: boolean; + accessToken?: boolean; + refreshToken?: boolean; +} + +export interface Config { + name?: string; + signIn?: { + email?: { + enabled: boolean; + passwordRequired: boolean; + }; + phoneNumber?: { + enabled: boolean; + testPhoneNumbers: Record; + }; + anonymous?: { + enabled: boolean; + }; + allowDuplicateEmails?: boolean; + hashConfig?: { + algorithm: HashAlgorithm; + signerKey: string; + saltSeparator: string; + rounds: number; + memoryCost: number; + }; + }; + notification?: { + sendEmail: { + method: "METHOD_UNSPECIFIED" | "DEFAULT" | "CUSTOM_SMTP"; + resetPasswordTemplate: EmailTemplate; + verifyEmailTemplate: EmailTemplate; + changeEmailTemplate: EmailTemplate; + legacyResetPasswordTemplate: EmailTemplate; + callbackUri: string; + dnsInfo: { + customDomain: string; + useCustomDomain: boolean; + pendingCustomDomain: string; + customDomainState: + | "VERIFICATION_STATE_UNSPECIFIED" + | "NOT_STARTED" + | "IN_PROGRESS" + | "FAILED" + | "SUCCEEDED"; + domainVerificationRequestTime: string; + }; + revertSecondFactorAdditionTemplate: EmailTemplate; + smtp: { + senderEmail: string; + host: string; + port: number; + username: string; + password: string; + securityMode: "SECURITY_MODE_UNSPECIFIED" | "SSL" | "START_TLS"; + }; + }; + sendSms: { + useDeviceLocale?: boolean; + smsTemplate?: { + content?: string; + }; + }; + defaultLocale?: string; + }; + quota?: { + signUpQuotaConfig?: { + quota?: string; + startTime?: string; + quotaDuration?: string; + }; + }; + monitoring?: { + requestLogging?: { + enabled?: boolean; + }; + }; + multiTenant?: { + allowTenants?: boolean; + defaultTenantLocation?: string; + }; + authorizedDomains?: Array; + subtype?: "SUBTYPE_UNSPECIFIED" | "IDENTITY_PLATFORM" | "FIREBASE_AUTH"; + client?: { + apiKey?: string; + permissions?: { + disabledUserSignup?: boolean; + disabledUserDeletion?: boolean; + }; + firebaseSubdomain?: string; + }; + mfa?: { + state?: "STATE_UNSPECIFIED" | "DISABLED" | "ENABLED" | "MANDATORY"; + enabledProviders?: Array; + }; + blockingFunctions?: BlockingFunctionsConfig; +} + +/** + * Helper function to get the blocking function config from identity platform. + * @param project GCP project ID or number + * @returns the blocking functions config + */ +export async function getBlockingFunctionsConfig( + project: string +): Promise { + const config = (await getConfig(project)) || {}; + if (!config.blockingFunctions) { + config.blockingFunctions = {}; + } + return config.blockingFunctions; +} + +/** + * Gets the identity platform configuration. + * @param project GCP project ID or number + * @returns the identity platform config + */ +export async function getConfig(project: string): Promise { + const response = await adminApiClient.get(`projects/${project}/config`); + return response.body; +} + +/** + * Helper function to set the blocking function config to identity platform. + * @param project GCP project ID or number + * @param blockingConfig the blocking functions configuration to update + * @returns the blocking functions config + */ +export async function setBlockingFunctionsConfig( + project: string, + blockingConfig: BlockingFunctionsConfig +): Promise { + const config = + (await updateConfig(project, { blockingFunctions: blockingConfig }, "blockingFunctions")) || {}; + if (!config.blockingFunctions) { + config.blockingFunctions = {}; + } + return config.blockingFunctions; +} + +/** + * Sets the identity platform configuration. + * @param project GCP project ID or number + * @param config the configuration to update + * @param updateMask optional update mask for the API + * @returns the updated config + */ +export async function updateConfig( + project: string, + config: Config, + updateMask?: string +): Promise { + if (!updateMask) { + updateMask = proto.fieldMasks(config).join(","); + } + const response = await adminApiClient.patch( + `projects/${project}/config`, + config, + { + queryParams: { + updateMask, + }, + } + ); + return response.body; +} diff --git a/src/test/deploy/functions/prepare.spec.ts b/src/test/deploy/functions/prepare.spec.ts index 38ba2a2cc80..9a8f222033d 100644 --- a/src/test/deploy/functions/prepare.spec.ts +++ b/src/test/deploy/functions/prepare.spec.ts @@ -2,6 +2,7 @@ import { expect } from "chai"; import * as backend from "../../../deploy/functions/backend"; import * as prepare from "../../../deploy/functions/prepare"; +import { BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT } from "../../../functions/events/v1"; describe("prepare", () => { const ENDPOINT_BASE: Omit = { @@ -122,4 +123,40 @@ describe("prepare", () => { expect(want.availableMemoryMb).to.equal(512); }); }); + + describe("inferBlockingDetails", () => { + it("should merge the blocking options and set default value", () => { + const beforeCreate: backend.Endpoint = { + ...ENDPOINT_BASE, + id: "beforeCreate", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: true, + refreshToken: false, + }, + }, + }; + const beforeSignIn: backend.Endpoint = { + ...ENDPOINT_BASE, + id: "beforeSignIn", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + options: { + accessToken: false, + idToken: true, + }, + }, + }; + + prepare.inferBlockingDetails(backend.of(beforeCreate, beforeSignIn)); + + expect(beforeCreate.blockingTrigger.options?.accessToken).to.be.true; + expect(beforeCreate.blockingTrigger.options?.idToken).to.be.true; + expect(beforeCreate.blockingTrigger.options?.refreshToken).to.be.false; + expect(beforeSignIn.blockingTrigger.options?.accessToken).to.be.true; + expect(beforeSignIn.blockingTrigger.options?.idToken).to.be.true; + expect(beforeSignIn.blockingTrigger.options?.refreshToken).to.be.false; + }); + }); }); diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 67907ef1ba8..447091f1bc2 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -15,6 +15,10 @@ import * as backend from "../../../../deploy/functions/backend"; import * as scraper from "../../../../deploy/functions/release/sourceTokenScraper"; import * as planner from "../../../../deploy/functions/release/planner"; import * as v2events from "../../../../functions/events/v2"; +import * as v1events from "../../../../functions/events/v1"; +import * as servicesNS from "../../../../deploy/functions/services"; +import * as identityPlatformNS from "../../../../gcp/identityPlatform"; +import { AuthBlockingService } from "../../../../deploy/functions/services/auth"; describe("Fabricator", () => { // Stub all GCP APIs to make sure this test is hermetic @@ -25,6 +29,8 @@ describe("Fabricator", () => { let scheduler: sinon.SinonStubbedInstance; let run: sinon.SinonStubbedInstance; let tasks: sinon.SinonStubbedInstance; + let services: sinon.SinonStubbedInstance; + let identityPlatform: sinon.SinonStubbedInstance; beforeEach(() => { gcf = sinon.stub(gcfNS); @@ -34,6 +40,8 @@ describe("Fabricator", () => { scheduler = sinon.stub(schedulerNS); run = sinon.stub(runNS); tasks = sinon.stub(cloudtasksNS); + services = sinon.stub(servicesNS); + identityPlatform = sinon.stub(identityPlatformNS); gcf.functionFromEndpoint.restore(); gcfv2.functionFromEndpoint.restore(); @@ -67,6 +75,13 @@ describe("Fabricator", () => { tasks.setEnqueuer.rejects(new Error("unexpected tasks.setEnqueuer")); tasks.setIamPolicy.rejects(new Error("unexpected tasks.setIamPolicy")); tasks.getIamPolicy.rejects(new Error("unexpected tasks.getIamPolicy")); + services.serviceForEndpoint.throws("unexpected services.serviceForEndpoint"); + identityPlatform.getBlockingFunctionsConfig.rejects( + new Error("unexpected identityPlatform.getBlockingFunctionsConfig") + ); + identityPlatform.setBlockingFunctionsConfig.rejects( + new Error("unexpected identityPlatform.setBlockingFunctionsConfig") + ); }); afterEach(() => { @@ -265,6 +280,21 @@ describe("Fabricator", () => { }); }); + describe("blockingTrigger", () => { + it("sets the invoker to public", async () => { + gcf.createFunction.resolves({ name: "op", type: "create", done: false }); + poller.pollOperation.resolves(); + gcf.setInvokerCreate.resolves(); + const ep = endpoint({ blockingTrigger: { eventType: v1events.BEFORE_CREATE_EVENT } }); + + await fab.createV1Function(ep, new scraper.SourceTokenScraper()); + + expect(gcf.setInvokerCreate).to.have.been.calledWith(ep.project, backend.functionName(ep), [ + "public", + ]); + }); + }); + it("doesn't set invoker on non-http functions", async () => { gcf.createFunction.resolves({ name: "op", type: "create", done: false }); poller.pollOperation.resolves(); @@ -331,15 +361,24 @@ describe("Fabricator", () => { invoker: ["custom@"], }, }); + const ep2 = endpoint({ + blockingTrigger: { + eventType: v1events.BEFORE_CREATE_EVENT, + }, + }); await fab.updateV1Function(ep0, new scraper.SourceTokenScraper()); await fab.updateV1Function(ep1, new scraper.SourceTokenScraper()); + await fab.updateV1Function(ep2, new scraper.SourceTokenScraper()); expect(gcf.setInvokerUpdate).to.have.been.calledWith(ep0.project, backend.functionName(ep0), [ "custom@", ]); expect(gcf.setInvokerUpdate).to.have.been.calledWith(ep1.project, backend.functionName(ep1), [ "custom@", ]); + expect(gcf.setInvokerUpdate).to.have.been.calledWith(ep2.project, backend.functionName(ep2), [ + "public", + ]); }); it("does not set invoker by default", async () => { @@ -573,6 +612,21 @@ describe("Fabricator", () => { }); }); + describe("blockingTrigger", () => { + it("always sets invoker to public", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint( + { blockingTrigger: { eventType: v1events.BEFORE_CREATE_EVENT } }, + { platform: "gcfv2" } + ); + + await fab.createV2Function(ep); + expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["public"]); + }); + }); + it("doesn't set invoker on non-http functions", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); @@ -642,6 +696,23 @@ describe("Fabricator", () => { expect(run.setInvokerUpdate).to.have.been.calledWith(ep.project, "service", ["custom@"]); }); + it("sets explicit invoker on blockingTrigger", async () => { + gcfv2.updateFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerUpdate.resolves(); + const ep = endpoint( + { + blockingTrigger: { + eventType: v1events.BEFORE_CREATE_EVENT, + }, + }, + { platform: "gcfv2" } + ); + + await fab.updateV2Function(ep); + expect(run.setInvokerUpdate).to.have.been.calledWith(ep.project, "service", ["public"]); + }); + it("does not set invoker by default", async () => { gcfv2.updateFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); @@ -868,6 +939,68 @@ describe("Fabricator", () => { }); }); + describe("registerBlockingTrigger", () => { + const ep = endpoint( + { + blockingTrigger: { + eventType: v1events.BEFORE_CREATE_EVENT, + }, + }, + { uri: "myuri.net" } + ) as backend.Endpoint & backend.BlockingTriggered; + const authBlockingService = new AuthBlockingService(); + + it("registers auth blocking trigger", async () => { + services.serviceForEndpoint.returns(authBlockingService); + identityPlatform.getBlockingFunctionsConfig.resolves({}); + identityPlatform.setBlockingFunctionsConfig.resolves({}); + await fab.registerBlockingTrigger(ep); + expect(identityPlatform.getBlockingFunctionsConfig).to.have.been.called; + expect(identityPlatform.setBlockingFunctionsConfig).to.have.been.called; + }); + + it("wraps errors", async () => { + services.serviceForEndpoint.returns(authBlockingService); + identityPlatform.getBlockingFunctionsConfig.rejects(new Error("Fail")); + await expect(fab.registerBlockingTrigger(ep)).to.eventually.be.rejectedWith( + reporter.DeploymentError, + "register blocking trigger" + ); + }); + }); + + describe("unregisterBlockingTrigger", () => { + const ep = endpoint( + { + blockingTrigger: { + eventType: v1events.BEFORE_CREATE_EVENT, + }, + }, + { uri: "myuri.net" } + ) as backend.Endpoint & backend.BlockingTriggered; + const authBlockingService = new AuthBlockingService(); + + it("unregisters auth blocking trigger", async () => { + services.serviceForEndpoint.returns(authBlockingService); + identityPlatform.getBlockingFunctionsConfig.resolves({ + triggers: { beforeCreate: { functionUri: "myuri.net" } }, + }); + identityPlatform.setBlockingFunctionsConfig.resolves({}); + await fab.unregisterBlockingTrigger(ep); + expect(identityPlatform.getBlockingFunctionsConfig).to.have.been.called; + expect(identityPlatform.setBlockingFunctionsConfig).to.have.been.called; + }); + + it("wraps errors", async () => { + services.serviceForEndpoint.returns(authBlockingService); + identityPlatform.getBlockingFunctionsConfig.rejects(new Error("Fail")); + await expect(fab.unregisterBlockingTrigger(ep)).to.eventually.be.rejectedWith( + reporter.DeploymentError, + "unregister blocking trigger" + ); + }); + }); + describe("setTrigger", () => { it("does nothing for HTTPS functions", async () => { // all APIs throw by default diff --git a/src/test/deploy/functions/release/reporter.spec.ts b/src/test/deploy/functions/release/reporter.spec.ts index d2727022e7d..ae2de5cd3f2 100644 --- a/src/test/deploy/functions/release/reporter.spec.ts +++ b/src/test/deploy/functions/release/reporter.spec.ts @@ -5,6 +5,7 @@ import { logger } from "../../../../logger"; import * as backend from "../../../../deploy/functions/backend"; import * as reporter from "../../../../deploy/functions/release/reporter"; import * as track from "../../../../track"; +import * as events from "../../../../functions/events"; const ENDPOINT_BASE: Omit = { platform: "gcfv1", @@ -81,6 +82,25 @@ describe("reporter", () => { ).to.equal("v2.scheduled"); }); + it("detects v1.blocking", () => { + expect( + reporter.triggerTag({ + ...ENDPOINT_BASE, + blockingTrigger: { eventType: events.v1.BEFORE_CREATE_EVENT }, + }) + ).to.equal("v1.blocking"); + }); + + it("detects v2.blocking", () => { + expect( + reporter.triggerTag({ + ...ENDPOINT_BASE, + platform: "gcfv2", + blockingTrigger: { eventType: events.v1.BEFORE_CREATE_EVENT }, + }) + ).to.equal("v2.blocking"); + }); + it("detects others", () => { expect( reporter.triggerTag({ diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 633dc7a8c4b..9feda372e59 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -4,6 +4,7 @@ import { FirebaseError } from "../../../../../error"; import * as backend from "../../../../../deploy/functions/backend"; import { Runtime } from "../../../../../deploy/functions/runtimes"; import * as v1alpha1 from "../../../../../deploy/functions/runtimes/discovery/v1alpha1"; +import { BEFORE_CREATE_EVENT } from "../../../../../functions/events/v1"; const PROJECT = "project"; const REGION = "region"; @@ -256,6 +257,36 @@ describe("backendFromV1Alpha1", () => { } }); + describe("blockingTriggers", () => { + const validTrigger: backend.BlockingTrigger = { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: true, + idToken: false, + refreshToken: true, + }, + }; + + const invalidOptions = { + eventType: true, + options: 11, + }; + + for (const [key, value] of Object.entries(invalidOptions)) { + it(`invalid value for blocking trigger key ${key}`, () => { + const blockingTrigger = { + ...validTrigger, + [key]: value, + }; + assertParserError({ + endpoints: { + func: { ...MIN_ENDPOINT, blockingTrigger }, + }, + }); + }); + } + }); + it("detects missing triggers", () => { assertParserError({ endpoints: MIN_ENDPOINT }); }); @@ -366,6 +397,52 @@ describe("backendFromV1Alpha1", () => { expect(parsed).to.deep.equal(expected); }); + it("copies blocking triggers", () => { + const blockingTrigger: backend.BlockingTrigger = { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: true, + idToken: false, + refreshToken: true, + }, + }; + const yaml: v1alpha1.Manifest = { + specVersion: "v1alpha1", + endpoints: { + id: { + ...MIN_ENDPOINT, + blockingTrigger, + }, + }, + }; + const expected = backend.of({ ...DEFAULTED_ENDPOINT, blockingTrigger }); + const parsed = v1alpha1.backendFromV1Alpha1(yaml, PROJECT, REGION, RUNTIME); + expect(parsed).to.deep.equal(expected); + }); + + it("copies blocking triggers without options", () => { + const blockingTrigger: backend.BlockingTrigger = { + eventType: BEFORE_CREATE_EVENT, + }; + const yaml: v1alpha1.Manifest = { + specVersion: "v1alpha1", + endpoints: { + id: { + ...MIN_ENDPOINT, + blockingTrigger, + }, + }, + }; + const expected = backend.of({ + ...DEFAULTED_ENDPOINT, + blockingTrigger: { + ...blockingTrigger, + }, + }); + const parsed = v1alpha1.backendFromV1Alpha1(yaml, PROJECT, REGION, RUNTIME); + expect(parsed).to.deep.equal(expected); + }); + describe("channel name", () => { it("resolves partial (channel ID only) channel resource name", () => { const eventTrigger: backend.EventTrigger = { diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index 6c671678551..1a10affcbbb 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -4,6 +4,7 @@ import { FirebaseError } from "../../../../../error"; import * as backend from "../../../../../deploy/functions/backend"; import * as parseTriggers from "../../../../../deploy/functions/runtimes/node/parseTriggers"; import * as api from "../../../../../api"; +import { BEFORE_CREATE_EVENT } from "../../../../../functions/events/v1"; describe("addResourcesToBackend", () => { const oldDefaultRegion = api.functionsDefaultRegion; @@ -360,4 +361,71 @@ describe("addResourcesToBackend", () => { parseTriggers.addResourcesToBackend("project", "nodejs16", trigger, result); expect(result).to.deep.equal(expected); }); + + it("should parse a basic blocking trigger", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + }, + }; + const expected: backend.Backend = { + ...backend.of({ + ...BASIC_ENDPOINT, + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: undefined, + }, + }), + requiredAPIs: [ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions.", + }, + ], + }; + const result = backend.empty(); + + parseTriggers.addResourcesToBackend("project", "nodejs16", trigger, result); + + expect(result).to.deep.equal(expected); + }); + + it("should parse a blocking trigger with options", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: true, + idToken: false, + refreshToken: true, + }, + }, + }; + const expected: backend.Backend = { + ...backend.of({ + ...BASIC_ENDPOINT, + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: true, + idToken: false, + refreshToken: true, + }, + }, + }), + requiredAPIs: [ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions.", + }, + ], + }; + const result = backend.empty(); + + parseTriggers.addResourcesToBackend("project", "nodejs16", trigger, result); + + expect(result).to.deep.equal(expected); + }); }); diff --git a/src/test/deploy/functions/services/auth.spec.ts b/src/test/deploy/functions/services/auth.spec.ts new file mode 100644 index 00000000000..e94d075e342 --- /dev/null +++ b/src/test/deploy/functions/services/auth.spec.ts @@ -0,0 +1,495 @@ +import * as auth from "../../../../deploy/functions/services/auth"; +import * as backend from "../../../../deploy/functions/backend"; +import * as identityPlatform from "../../../../gcp/identityPlatform"; +import * as sinon from "sinon"; +import { expect } from "chai"; +import { BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT } from "../../../../functions/events/v1"; + +const BASE_EP = { + id: "id", + region: "us-east1", + project: "project", + entryPoint: "func", + runtime: "nodejs16", +}; + +const authBlockingService = new auth.AuthBlockingService(); + +describe("authBlocking", () => { + let getConfig: sinon.SinonStub; + let setConfig: sinon.SinonStub; + + beforeEach(() => { + getConfig = sinon + .stub(identityPlatform, "getBlockingFunctionsConfig") + .throws("Unexpected call to getBlockingFunctionsConfig"); + setConfig = sinon + .stub(identityPlatform, "setBlockingFunctionsConfig") + .throws("Unexpected call to setBlockingFunctionsConfig"); + }); + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + describe("validateBlockingTrigger", () => { + it("should throw an error if more than one beforeCreate blocking endpoint", () => { + const ep1: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + id: "id1", + entryPoint: "func1", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + }, + }; + const ep2: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + id: "id2", + entryPoint: "func2", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + }, + }; + + expect(() => authBlockingService.validateTrigger(ep1, backend.of(ep1, ep2))).to.throw( + `Can only create at most one Auth Blocking Trigger for ${BEFORE_CREATE_EVENT} events` + ); + }); + + it("should throw an error if more than one beforeSignIn blocking endpoint", () => { + const ep1: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + id: "id1", + entryPoint: "func1", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + }, + }; + const ep2: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + id: "id2", + entryPoint: "func2", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + }, + }; + + expect(() => authBlockingService.validateTrigger(ep1, backend.of(ep1, ep2))).to.throw( + `Can only create at most one Auth Blocking Trigger for ${BEFORE_SIGN_IN_EVENT} events` + ); + }); + + it("should not throw on valid blocking endpoints", () => { + const ep1: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + id: "id1", + entryPoint: "func1", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: false, + idToken: true, + }, + }, + }; + const ep2: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + id: "id2", + entryPoint: "func2", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + options: { + accessToken: true, + }, + }, + }; + const want: backend.Backend = { + ...backend.of(ep1, ep2), + }; + + expect(() => authBlockingService.validateTrigger(ep1, want)).to.not.throw(); + }); + }); + + describe("registerBlockingTrigger", () => { + it("should handle an empty config", async () => { + const blockingConfig = {}; + const newBlockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "somethingnew.url", + }, + }, + forwardInboundCredentials: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }, + }; + getConfig.resolves(blockingConfig); + setConfig.resolves(newBlockingConfig); + + await authBlockingService.registerTrigger(ep); + + expect(blockingConfig).to.deep.equal(newBlockingConfig); + expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); + }); + + it("should register on a new beforeCreate endpoint", async () => { + const blockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "beforecreate.url", + }, + beforeSignIn: { + functionUri: "beforesignin.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const newBlockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "somethingnew.url", + }, + beforeSignIn: { + functionUri: "beforesignin.url", + }, + }, + forwardInboundCredentials: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }, + }; + getConfig.resolves(blockingConfig); + setConfig.resolves(newBlockingConfig); + + await authBlockingService.registerTrigger(ep); + + expect(blockingConfig).to.deep.equal(newBlockingConfig); + expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); + }); + + it("should register on a new beforeSignIn endpoint", async () => { + const blockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "beforecreate.url", + }, + beforeSignIn: { + functionUri: "beforesignin.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const newBlockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "beforecreate.url", + }, + beforeSignIn: { + functionUri: "somethingnew.url", + }, + }, + forwardInboundCredentials: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + options: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }, + }; + getConfig.resolves(blockingConfig); + setConfig.resolves(newBlockingConfig); + + await authBlockingService.registerTrigger(ep); + + expect(blockingConfig).to.deep.equal(newBlockingConfig); + expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); + }); + + it("should do not set the config if the config is unchanged", async () => { + const blockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "somethingnew.url", + }, + beforeSignIn: { + functionUri: "beforesignin.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: true, + }, + }, + }; + getConfig.resolves(blockingConfig); + + await authBlockingService.registerTrigger(ep); + + expect(setConfig).to.not.have.been.called; + }); + }); + + describe("unregisterBlockingTrigger", () => { + it("should not unregister a beforeCreate endpoint if uri does not match", async () => { + const blockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "beforecreate.url", + }, + beforeSignIn: { + functionUri: "beforesignin.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }, + }; + getConfig.resolves(blockingConfig); + + await authBlockingService.unregisterTrigger(ep); + + expect(setConfig).to.not.have.been.called; + }); + + it("should not unregister a beforeSignIn endpoint if the uri does not match", async () => { + const blockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "beforecreate.url", + }, + beforeSignIn: { + functionUri: "beforesignin.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + options: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }, + }; + getConfig.resolves(blockingConfig); + + await authBlockingService.unregisterTrigger(ep); + + expect(setConfig).to.not.have.been.called; + }); + + it("should unregister a beforeCreate endpoint", async () => { + const blockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "somethingnew.url", + }, + beforeSignIn: { + functionUri: "beforesignin.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const newBlockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeSignIn: { + functionUri: "beforesignin.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }, + }; + getConfig.resolves(blockingConfig); + setConfig.resolves(newBlockingConfig); + + await authBlockingService.unregisterTrigger(ep); + + expect(blockingConfig).to.deep.equal(newBlockingConfig); + expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); + }); + + it("should unregister a beforeSignIn endpoint", async () => { + const blockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "beforecreate.url", + }, + beforeSignIn: { + functionUri: "somethingnew.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const newBlockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "beforecreate.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + options: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }, + }; + getConfig.resolves(blockingConfig); + setConfig.resolves(newBlockingConfig); + + await authBlockingService.unregisterTrigger(ep); + + expect(blockingConfig).to.deep.equal(newBlockingConfig); + expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); + }); + + it("should unregister a beforeSignIn endpoint that was registered to both event types", async () => { + const blockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "somethingnew.url", + }, + beforeSignIn: { + functionUri: "somethingnew.url", + }, + }, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const newBlockingConfig: identityPlatform.BlockingFunctionsConfig = { + triggers: {}, + forwardInboundCredentials: { + accessToken: true, + }, + }; + const ep: backend.Endpoint = { + ...BASE_EP, + platform: "gcfv1", + uri: "somethingnew.url", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + options: { + accessToken: false, + idToken: true, + refreshToken: false, + }, + }, + }; + getConfig.resolves(blockingConfig); + setConfig.resolves(newBlockingConfig); + + await authBlockingService.unregisterTrigger(ep); + + expect(blockingConfig).to.deep.equal(newBlockingConfig); + expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); + }); + }); +}); diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index a474f9afe27..de968066ddb 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -7,6 +7,7 @@ import * as validate from "../../../deploy/functions/validate"; import * as projectPath from "../../../projectPath"; import * as secretManager from "../../../gcp/secretManager"; import * as backend from "../../../deploy/functions/backend"; +import { BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT } from "../../../functions/events/v1"; describe("validate", () => { describe("functionsDirectoryExists", () => { @@ -198,6 +199,101 @@ describe("validate", () => { /they have fewer than 2GB memory/ ); }); + + it("Disallows multiple beforeCreate blocking", () => { + const ep1: backend.Endpoint = { + platform: "gcfv1", + id: "id1", + region: "us-east1", + project: "project", + entryPoint: "func1", + runtime: "nodejs16", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + }, + }; + const ep2: backend.Endpoint = { + platform: "gcfv1", + id: "id2", + region: "us-east1", + project: "project", + entryPoint: "func2", + runtime: "nodejs16", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + }, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep1, ep2))).to.throw( + `Can only create at most one Auth Blocking Trigger for ${BEFORE_CREATE_EVENT} events` + ); + }); + + it("Disallows multiple beforeSignIn blocking", () => { + const ep1: backend.Endpoint = { + platform: "gcfv1", + id: "id1", + region: "us-east1", + project: "project", + entryPoint: "func1", + runtime: "nodejs16", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + }, + }; + const ep2: backend.Endpoint = { + platform: "gcfv1", + id: "id2", + region: "us-east1", + project: "project", + entryPoint: "func2", + runtime: "nodejs16", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + }, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep1, ep2))).to.throw( + `Can only create at most one Auth Blocking Trigger for ${BEFORE_SIGN_IN_EVENT} events` + ); + }); + + it("Allows valid blocking functions", () => { + const ep1: backend.Endpoint = { + platform: "gcfv1", + id: "id1", + region: "us-east1", + project: "project", + entryPoint: "func1", + runtime: "nodejs16", + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + options: { + accessToken: false, + idToken: true, + }, + }, + }; + const ep2: backend.Endpoint = { + platform: "gcfv1", + id: "id2", + region: "us-east1", + project: "project", + entryPoint: "func2", + runtime: "nodejs16", + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + options: { + accessToken: true, + }, + }, + }; + const want: backend.Backend = { + ...backend.of(ep1, ep2), + }; + + expect(() => validate.endpointsAreValid(want)).to.not.throw(); + }); }); describe("secretsAreValid", () => { diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index c560d1c2db5..cf021a69a36 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -4,6 +4,7 @@ import * as nock from "nock"; import { functionsOrigin } from "../../api"; import * as backend from "../../deploy/functions/backend"; +import { BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT } from "../../functions/events/v1"; import * as cloudfunctions from "../../gcp/cloudfunctions"; import * as projectConfig from "../../functions/projectConfig"; @@ -186,6 +187,52 @@ describe("cloudfunctions", () => { ); }); + it("detects beforeCreate blocking functions", () => { + const blockingEndpoint: backend.Endpoint = { + ...ENDPOINT, + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + }, + }; + const blockingFunction: Omit = + { + ...CLOUD_FUNCTION, + sourceUploadUrl: UPLOAD_URL, + httpsTrigger: {}, + labels: { + ...CLOUD_FUNCTION.labels, + [cloudfunctions.BLOCKING_LABEL]: "before-create", + }, + }; + + expect(cloudfunctions.functionFromEndpoint(blockingEndpoint, UPLOAD_URL)).to.deep.equal( + blockingFunction + ); + }); + + it("detects beforeSignIn blocking functions", () => { + const blockingEndpoint: backend.Endpoint = { + ...ENDPOINT, + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + }, + }; + const blockingFunction: Omit = + { + ...CLOUD_FUNCTION, + sourceUploadUrl: UPLOAD_URL, + httpsTrigger: {}, + labels: { + ...CLOUD_FUNCTION.labels, + [cloudfunctions.BLOCKING_LABEL]: "before-sign-in", + }, + }; + + expect(cloudfunctions.functionFromEndpoint(blockingEndpoint, UPLOAD_URL)).to.deep.equal( + blockingFunction + ); + }); + it("should export codebase as label", () => { expect( cloudfunctions.functionFromEndpoint( @@ -298,6 +345,46 @@ describe("cloudfunctions", () => { }); }); + it("should translate beforeCreate blocking triggers", () => { + expect( + cloudfunctions.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION, + httpsTrigger: {}, + labels: { + "deployment-blocking": "before-create", + }, + }) + ).to.deep.equal({ + ...ENDPOINT, + blockingTrigger: { + eventType: BEFORE_CREATE_EVENT, + }, + labels: { + "deployment-blocking": "before-create", + }, + }); + }); + + it("should translate beforeSignIn blocking triggers", () => { + expect( + cloudfunctions.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION, + httpsTrigger: {}, + labels: { + "deployment-blocking": "before-sign-in", + }, + }) + ).to.deep.equal({ + ...ENDPOINT, + blockingTrigger: { + eventType: BEFORE_SIGN_IN_EVENT, + }, + labels: { + "deployment-blocking": "before-sign-in", + }, + }); + }); + it("should copy optional fields", () => { const wantExtraFields: Partial = { availableMemoryMb: 128, diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 0f3a4833e6d..2cd3edd8667 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import * as cloudfunctionsv2 from "../../gcp/cloudfunctionsv2"; import * as backend from "../../deploy/functions/backend"; -import * as v2events from "../../functions/events/v2"; +import * as events from "../../functions/events"; import * as projectConfig from "../../functions/projectConfig"; describe("cloudfunctionsv2", () => { @@ -148,6 +148,44 @@ describe("cloudfunctionsv2", () => { "deployment-taskqueue": "true", }, }); + + expect( + cloudfunctionsv2.functionFromEndpoint( + { + ...ENDPOINT, + platform: "gcfv2", + blockingTrigger: { + eventType: events.v1.BEFORE_CREATE_EVENT, + }, + }, + CLOUD_FUNCTION_V2_SOURCE + ) + ).to.deep.equal({ + ...CLOUD_FUNCTION_V2, + labels: { + ...CLOUD_FUNCTION_V2.labels, + [cloudfunctionsv2.BLOCKING_LABEL]: "before-create", + }, + }); + + expect( + cloudfunctionsv2.functionFromEndpoint( + { + ...ENDPOINT, + platform: "gcfv2", + blockingTrigger: { + eventType: events.v1.BEFORE_SIGN_IN_EVENT, + }, + }, + CLOUD_FUNCTION_V2_SOURCE + ) + ).to.deep.equal({ + ...CLOUD_FUNCTION_V2, + labels: { + ...CLOUD_FUNCTION_V2.labels, + [cloudfunctionsv2.BLOCKING_LABEL]: "before-sign-in", + }, + }); }); it("should copy trival fields", () => { @@ -200,7 +238,7 @@ describe("cloudfunctionsv2", () => { ...ENDPOINT, platform: "gcfv2", eventTrigger: { - eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventType: events.v2.PUBSUB_PUBLISH_EVENT, eventFilters: { topic: "projects/p/topics/t", serviceName: "pubsub.googleapis.com", @@ -219,7 +257,7 @@ describe("cloudfunctionsv2", () => { > = { ...CLOUD_FUNCTION_V2, eventTrigger: { - eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventType: events.v2.PUBSUB_PUBLISH_EVENT, pubsubTopic: "projects/p/topics/t", eventFilters: [ { @@ -272,7 +310,7 @@ describe("cloudfunctionsv2", () => { platform: "gcfv2", uri: RUN_URI, eventTrigger: { - eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventType: events.v2.PUBSUB_PUBLISH_EVENT, eventFilters: { topic: "projects/p/topics/t" }, retry: false, }, @@ -281,7 +319,7 @@ describe("cloudfunctionsv2", () => { cloudfunctionsv2.endpointFromFunction({ ...HAVE_CLOUD_FUNCTION_V2, eventTrigger: { - eventType: v2events.PUBSUB_PUBLISH_EVENT, + eventType: events.v2.PUBSUB_PUBLISH_EVENT, pubsubTopic: "projects/p/topics/t", }, }) @@ -363,6 +401,40 @@ describe("cloudfunctionsv2", () => { }); }); + it("should translate beforeCreate blocking functions", () => { + expect( + cloudfunctionsv2.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION_V2, + labels: { "deployment-blocking": "before-create" }, + }) + ).to.deep.equal({ + ...ENDPOINT, + blockingTrigger: { + eventType: events.v1.BEFORE_CREATE_EVENT, + }, + platform: "gcfv2", + uri: RUN_URI, + labels: { "deployment-blocking": "before-create" }, + }); + }); + + it("should translate beforeSignIn blocking functions", () => { + expect( + cloudfunctionsv2.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION_V2, + labels: { "deployment-blocking": "before-sign-in" }, + }) + ).to.deep.equal({ + ...ENDPOINT, + blockingTrigger: { + eventType: events.v1.BEFORE_SIGN_IN_EVENT, + }, + platform: "gcfv2", + uri: RUN_URI, + labels: { "deployment-blocking": "before-sign-in" }, + }); + }); + it("should copy optional fields", () => { const extraFields: backend.ServiceConfiguration = { ingressSettings: "ALLOW_ALL", From 53b7899084e7cb7fd6b2c1a5973e8c291477614e Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 12 Apr 2022 13:14:36 -0700 Subject: [PATCH 0236/1699] Create Build type with conversion to Backend (#4417) * Implement Build type with conversion to Backend for Functions deploys. This code is currently unreachable in the Functions deploy process. --- src/deploy/functions/backend.ts | 2 +- src/deploy/functions/build.ts | 408 ++++++++++++++++++ .../functions/runtimes/node/parseTriggers.ts | 4 +- 3 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 src/deploy/functions/build.ts diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 91f55cbc00a..8241001189d 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -301,7 +301,7 @@ export type Endpoint = TargetIds & }; export interface RequiredAPI { - reason: string; + reason?: string; api: string; } diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts new file mode 100644 index 00000000000..f40eeb43587 --- /dev/null +++ b/src/deploy/functions/build.ts @@ -0,0 +1,408 @@ +import * as backend from "./backend"; +import * as proto from "../../gcp/proto"; +import * as api from "../../.../../api"; +import { FirebaseError } from "../../error"; +import { assertExhaustive } from "../../functional"; + +/* The union of a customer-controlled deployment and potentially deploy-time defined parameters */ +export interface Build { + requiredAPIs: RequiredApi[]; + endpoints: Record; + params: Param[]; +} + +interface RequiredApi { + // The API that should be enabled. For Google APIs, this should be a googleapis.com subdomain + // (e.g. vision.googleapis.com) + api: string; + + // A reason why this codebase requires this API. + // Will be considered required for all Extensions codebases. Considered optional for Functions + // codebases. + reason?: string; +} + +// Defining a StringParam with { param: "FOO" } declares that "{{ params.FOO }}" is a valid +// Expression elsewhere. +// Expression is always an int. Float Params, list params, and secret params cannot be used in +// expressions. +// `Expression == Expression` is an Expression +// `Expression ? Expression : Expression` is an Expression +type Expression = string; +type Field = T | Expression | null; + +function resolveInt(from: number | Expression | null): number { + if (from == null) { + return 0; + } else if (typeof from === "string") { + throw new FirebaseError("CEL evaluation of expression '" + from + "' not yet supported"); + } + return from; +} + +function resolveString(from: string | Expression | null): string { + if (from == null) { + return ""; + } else if (from.includes("{{") && from.includes("}}")) { + throw new FirebaseError("CEL evaluation of expression '" + from + "' not yet supported"); + } + return from; +} + +function resolveBoolean(from: boolean | Expression | null): boolean { + if (from == null) { + return false; + } else if (typeof from === "string") { + throw new FirebaseError("CEL evaluation of expression '" + from + "' not yet supported"); + } + return from; +} + +// A service account must either: +// 1. Be a project-relative email that ends with "@" (e.g. database-users@) +// 2. Be a well-known shorthand (e..g "public" and "private") +type ServiceAccount = string; + +// Trigger definition for arbitrary HTTPS endpoints +interface HttpsTrigger { + // Which service account should be able to trigger this function. No value means "make public + // on create and don't do anything on update." For more, see go/cf3-http-access-control + invoker?: ServiceAccount | null; +} + +// Trigger definitions for RPCs servers using the HTTP protocol defined at +// https://firebase.google.com/docs/functions/callable-reference +// eslint-disable-next-line +interface CallableTrigger { } + +// Trigger definitions for endpoints that should be called as a delegate for other operations. +// For example, before user login. +interface BlockingTrigger { + eventType: string; +} + +// Trigger definitions for endpoints that listen to CloudEvents emitted by other systems (or legacy +// Google events for GCF gen 1) +interface EventTrigger { + eventType: string; + eventFilters: Record>; + + // whether failed function executions should retry the event execution. + // Retries are indefinite, so developers should be sure to add some end condition (e.g. event + // age) + retry: Field; + + // Region of the EventArc trigger. Must be the same region or multi-region as the event + // trigger or be us-central1. All first party triggers (all triggers as of Jan 2022) need not + // specify this field because tooling determines the correct value automatically. + region?: Field; + + // The service account that EventArc should use to invoke this function. Setting this field + // requires the EventArc P4SA to be granted the "ActAs" permission to this service account and + // will cause the "invoker" role to be granted to this service account on the endpoint + // (Function or Route) + serviceAccount?: ServiceAccount | null; +} + +interface TaskQueueRateLimits { + maxConcurrentDispatches?: Field; + maxDispatchesPerSecond?: Field; +} + +interface TaskQueueRetryConfig { + maxAttempts?: Field; + maxRetryDurationSeconds: Field; + minBackoffSeconds?: Field; + maxBackoffSeconds?: Field; + maxDoublings?: Field; +} + +interface TaskQueueTrigger { + rateLimits?: TaskQueueRateLimits | null; + retryConfig?: TaskQueueRetryConfig | null; + + // empty array means private + invoker?: Array> | null; +} + +interface ScheduleRetryConfig { + retryCount?: Field; + maxRetrySeconds?: Field; + minBackoffSeconds?: Field; + maxBackoffSeconds?: Field; + maxDoublings?: Field; +} + +interface ScheduleTrigger { + schedule: string | Expression; + timeZone: string | Expression; + retryConfig: ScheduleRetryConfig; +} + +type Triggered = + | { httpsTrigger: HttpsTrigger } + | { callableTrigger: CallableTrigger } + | { blockingTrigger: BlockingTrigger } + | { eventTrigger: EventTrigger } + | { scheduleTrigger: ScheduleTrigger } + | { taskQueueTrigger: TaskQueueTrigger }; + +interface VpcSettings { + connector: string | Expression; + egressSettings?: "PRIVATE_RANGES_ONLY" | "ALL_TRAFFIC"; +} + +type Endpoint = Triggered & { + // Defaults to "gcfv2". "Run" will be an additional option defined later + platform?: "gcfv1" | "gcfv2"; + + // Necessary for the GCF API to determine what code to load with the Functions Framework. + // Will become optional once "run" is supported as a platform + entryPoint: string; + + // The services account that this function should run as. Has no effect for a Run service. + // defaults to the GAE service account when a function is first created as a GCF gen 1 function. + // Defaults to the compute service account when a function is first created as a GCF gen 2 function + // or when using Cloud Run. + serviceAccount: ServiceAccount | null; + + // defaults to ["us-central1"], overridable in firebase-tools with + // process.env.FIREBASE_FUNCTIONS_DEFAULT_REGION + region?: string[]; + + // Firebase default of 80. Cloud default of 1 + concurrency?: Field; + + // Default of 256 + availableMemoryMb?: Field; + + // Default of 60 + timeoutSeconds?: Field; + + // Default of 1000 + maxInstances?: Field; + + // Default of 0 + minInstances?: Field; + + vpc?: VpcSettings | null; + ingressSettings?: "ALLOW_ALL" | "ALLOW_INTERNAL_ONLY" | "ALLOW_INTERNAL_AND_GCLB" | null; + + environmentVariables?: Record>; + labels?: Record>; +}; + +interface ParamBase { + // name of the param. Will be exposed as an environment variable with this name + param: string; + + // A human friendly name for the param. Will be used in install/configure flows to describe + // what param is being updated. If omitted, UX will use the value of "param" instead. + label?: string; + + // A long description of the parameter's purpose and allowed values. If omitted, UX will not + // provide a description of the parameter + description?: string; + + // Default value. If not provided, a param must be supplied. + default?: T | Expression; + + // default: false + immutable?: boolean; +} + +interface StringParam extends ParamBase { + type?: "string"; +} + +type Param = StringParam; + +function isMemoryOption(value: backend.MemoryOptions | any): value is backend.MemoryOptions { + return value == null || [128, 256, 512, 1024, 2048, 4096, 8192].includes(value); +} + +/** Converts a build specification into a Backend representation, with all Params resolved and interpolated */ +// TODO(vsfan): resolve build.Params +// TODO(vsfan): handle Expression types +export function resolveBackend(build: Build): backend.Backend { + const bkEndpoints: Array = []; + for (const endpointId of Object.keys(build.endpoints)) { + const endpoint = build.endpoints[endpointId]; + + let regions = endpoint.region; + if (typeof regions === "undefined") { + regions = [api.functionsDefaultRegion]; + } + for (const region of regions) { + const trigger = discoverTrigger(endpoint); + + if (typeof endpoint.platform === "undefined") { + throw new FirebaseError("platform can't be undefined"); + } + if (!isMemoryOption(endpoint.availableMemoryMb)) { + throw new FirebaseError("available memory must be a supported value, if present"); + } + let timeout: number; + if (endpoint.timeoutSeconds) { + timeout = resolveInt(endpoint.timeoutSeconds); + } else { + timeout = 60; + } + + const bkEndpoint: backend.Endpoint = { + id: endpointId, + project: "", + region: region, + entryPoint: endpoint.entryPoint, + platform: endpoint.platform, + runtime: "", + labels: endpoint.labels, + environmentVariables: endpoint.environmentVariables, + secretEnvironmentVariables: undefined, + availableMemoryMb: endpoint.availableMemoryMb, + timeoutSeconds: timeout, + ...trigger, + }; + proto.renameIfPresent(bkEndpoint, endpoint, "maxInstances", "maxInstances", resolveInt); + proto.renameIfPresent(bkEndpoint, endpoint, "minInstances", "minInstances", resolveInt); + proto.renameIfPresent(bkEndpoint, endpoint, "concurrency", "concurrency", resolveInt); + proto.copyIfPresent(bkEndpoint, endpoint, "ingressSettings"); + if (endpoint.vpc) { + bkEndpoint.vpc = { + connector: resolveString(endpoint.vpc.connector), + egressSettings: endpoint.vpc.egressSettings, + }; + } + if (endpoint.serviceAccount) { + bkEndpoint.serviceAccountEmail = endpoint.serviceAccount; + } else { + bkEndpoint.serviceAccountEmail = "default"; + } + + bkEndpoints.push(bkEndpoint); + } + } + + const bkend = backend.of(...bkEndpoints); + bkend.requiredAPIs = build.requiredAPIs; + return bkend; +} + +function discoverTrigger(endpoint: Endpoint): backend.Triggered { + let trigger: backend.Triggered; + if ("httpsTrigger" in endpoint) { + const bkHttps: backend.HttpsTrigger = {}; + if (endpoint.httpsTrigger.invoker) { + bkHttps.invoker = [endpoint.httpsTrigger.invoker]; + } + trigger = { httpsTrigger: bkHttps }; + } else if ("callableTrigger" in endpoint) { + trigger = { callableTrigger: {} }; + } else if ("blockingTrigger" in endpoint) { + throw new FirebaseError("blocking triggers not supported"); + } else if ("eventTrigger" in endpoint) { + const bkEventFilters: Record = {}; + for (const key in endpoint.eventTrigger.eventFilters) { + if (typeof key === "string") { + bkEventFilters[key] = resolveString(endpoint.eventTrigger.eventFilters[key]); + } + } + const bkEvent: backend.EventTrigger = { + eventType: endpoint.eventTrigger.eventType, + eventFilters: bkEventFilters, + retry: resolveBoolean(endpoint.eventTrigger.retry), + }; + if (endpoint.eventTrigger.serviceAccount) { + bkEvent.serviceAccountEmail = endpoint.eventTrigger.serviceAccount; + } + if (endpoint.eventTrigger.region) { + bkEvent.region = resolveString(endpoint.eventTrigger.region); + } + trigger = { eventTrigger: bkEvent }; + } else if ("scheduleTrigger" in endpoint) { + const bkSchedule: backend.ScheduleTrigger = { + schedule: resolveString(endpoint.scheduleTrigger.schedule), + timeZone: resolveString(endpoint.scheduleTrigger.timeZone), + }; + proto.renameIfPresent( + bkSchedule, + endpoint.scheduleTrigger, + "retryConfig", + "retryConfig", + resolveInt + ); + trigger = { scheduleTrigger: bkSchedule }; + } else if ("taskQueueTrigger" in endpoint) { + const bkTaskQueue: backend.TaskQueueTrigger = {}; + if (endpoint.taskQueueTrigger.rateLimits) { + const bkRateLimits: backend.TaskQueueRateLimits = {}; + proto.renameIfPresent( + bkRateLimits, + endpoint.taskQueueTrigger.rateLimits, + "maxConcurrentDispatches", + "maxConcurrentDispatches", + resolveInt + ); + proto.renameIfPresent( + bkRateLimits, + endpoint.taskQueueTrigger.rateLimits, + "maxDispatchesPerSecond", + "maxDispatchesPerSecond", + resolveInt + ); + bkTaskQueue.rateLimits = bkRateLimits; + } + if (endpoint.taskQueueTrigger.retryConfig) { + const bkRetryConfig: backend.TaskQueueRetryConfig = {}; + proto.renameIfPresent( + bkRetryConfig, + endpoint.taskQueueTrigger.retryConfig, + "maxAttempts", + "maxAttempts", + resolveInt + ); + proto.renameIfPresent( + bkRetryConfig, + endpoint.taskQueueTrigger.retryConfig, + "maxBackoffSeconds", + "maxBackoffSeconds", + (from: number | Expression | null): string => { + return proto.durationFromSeconds(resolveInt(from)); + } + ); + proto.renameIfPresent( + bkRetryConfig, + endpoint.taskQueueTrigger.retryConfig, + "minBackoffSeconds", + "minBackoffSeconds", + (from: number | Expression | null): string => { + return proto.durationFromSeconds(resolveInt(from)); + } + ); + proto.renameIfPresent( + bkRetryConfig, + endpoint.taskQueueTrigger.retryConfig, + "maxRetrySeconds", + "maxRetryDurationSeconds", + (from: number | Expression | null): string => { + return proto.durationFromSeconds(resolveInt(from)); + } + ); + proto.renameIfPresent( + bkRetryConfig, + endpoint.taskQueueTrigger.retryConfig, + "maxDoublings", + "maxDoublings", + resolveInt + ); + bkTaskQueue.retryConfig = bkRetryConfig; + } + if (endpoint.taskQueueTrigger.invoker) { + bkTaskQueue.invoker = endpoint.taskQueueTrigger.invoker.map((sa) => resolveString(sa)); + } + trigger = { taskQueueTrigger: bkTaskQueue }; + } else { + assertExhaustive(endpoint); + } + return trigger; +} diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index 463828f5d3d..c887ee4f2d9 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -159,7 +159,9 @@ export function mergeRequiredAPIs(backend: backend.Backend) { const apiToReasons: Record> = {}; for (const { api, reason } of backend.requiredAPIs) { const reasons = apiToReasons[api] || new Set(); - reasons.add(reason); + if (reason) { + reasons.add(reason); + } apiToReasons[api] = reasons; } From a9e237a1bbeec6621373d45d4f4bd6724624a23f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 12 Apr 2022 14:11:28 -0700 Subject: [PATCH 0237/1699] Fix issue where v2 deploys are failing due to bad refactoring. (#4431) I decided to make a small change AFTER getting approval for https://github.com/firebase/firebase-tools/pull/4406. This is a bad idea in general, and espeically bad idea now that I've completely broken v2 function deploys. Here a fix to correctly upload source code for v2 function. --- src/deploy/functions/args.ts | 2 +- src/deploy/functions/deploy.ts | 6 +++--- src/deploy/functions/release/fabricator.ts | 10 +++++----- src/test/deploy/functions/release/fabricator.spec.ts | 5 +---- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/deploy/functions/args.ts b/src/deploy/functions/args.ts index 4f9e1442d69..be11f015c3c 100644 --- a/src/deploy/functions/args.ts +++ b/src/deploy/functions/args.ts @@ -22,7 +22,7 @@ export interface Source { // Filled in the "deploy" phase. sourceUrl?: string; - storage?: Record; + storage?: gcfV2.StorageSource; } // Context holds cached values of what we've looked up in handling this request. diff --git a/src/deploy/functions/deploy.ts b/src/deploy/functions/deploy.ts index e4d36b87054..1b7784fd593 100644 --- a/src/deploy/functions/deploy.ts +++ b/src/deploy/functions/deploy.ts @@ -10,7 +10,6 @@ import * as gcs from "../../gcp/storage"; import * as gcf from "../../gcp/cloudfunctions"; import * as gcfv2 from "../../gcp/cloudfunctionsv2"; import * as backend from "./backend"; -import { FirebaseError } from "../../error"; setGracefulCleanup(); @@ -33,7 +32,7 @@ async function uploadSourceV2(context: args.Context, region: string): Promise e.platform); if (byPlatform.gcfv1.length > 0) { uploads.push(uploadSourceV1(context, byPlatform.gcfv1[0].region)); - } else if (byPlatform.gcfv2.length > 0) { + } + if (byPlatform.gcfv2.length > 0) { uploads.push(uploadSourceV2(context, byPlatform.gcfv2[0].region)); } await Promise.all(uploads); diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 9d83cad1ed5..8059e0c7529 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -50,7 +50,7 @@ export interface FabricatorArgs { sourceUrl?: string; // Required if creating or updating any GCFv2 functions - storage?: Record; + storage?: gcfV2.StorageSource; } const rethrowAs = @@ -64,8 +64,8 @@ const rethrowAs = export class Fabricator { executor: Executor; functionExecutor: Executor; - sourceUrl: string | undefined; - storage: Record | undefined; + sourceUrl?: string; + storage?: gcfV2.StorageSource; appEngineLocation: string; constructor(args: FabricatorArgs) { @@ -269,7 +269,7 @@ export class Fabricator { logger.debug("Precondition failed. Cannot create a GCFv2 function without storage"); throw new Error("Precondition failed"); } - const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage[endpoint.region]); + const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage); // N.B. As of GCFv2 private preview GCF no longer creates Pub/Sub topics // for Pub/Sub event handlers. This may change, at which point this code @@ -393,7 +393,7 @@ export class Fabricator { logger.debug("Precondition failed. Cannot update a GCFv2 function without storage"); throw new Error("Precondition failed"); } - const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage[endpoint.region]); + const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage); // N.B. As of GCFv2 private preview the API chokes on any update call that // includes the pub/sub topic even if that topic is unchanged. diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 447091f1bc2..0c373698024 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -97,10 +97,7 @@ describe("Fabricator", () => { executor: new executor.InlineExecutor(), functionExecutor: new executor.InlineExecutor(), sourceUrl: "https://example.com", - storage: { - "us-central1": storage, - "us-west1": storage, - }, + storage: storage, appEngineLocation: "us-central1", }; let fab: fabricator.Fabricator; From 68f13b58fec7ab1d20fa6308d49f556f58cda49c Mon Sep 17 00:00:00 2001 From: James Daniels Date: Wed, 13 Apr 2022 13:00:38 -0400 Subject: [PATCH 0238/1699] firebase-frameworks integration (#4391) * Add `firebase-frameworks` dep * Require and call out to `firebase-frameworks`, if `hosting.source` option is present * Fix for deploying GCFv2 rewrites for the first time * Remove any rewrites that refer to a gen2 function being deployed before creating the version * Patch the rewrites back in when finalizing the version * Allow developers to refer to CF3v2 functions in rewriting using `function` and `region`, rather than `run: {...}` * Refactor `deploy/index.js` into Typescript, forgive the anys --- npm-shrinkwrap.json | 29 ++++ package.json | 1 + src/commands/deploy.js | 2 +- src/commands/hosting-channel-deploy.ts | 2 +- src/deploy/database/deploy.ts | 2 + src/deploy/database/index.js | 1 + src/deploy/functions/deploy.ts | 4 +- src/deploy/hosting/args.ts | 3 + src/deploy/hosting/convertConfig.ts | 54 ++++++- src/deploy/hosting/prepare.ts | 6 +- src/deploy/hosting/release.ts | 15 +- src/deploy/index.js | 126 --------------- src/deploy/index.ts | 115 +++++++++++++ src/deploy/remoteconfig/deploy.ts | 2 + src/deploy/remoteconfig/index.ts | 3 +- src/previews.ts | 2 + src/serve/index.ts | 12 ++ src/test/deploy/hosting/convertConfig.spec.ts | 152 +++++++++++++++++- 18 files changed, 380 insertions(+), 151 deletions(-) create mode 100644 src/deploy/database/deploy.ts create mode 100644 src/deploy/hosting/args.ts delete mode 100644 src/deploy/index.js create mode 100644 src/deploy/index.ts create mode 100644 src/deploy/remoteconfig/deploy.ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c06fd293387..1a881e68bdf 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -30,6 +30,7 @@ "exit-code": "^1.0.2", "express": "^4.16.4", "filesize": "^6.1.0", + "firebase-frameworks": "^0.3.0", "fs-extra": "^5.0.0", "glob": "^7.1.2", "google-auth-library": "^7.11.0", @@ -6106,6 +6107,19 @@ "@firebase/app-types": "0.6.3" } }, + "node_modules/firebase-frameworks": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.3.0.tgz", + "integrity": "sha512-Zxtx5WsD8ZZdItIeDjjpM+JgaIWDdwBujmLYLKf2Ou6onyRsd8bNRrnVsqrnq4S3FN9TcNYliXdwMu7AwYdW7Q==", + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/firebase-frameworks/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "node_modules/firebase-functions": { "version": "3.18.1", "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.18.1.tgz", @@ -18586,6 +18600,21 @@ } } }, + "firebase-frameworks": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.3.0.tgz", + "integrity": "sha512-Zxtx5WsD8ZZdItIeDjjpM+JgaIWDdwBujmLYLKf2Ou6onyRsd8bNRrnVsqrnq4S3FN9TcNYliXdwMu7AwYdW7Q==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, "firebase-functions": { "version": "3.18.1", "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.18.1.tgz", diff --git a/package.json b/package.json index 0c9c7d30fef..53a4303fcbb 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "exit-code": "^1.0.2", "express": "^4.16.4", "filesize": "^6.1.0", + "firebase-frameworks": "^0.3.0", "fs-extra": "^5.0.0", "glob": "^7.1.2", "google-auth-library": "^7.11.0", diff --git a/src/commands/deploy.js b/src/commands/deploy.js index 30603a0c80b..054b201a7c9 100644 --- a/src/commands/deploy.js +++ b/src/commands/deploy.js @@ -6,7 +6,7 @@ const { requirePermissions } = require("../requirePermissions"); const { checkServiceAccountIam } = require("../deploy/functions/checkIam"); const checkValidTargetFilters = require("../checkValidTargetFilters"); const { Command } = require("../command"); -const deploy = require("../deploy"); +const { deploy } = require("../deploy"); const { requireConfig } = require("../requireConfig"); const { filterTargets } = require("../filterTargets"); const { requireHostingSite } = require("../requireHostingSite"); diff --git a/src/commands/hosting-channel-deploy.ts b/src/commands/hosting-channel-deploy.ts index c19f79b7157..b43bd58f546 100644 --- a/src/commands/hosting-channel-deploy.ts +++ b/src/commands/hosting-channel-deploy.ts @@ -13,7 +13,7 @@ import { } from "../hosting/api"; import { normalizedHostingConfigs } from "../hosting/normalizedHostingConfigs"; import { requirePermissions } from "../requirePermissions"; -import * as deploy from "../deploy"; +import { deploy } from "../deploy"; import { needProjectId } from "../projectUtils"; import { logger } from "../logger"; import { requireConfig } from "../requireConfig"; diff --git a/src/deploy/database/deploy.ts b/src/deploy/database/deploy.ts new file mode 100644 index 00000000000..b1694101a08 --- /dev/null +++ b/src/deploy/database/deploy.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-function +export default async function () {} diff --git a/src/deploy/database/index.js b/src/deploy/database/index.js index 523f4ccfeba..281804a3419 100644 --- a/src/deploy/database/index.js +++ b/src/deploy/database/index.js @@ -3,4 +3,5 @@ module.exports = { prepare: require("./prepare"), release: require("./release"), + deploy: require("./deploy").default, }; diff --git a/src/deploy/functions/deploy.ts b/src/deploy/functions/deploy.ts index 1b7784fd593..127a84f7576 100644 --- a/src/deploy/functions/deploy.ts +++ b/src/deploy/functions/deploy.ts @@ -62,10 +62,10 @@ export async function deploy( // Choose one of the function region for source upload. const byPlatform = groupBy(backend.allEndpoints(want), (e) => e.platform); - if (byPlatform.gcfv1.length > 0) { + if (byPlatform.gcfv1?.length > 0) { uploads.push(uploadSourceV1(context, byPlatform.gcfv1[0].region)); } - if (byPlatform.gcfv2.length > 0) { + if (byPlatform.gcfv2?.length > 0) { uploads.push(uploadSourceV2(context, byPlatform.gcfv2[0].region)); } await Promise.all(uploads); diff --git a/src/deploy/hosting/args.ts b/src/deploy/hosting/args.ts new file mode 100644 index 00000000000..a4cb42460de --- /dev/null +++ b/src/deploy/hosting/args.ts @@ -0,0 +1,3 @@ +import type { Payload as FunctionsPayload } from "../functions/args"; + +export type Payload = FunctionsPayload; diff --git a/src/deploy/hosting/convertConfig.ts b/src/deploy/hosting/convertConfig.ts index b7983cbf6f7..4fc0c6ef782 100644 --- a/src/deploy/hosting/convertConfig.ts +++ b/src/deploy/hosting/convertConfig.ts @@ -1,5 +1,7 @@ import { FirebaseError } from "../../error"; import { HostingConfig, HostingRewrites, HostingHeaders } from "../../firebaseConfig"; +import { existingBackend, allEndpoints, isHttpsTriggered } from "../functions/backend"; +import { Payload } from "./args"; function has(obj: { [k: string]: unknown }, k: string): boolean { return obj[k] !== undefined; @@ -38,7 +40,12 @@ function extractPattern(type: string, spec: HostingRewrites | HostingHeaders): a * convertConfig takes a hosting config object from firebase.json and transforms it into * the valid format for sending to the Firebase Hosting REST API */ -export function convertConfig(config?: HostingConfig): { [k: string]: any } { +export async function convertConfig( + context: any, + payload: Payload, + config: HostingConfig | undefined, + finalize: boolean +): Promise<{ [k: string]: any }> { if (Array.isArray(config)) { throw new FirebaseError(`convertConfig should be given a single configuration, not an array.`, { exit: 2, @@ -50,26 +57,57 @@ export function convertConfig(config?: HostingConfig): { [k: string]: any } { return out; } + const endpointBeingDeployed = (serviceId: string, region: string = "us-central1") => { + const endpoint = payload.functions?.wantBackend?.endpoints[region]?.[serviceId]; + if (endpoint && isHttpsTriggered(endpoint) && endpoint.platform === "gcfv2") return endpoint; + return undefined; + }; + + const matchingEndpoint = async (serviceId: string, region: string = "us-central1") => { + const pendingEndpoint = endpointBeingDeployed(serviceId, region); + if (pendingEndpoint) return pendingEndpoint; + const backend = await existingBackend(context); + return allEndpoints(backend).find( + (it) => + isHttpsTriggered(it) && + it.platform === "gcfv2" && + it.id === serviceId && + it.region === region + ); + }; + // rewrites if (Array.isArray(config.rewrites)) { - out.rewrites = config.rewrites.map((rewrite) => { + out.rewrites = []; + for (const rewrite of config.rewrites) { const vRewrite = extractPattern("rewrite", rewrite); if ("destination" in rewrite) { vRewrite.path = rewrite.destination; } else if ("function" in rewrite) { - vRewrite.function = rewrite.function; - if (rewrite.region) { - vRewrite.functionRegion = rewrite.region; + // Skip these rewrites during hosting prepare + if (!finalize && endpointBeingDeployed(rewrite.function, rewrite.region)) continue; + // Convert function references to GCFv2 to their equivalent run config + // we can't use the already fetched endpoints, since those are scoped to the codebase + const endpoint = await matchingEndpoint(rewrite.function, rewrite.region); + if (endpoint) { + vRewrite.run = { serviceId: endpoint.id, region: endpoint.region }; } else { - vRewrite.functionRegion = "us-central1"; + vRewrite.function = rewrite.function; + if (rewrite.region) { + vRewrite.functionRegion = rewrite.region; + } else { + vRewrite.functionRegion = "us-central1"; + } } } else if ("dynamicLinks" in rewrite) { vRewrite.dynamicLinks = rewrite.dynamicLinks; } else if ("run" in rewrite) { + // Skip these rewrites during hosting prepare + if (!finalize && endpointBeingDeployed(rewrite.run.serviceId, rewrite.run.region)) continue; vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run); } - return vRewrite; - }); + out.rewrites.push(vRewrite); + } } // redirects diff --git a/src/deploy/hosting/prepare.ts b/src/deploy/hosting/prepare.ts index 3bc394d923f..46daa779f04 100644 --- a/src/deploy/hosting/prepare.ts +++ b/src/deploy/hosting/prepare.ts @@ -5,11 +5,13 @@ import { normalizedHostingConfigs } from "../../hosting/normalizedHostingConfigs import { validateDeploy } from "./validate"; import { convertConfig } from "./convertConfig"; import * as deploymentTool from "../../deploymentTool"; +import { Payload } from "./args"; +import { allEndpoints } from "../functions/backend"; /** * Prepare creates versions for each Hosting site to be deployed. */ -export async function prepare(context: any, options: any): Promise { +export async function prepare(context: any, options: any, payload: Payload): Promise { // Allow the public directory to be overridden by the --public flag if (options.public) { if (Array.isArray(options.config.get("hosting"))) { @@ -40,7 +42,7 @@ export async function prepare(context: any, options: any): Promise { validateDeploy(deploy, options); const data = { - config: convertConfig(cfg), + config: await convertConfig(context, payload, cfg, false), labels: deploymentTool.labels(), }; diff --git a/src/deploy/hosting/release.ts b/src/deploy/hosting/release.ts index 5826f05fa98..5bc7c27b862 100644 --- a/src/deploy/hosting/release.ts +++ b/src/deploy/hosting/release.ts @@ -2,11 +2,13 @@ import { client } from "./client"; import { logger } from "../../logger"; import { needProjectNumber } from "../../projectUtils"; import * as utils from "../../utils"; +import { convertConfig } from "./convertConfig"; +import { Payload } from "./args"; /** * Release finalized a Hosting release. */ -export async function release(context: any, options: any): Promise { +export async function release(context: any, options: any, payload: Payload): Promise { if (!context.hosting || !context.hosting.deploys) { return; } @@ -17,11 +19,12 @@ export async function release(context: any, options: any): Promise { await Promise.all( context.hosting.deploys.map(async (deploy: any) => { utils.logLabeledBullet(`hosting[${deploy.site}]`, "finalizing version..."); - const finalizeResult = await client.patch( - `/${deploy.version}`, - { status: "FINALIZED" }, - { queryParams: { updateMask: "status" } } - ); + + const config = await convertConfig(context, payload, deploy.config, true); + const data = { status: "FINALIZED", config }; + const queryParams = { updateMask: "status,config" }; + + const finalizeResult = await client.patch(`/${deploy.version}`, data, { queryParams }); logger.debug(`[hosting] finalized version for ${deploy.site}:${finalizeResult.body}`); utils.logLabeledSuccess(`hosting[${deploy.site}]`, "version finalized"); diff --git a/src/deploy/index.js b/src/deploy/index.js deleted file mode 100644 index c424868fbe8..00000000000 --- a/src/deploy/index.js +++ /dev/null @@ -1,126 +0,0 @@ -"use strict"; - -const { logger } = require("../logger"); -var api = require("../api"); -var clc = require("cli-color"); -var _ = require("lodash"); -var needProjectId = require("../projectUtils").needProjectId; -var utils = require("../utils"); -var { FirebaseError } = require("../error"); -var track = require("../track"); -var lifecycleHooks = require("./lifecycleHooks"); - -var TARGETS = { - hosting: require("./hosting"), - database: require("./database"), - firestore: require("./firestore"), - functions: require("./functions"), - storage: require("./storage"), - remoteconfig: require("./remoteconfig"), - extensions: require("./extensions"), -}; - -var _noop = function () { - return Promise.resolve(); -}; - -var _chain = function (fns, context, options, payload) { - var latest = (fns.shift() || _noop)(context, options, payload); - if (fns.length) { - return latest.then(function () { - return _chain(fns, context, options, payload); - }); - } - - return latest; -}; - -/** - * The `deploy()` function runs through a three step deploy process for a listed - * number of deploy targets. This allows deploys to be done all together or - * for individual deployable elements to be deployed as such. - */ -var deploy = function (targetNames, options, customContext = {}) { - var projectId = needProjectId(options); - var payload = {}; - // a shared context object for deploy targets to decorate as needed - /** @type {object} */ - var context = Object.assign({ projectId }, customContext); - var predeploys = []; - var prepares = []; - var deploys = []; - var releases = []; - var postdeploys = []; - var startTime = Date.now(); - - for (var i = 0; i < targetNames.length; i++) { - var targetName = targetNames[i]; - var target = TARGETS[targetName]; - - if (!target) { - return Promise.reject( - new FirebaseError(clc.bold(targetName) + " is not a valid deploy target", { exit: 1 }) - ); - } - - predeploys.push(lifecycleHooks(targetName, "predeploy")); - if (target.prepare) { - prepares.push(target.prepare); - } - if (target.deploy) { - deploys.push(target.deploy); - } - if (target.release) { - releases.push(target.release); - } - postdeploys.push(lifecycleHooks(targetName, "postdeploy")); - } - - logger.info(); - logger.info(clc.bold(clc.white("===") + " Deploying to '" + projectId + "'...")); - logger.info(); - - utils.logBullet("deploying " + clc.bold(targetNames.join(", "))); - - return _chain(predeploys, context, options, payload) - .then(function () { - return _chain(prepares, context, options, payload); - }) - .then(function () { - return _chain(deploys, context, options, payload); - }) - .then(function () { - return _chain(releases, context, options, payload); - }) - .then(function () { - return _chain(postdeploys, context, options, payload); - }) - .then(function () { - if (_.has(options, "config.notes.databaseRules")) { - return track("Rules Deploy", options.config.notes.databaseRules); - } - return; - }) - .then(function () { - const duration = Date.now() - startTime; - return track("Product Deploy", [...targetNames].sort().join(","), duration); - }) - .then(function () { - logger.info(); - utils.logSuccess(clc.underline.bold("Deploy complete!")); - logger.info(); - var deployedHosting = _.includes(targetNames, "hosting"); - logger.info(clc.bold("Project Console:"), utils.consoleUrl(options.project, "/overview")); - if (deployedHosting) { - _.each(context.hosting.deploys, function (deploy) { - logger.info(clc.bold("Hosting URL:"), utils.addSubdomain(api.hostingOrigin, deploy.site)); - }); - const versionNames = context.hosting.deploys.map((deploy) => deploy.version); - return { hosting: versionNames.length === 1 ? versionNames[0] : versionNames }; - } - }); -}; - -deploy.TARGETS = TARGETS; - -module.exports = deploy; diff --git a/src/deploy/index.ts b/src/deploy/index.ts new file mode 100644 index 00000000000..d15667438c0 --- /dev/null +++ b/src/deploy/index.ts @@ -0,0 +1,115 @@ +import { logger } from "../logger"; +import { hostingOrigin } from "../api"; +import { bold, white } from "cli-color"; +import { has, includes, each } from "lodash"; +import { needProjectId } from "../projectUtils"; +import { logBullet, logSuccess, consoleUrl, addSubdomain } from "../utils"; +import { FirebaseError } from "../error"; +import * as track from "../track"; +import * as lifecycleHooks from "./lifecycleHooks"; +import { previews } from "../previews"; +import * as HostingTarget from "./hosting"; +import * as DatabaseTarget from "./database"; +import * as FirestoreTarget from "./firestore"; +import * as FunctionsTarget from "./functions"; +import * as StorageTarget from "./storage"; +import * as RemoteConfigTarget from "./remoteconfig"; +import * as ExtensionsTarget from "./extensions"; + +const TARGETS = { + hosting: HostingTarget, + database: DatabaseTarget, + firestore: FirestoreTarget, + functions: FunctionsTarget, + storage: StorageTarget, + remoteconfig: RemoteConfigTarget, + extensions: ExtensionsTarget, +}; + +type Chain = ((context: any, options: any, payload: any) => Promise)[]; + +const chain = async function (fns: Chain, context: any, options: any, payload: any): Promise { + for (const latest of fns) { + await latest(context, options, payload); + } +}; + +/** + * The `deploy()` function runs through a three step deploy process for a listed + * number of deploy targets. This allows deploys to be done all together or + * for individual deployable elements to be deployed as such. + */ +export const deploy = async function ( + targetNames: (keyof typeof TARGETS)[], + options: any, + customContext = {} +) { + const projectId = needProjectId(options); + const payload = {}; + // a shared context object for deploy targets to decorate as needed + const context: any = Object.assign({ projectId }, customContext); + const predeploys: Chain = []; + const prepares: Chain = []; + const deploys: Chain = []; + const releases: Chain = []; + const postdeploys: Chain = []; + const startTime = Date.now(); + + if (previews.frameworkawareness && targetNames.includes("hosting")) { + const config = options.config.get("hosting"); + if (Array.isArray(config) ? config.some((it) => it.source) : config.source) { + await require("firebase-frameworks").prepare(targetNames, context, options); + } + } + + for (const targetName of targetNames) { + const target = TARGETS[targetName]; + + if (!target) { + return Promise.reject( + new FirebaseError(bold(targetName) + " is not a valid deploy target", { exit: 1 }) + ); + } + + predeploys.push(lifecycleHooks(targetName, "predeploy")); + prepares.push(target.prepare); + deploys.push(target.deploy); + releases.push(target.release); + postdeploys.push(lifecycleHooks(targetName, "postdeploy")); + } + + logger.info(); + logger.info(bold(white("===") + " Deploying to '" + projectId + "'...")); + logger.info(); + + logBullet("deploying " + bold(targetNames.join(", "))); + + await chain(predeploys, context, options, payload); + await chain(prepares, context, options, payload); + await chain(deploys, context, options, payload); + await chain(releases, context, options, payload); + await chain(postdeploys, context, options, payload); + + if (has(options, "config.notes.databaseRules")) { + await track("Rules Deploy", options.config.notes.databaseRules); + } + + const duration = Date.now() - startTime; + await track("Product Deploy", [...targetNames].sort().join(","), duration); + + logger.info(); + logSuccess(bold.underline("Deploy complete!")); + logger.info(); + + const deployedHosting = includes(targetNames, "hosting"); + logger.info(bold("Project Console:"), consoleUrl(options.project, "/overview")); + if (deployedHosting) { + each(context.hosting.deploys, (deploy) => { + logger.info(bold("Hosting URL:"), addSubdomain(hostingOrigin, deploy.site)); + }); + const versionNames = context.hosting.deploys.map((deploy: any) => deploy.version); + return { hosting: versionNames.length === 1 ? versionNames[0] : versionNames }; + } else { + return { hosting: undefined }; + } +}; diff --git a/src/deploy/remoteconfig/deploy.ts b/src/deploy/remoteconfig/deploy.ts new file mode 100644 index 00000000000..b1694101a08 --- /dev/null +++ b/src/deploy/remoteconfig/deploy.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-function +export default async function () {} diff --git a/src/deploy/remoteconfig/index.ts b/src/deploy/remoteconfig/index.ts index 4f0e9f523e0..880f9708226 100644 --- a/src/deploy/remoteconfig/index.ts +++ b/src/deploy/remoteconfig/index.ts @@ -1,4 +1,5 @@ import prepare from "./prepare"; import release from "./release"; +import deploy from "./deploy"; -export { prepare, release }; +export { prepare, release, deploy }; diff --git a/src/previews.ts b/src/previews.ts index 81589dda6dc..013d65414b0 100644 --- a/src/previews.ts +++ b/src/previews.ts @@ -12,6 +12,7 @@ interface PreviewFlags { deletegcfartifacts: boolean; artifactregistry: boolean; emulatoruisnapshot: boolean; + frameworkawareness: boolean; } export const previews: PreviewFlags = { @@ -26,6 +27,7 @@ export const previews: PreviewFlags = { deletegcfartifacts: false, artifactregistry: false, emulatoruisnapshot: false, + frameworkawareness: false, ...(configstore.get("previews") as Partial), }; diff --git a/src/serve/index.ts b/src/serve/index.ts index 79699a80bb9..9c853779735 100644 --- a/src/serve/index.ts +++ b/src/serve/index.ts @@ -1,6 +1,7 @@ import { EmulatorServer } from "../emulator/emulatorServer"; import * as _ from "lodash"; import { logger } from "../logger"; +import { previews } from "../previews"; const { FunctionsServer } = require("./functions"); @@ -20,6 +21,17 @@ const TARGETS: { export async function serve(options: any): Promise { const targetNames = options.targets; options.port = parseInt(options.port, 10); + if ( + previews.frameworkawareness && + targetNames.includes("hosting") && + [].concat(options.config.get("hosting")).some((it: any) => it.source) + ) { + await require("firebase-frameworks").prepare( + targetNames, + { project: options.projectId }, + options + ); + } await Promise.all( _.map(targetNames, (targetName: string) => { return TARGETS[targetName].start(options); diff --git a/src/test/deploy/hosting/convertConfig.spec.ts b/src/test/deploy/hosting/convertConfig.spec.ts index b6fd9f7f906..8ad16275447 100644 --- a/src/test/deploy/hosting/convertConfig.spec.ts +++ b/src/test/deploy/hosting/convertConfig.spec.ts @@ -3,8 +3,24 @@ import { HostingConfig } from "../../../firebaseConfig"; import { convertConfig } from "../../../deploy/hosting/convertConfig"; +const DEFAULT_CONTEXT = { + loadedExistingBackend: true, + existingBackend: { + endpoints: {}, + }, +}; + +const DEFAULT_PAYLOAD = {}; + describe("convertConfig", () => { - const tests: Array<{ name: string; input: HostingConfig | undefined; want: any }> = [ + const tests: Array<{ + name: string; + input: HostingConfig | undefined; + want: any; + payload?: any; + finalize?: boolean; + context?: any; + }> = [ { name: "returns nothing if no config is provided", input: undefined, @@ -36,6 +52,82 @@ describe("convertConfig", () => { input: { rewrites: [{ regex: "/foo$", function: "foofn", region: "us-central1" }] }, want: { rewrites: [{ regex: "/foo$", function: "foofn", functionRegion: "us-central1" }] }, }, + { + name: "skips functions referencing CF3v2 functions being deployed (during prepare)", + input: { rewrites: [{ regex: "/foo$", function: "foofn", region: "us-central1" }] }, + payload: { + functions: { + wantBackend: { + endpoints: { + "us-central1": { + foofn: { + id: "foofn", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: true, + }, + }, + }, + }, + }, + }, + want: { rewrites: [] }, + finalize: false, + }, + { + name: "rewrites referencing CF3v2 functions being deployed are changed to Cloud Run (during release)", + input: { rewrites: [{ regex: "/foo$", function: "foofn", region: "us-central1" }] }, + payload: { + functions: { + wantBackend: { + endpoints: { + "us-central1": { + foofn: { + id: "foofn", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: true, + }, + }, + }, + }, + }, + }, + want: { rewrites: [{ regex: "/foo$", run: { serviceId: "foofn", region: "us-central1" } }] }, + finalize: true, + }, + { + name: "rewrites referencing existing CF3v2 functions are changed to Cloud Run (during prepare)", + input: { rewrites: [{ regex: "/foo$", function: "foofn", region: "us-central1" }] }, + context: { + loadedExistingBackend: true, + existingBackend: { + endpoints: { + "us-central1": { + foofn: { id: "foofn", region: "us-central1", platform: "gcfv2", httpsTrigger: true }, + }, + }, + }, + }, + want: { rewrites: [{ regex: "/foo$", run: { serviceId: "foofn", region: "us-central1" } }] }, + finalize: true, + }, + { + name: "rewrites referencing existing CF3v2 functions are changed to Cloud Run (during release)", + input: { rewrites: [{ regex: "/foo$", function: "foofn", region: "us-central1" }] }, + context: { + loadedExistingBackend: true, + existingBackend: { + endpoints: { + "us-central1": { + foofn: { id: "foofn", region: "us-central1", platform: "gcfv2", httpsTrigger: true }, + }, + }, + }, + }, + want: { rewrites: [{ regex: "/foo$", run: { serviceId: "foofn", region: "us-central1" } }] }, + finalize: true, + }, { name: "returns rewrites for glob Run", input: { rewrites: [{ glob: "/foo", run: { serviceId: "hello" } }] }, @@ -46,6 +138,50 @@ describe("convertConfig", () => { input: { rewrites: [{ regex: "/foo$", run: { serviceId: "hello" } }] }, want: { rewrites: [{ regex: "/foo$", run: { region: "us-central1", serviceId: "hello" } }] }, }, + { + name: "skips rewrites for Cloud Run instances being deployed (during prepare)", + input: { rewrites: [{ regex: "/foo$", run: { serviceId: "hello" } }] }, + want: { rewrites: [] }, + payload: { + functions: { + wantBackend: { + endpoints: { + "us-central1": { + hello: { + id: "hello", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: true, + }, + }, + }, + }, + }, + }, + finalize: false, + }, + { + name: "return rewrites for Cloud Run instances being deployed (during release)", + input: { rewrites: [{ regex: "/foo$", run: { serviceId: "hello" } }] }, + want: { rewrites: [{ regex: "/foo$", run: { region: "us-central1", serviceId: "hello" } }] }, + payload: { + functions: { + wantBackend: { + endpoints: { + "us-central1": { + hello: { + id: "hello", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: true, + }, + }, + }, + }, + }, + }, + finalize: true, + }, { name: "returns rewrites for Run with specified regions", input: { rewrites: [{ glob: "/foo", run: { serviceId: "hello", region: "us-midwest" } }] }, @@ -149,9 +285,17 @@ describe("convertConfig", () => { }, ]; - for (const test of tests) { - it(test.name, () => { - expect(convertConfig(test.input)).to.deep.equal(test.want); + for (const { + name, + context = DEFAULT_CONTEXT, + input, + payload = DEFAULT_PAYLOAD, + want, + finalize = true, + } of tests) { + it(name, async () => { + const config = await convertConfig(context, payload, input, finalize); + expect(config).to.deep.equal(want); }); } }); From 70495e121f038233dc93afc83210afa48c164222 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 13 Apr 2022 13:53:53 -0700 Subject: [PATCH 0239/1699] Convert track.js to TS. (#4439) * Rename track.js to ts. * Convert track.js to TypeScript. * Update track imports. We're on track. * Get lodash off the track. * Remove unattractive double track. * Track the tests too. * Fix require statements. * Fix linting issues. --- npm-shrinkwrap.json | 13 +++++ package.json | 1 + src/command.ts | 2 +- src/deploy/functions/checkIam.ts | 2 +- src/deploy/functions/ensure.ts | 4 +- src/deploy/functions/release/reporter.ts | 22 ++++----- .../node/parseRuntimeAndValidateSDK.ts | 2 +- .../functions/runtimes/node/versioning.ts | 2 +- src/deploy/hosting/deploy.ts | 2 +- src/deploy/index.ts | 2 +- src/emulator/controller.ts | 2 +- src/emulator/functionsEmulator.ts | 2 +- src/ensureApiEnabled.ts | 2 +- src/extensions/paramHelper.ts | 2 +- src/track.js | 48 ------------------- src/track.ts | 33 +++++++++++++ 16 files changed, 70 insertions(+), 71 deletions(-) delete mode 100644 src/track.js create mode 100644 src/track.ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 1a881e68bdf..0f9ca032a9d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -115,6 +115,7 @@ "@types/tcp-port-used": "^1.0.0", "@types/tmp": "^0.1.0", "@types/triple-beam": "^1.3.0", + "@types/universal-analytics": "^0.4.5", "@types/unzipper": "^0.10.0", "@types/uuid": "^8.3.1", "@types/ws": "^7.2.3", @@ -2478,6 +2479,12 @@ "integrity": "sha512-tl34wMtk3q+fSdRSJ+N83f47IyXLXPPuLjHm7cmAx0fE2Wml2TZCQV3FmQdSR5J6UEGV3qafG054e0cVVFCqPA==", "dev": true }, + "node_modules/@types/universal-analytics": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@types/universal-analytics/-/universal-analytics-0.4.5.tgz", + "integrity": "sha512-Opb+Un786PS3te24VtJR/QPmX00P/pXaJQtLQYJklQefP4xP0Ic3mPc2z6SDz97OrITzR+RHTBEwjtNRjZ/nLQ==", + "dev": true + }, "node_modules/@types/unzipper": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.0.tgz", @@ -15815,6 +15822,12 @@ "integrity": "sha512-tl34wMtk3q+fSdRSJ+N83f47IyXLXPPuLjHm7cmAx0fE2Wml2TZCQV3FmQdSR5J6UEGV3qafG054e0cVVFCqPA==", "dev": true }, + "@types/universal-analytics": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@types/universal-analytics/-/universal-analytics-0.4.5.tgz", + "integrity": "sha512-Opb+Un786PS3te24VtJR/QPmX00P/pXaJQtLQYJklQefP4xP0Ic3mPc2z6SDz97OrITzR+RHTBEwjtNRjZ/nLQ==", + "dev": true + }, "@types/unzipper": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.0.tgz", diff --git a/package.json b/package.json index 53a4303fcbb..92829f5ae4e 100644 --- a/package.json +++ b/package.json @@ -188,6 +188,7 @@ "@types/tcp-port-used": "^1.0.0", "@types/tmp": "^0.1.0", "@types/triple-beam": "^1.3.0", + "@types/universal-analytics": "^0.4.5", "@types/unzipper": "^0.10.0", "@types/uuid": "^8.3.1", "@types/ws": "^7.2.3", diff --git a/src/command.ts b/src/command.ts index 2370a368d9b..c459d0b4c5a 100644 --- a/src/command.ts +++ b/src/command.ts @@ -8,7 +8,7 @@ import { loadRC, RC } from "./rc"; import { Config } from "./config"; import { configstore } from "./configstore"; import { detectProjectRoot } from "./detectProjectRoot"; -import track = require("./track"); +import { track } from "./track"; import clc = require("cli-color"); import { selectAccount, setActiveAccount } from "./auth"; import { getFirebaseProject } from "./management/projects"; diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index aa245edbac6..c1b970fcb7e 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -6,7 +6,7 @@ import { FirebaseError } from "../../error"; import * as iam from "../../gcp/iam"; import * as args from "./args"; import * as backend from "./backend"; -import * as track from "../../track"; +import { track } from "../../track"; import * as utils from "../../utils"; import { Options } from "../../options"; diff --git a/src/deploy/functions/ensure.ts b/src/deploy/functions/ensure.ts index 23bd8d1e541..cce2d713850 100644 --- a/src/deploy/functions/ensure.ts +++ b/src/deploy/functions/ensure.ts @@ -7,7 +7,7 @@ import { ensureServiceAgentRole } from "../../gcp/secretManager"; import { previews } from "../../previews"; import { getFirebaseProject } from "../../management/projects"; import { assertExhaustive } from "../../functional"; -import * as track from "../../track"; +import { track } from "../../track"; import * as backend from "./backend"; import * as ensureApiEnabled from "../../ensureApiEnabled"; @@ -33,7 +33,7 @@ function nodeBillingError(projectId: string): FirebaseError { void track("functions_runtime_notices", "nodejs10_billing_error"); return new FirebaseError( `Cloud Functions deployment requires the pay-as-you-go (Blaze) billing plan. To upgrade your project, visit the following URL: - + https://console.firebase.google.com/project/${projectId}/usage/details For additional information about this requirement, see Firebase FAQs: diff --git a/src/deploy/functions/release/reporter.ts b/src/deploy/functions/release/reporter.ts index d05351f1957..e604cb15419 100644 --- a/src/deploy/functions/release/reporter.ts +++ b/src/deploy/functions/release/reporter.ts @@ -2,7 +2,7 @@ import * as backend from "../backend"; import * as clc from "cli-color"; import { logger } from "../../../logger"; -import * as track from "../../../track"; +import { track } from "../../../track"; import * as utils from "../../../utils"; import { getFunctionLabel } from "../functionsDeployHelper"; @@ -68,23 +68,23 @@ export async function logAndTrackDeployStats(summary: Summary): Promise { totalTime += result.durationMs; if (!result.error) { totalSuccesses++; - reports.push(track.track("function_deploy_success", tag, result.durationMs)); + reports.push(track("function_deploy_success", tag, result.durationMs)); } else if (result.error instanceof AbortedDeploymentError) { totalAborts++; - reports.push(track.track("function_deploy_abort", tag, result.durationMs)); + reports.push(track("function_deploy_abort", tag, result.durationMs)); } else { totalErrors++; - reports.push(track.track("function_deploy_failure", tag, result.durationMs)); + reports.push(track("function_deploy_failure", tag, result.durationMs)); } } const regionCountTag = regions.size < 5 ? regions.size.toString() : ">=5"; - reports.push(track.track("functions_region_count", regionCountTag, 1)); + reports.push(track("functions_region_count", regionCountTag, 1)); const gcfv1 = summary.results.find((r) => r.endpoint.platform === "gcfv1"); const gcfv2 = summary.results.find((r) => r.endpoint.platform === "gcfv2"); const tag = gcfv1 && gcfv2 ? "v1+v2" : gcfv1 ? "v1" : "v2"; - reports.push(track.track("functions_codebase_deploy", tag, summary.results.length)); + reports.push(track("functions_codebase_deploy", tag, summary.results.length)); const avgTime = totalTime / (totalSuccesses + totalErrors); @@ -95,19 +95,19 @@ export async function logAndTrackDeployStats(summary: Summary): Promise { logger.debug(`Average Function Deployment time: ${avgTime}`); if (totalErrors + totalSuccesses > 0) { if (totalErrors === 0) { - reports.push(track.track("functions_deploy_result", "success", totalSuccesses)); + reports.push(track("functions_deploy_result", "success", totalSuccesses)); } else if (totalSuccesses > 0) { - reports.push(track.track("functions_deploy_result", "partial_success", totalSuccesses)); - reports.push(track.track("functions_deploy_result", "partial_failure", totalErrors)); + reports.push(track("functions_deploy_result", "partial_success", totalSuccesses)); + reports.push(track("functions_deploy_result", "partial_failure", totalErrors)); reports.push( - track.track( + track( "functions_deploy_result", "partial_error_ratio", totalErrors / (totalSuccesses + totalErrors) ) ); } else { - reports.push(track.track("functions_deploy_result", "failure", totalErrors)); + reports.push(track("functions_deploy_result", "failure", totalErrors)); } } diff --git a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts index 8d44e09ce7d..afa3f1b810d 100644 --- a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts +++ b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts @@ -3,7 +3,7 @@ import * as path from "path"; import * as clc from "cli-color"; import { FirebaseError } from "../../../../error"; -import * as track from "../../../../track"; +import { track } from "../../../../track"; import * as runtimes from "../../runtimes"; // have to require this because no @types/cjson available diff --git a/src/deploy/functions/runtimes/node/versioning.ts b/src/deploy/functions/runtimes/node/versioning.ts index d457d3801cd..db5b946ce84 100644 --- a/src/deploy/functions/runtimes/node/versioning.ts +++ b/src/deploy/functions/runtimes/node/versioning.ts @@ -6,7 +6,7 @@ import * as spawn from "cross-spawn"; import * as utils from "../../../../utils"; import { logger } from "../../../../logger"; -import * as track from "../../../../track"; +import { track } from "../../../../track"; interface NpmListResult { name: string; diff --git a/src/deploy/hosting/deploy.ts b/src/deploy/hosting/deploy.ts index d422a15f617..a645a577927 100644 --- a/src/deploy/hosting/deploy.ts +++ b/src/deploy/hosting/deploy.ts @@ -2,7 +2,7 @@ import { Uploader } from "./uploader"; import { detectProjectRoot } from "../../detectProjectRoot"; import { listFiles } from "../../listFiles"; import { logger } from "../../logger"; -import * as track from "../../track"; +import { track } from "../../track"; import { envOverride, logLabeledBullet, logLabeledSuccess } from "../../utils"; import { HostingDeploy } from "./hostingDeploy"; diff --git a/src/deploy/index.ts b/src/deploy/index.ts index d15667438c0..24a9ceb969e 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -5,7 +5,7 @@ import { has, includes, each } from "lodash"; import { needProjectId } from "../projectUtils"; import { logBullet, logSuccess, consoleUrl, addSubdomain } from "../utils"; import { FirebaseError } from "../error"; -import * as track from "../track"; +import { track } from "../track"; import * as lifecycleHooks from "./lifecycleHooks"; import { previews } from "../previews"; import * as HostingTarget from "./hosting"; diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 9f9064fd01e..0036563d235 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -4,7 +4,7 @@ import * as fs from "fs"; import * as path from "path"; import { logger } from "../logger"; -import * as track from "../track"; +import { track } from "../track"; import * as utils from "../utils"; import { EmulatorRegistry } from "./registry"; import { diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index fc8fdff16cf..be91a127126 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -13,7 +13,7 @@ import { EventEmitter } from "events"; import { Account } from "../auth"; import * as api from "../api"; import { logger } from "../logger"; -import * as track from "../track"; +import { track } from "../track"; import { Constants } from "./constants"; import { EmulatorInfo, diff --git a/src/ensureApiEnabled.ts b/src/ensureApiEnabled.ts index c086a8f558e..7fcaac1743c 100644 --- a/src/ensureApiEnabled.ts +++ b/src/ensureApiEnabled.ts @@ -1,6 +1,6 @@ import { bold } from "cli-color"; -import * as track from "./track"; +import { track } from "./track"; import { serviceUsageOrigin } from "./api"; import { Client } from "./apiv2"; import * as utils from "./utils"; diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 637e918124a..8bffbf3a1e5 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -12,7 +12,7 @@ import { validateCommandLineParams, } from "./extensionsHelper"; import * as askUserForParam from "./askUserForParam"; -import * as track from "../track"; +import { track } from "../track"; import * as env from "../functions/env"; import { cloneDeep } from "../utils"; diff --git a/src/track.js b/src/track.js deleted file mode 100644 index d52a298d71f..00000000000 --- a/src/track.js +++ /dev/null @@ -1,48 +0,0 @@ -"use strict"; - -var ua = require("universal-analytics"); - -var _ = require("lodash"); -var { configstore } = require("./configstore"); -var pkg = require("../package.json"); -var uuid = require("uuid"); -const { logger } = require("./logger"); - -var anonId = configstore.get("analytics-uuid"); -if (!anonId) { - anonId = uuid.v4(); - configstore.set("analytics-uuid", anonId); -} - -var visitor = ua(process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3", anonId, { - strictCidFormat: false, - https: true, -}); - -visitor.set("cd1", process.platform); // Platform -visitor.set("cd2", process.version); // NodeVersion -visitor.set("cd3", process.env.FIREPIT_VERSION || "none"); // FirepitVersion - -function track(action, label, duration) { - return new Promise(function (resolve) { - if (!_.isString(action) || !_.isString(label)) { - logger.debug("track received non-string arguments:", action, label); - resolve(); - } - duration = duration || 0; - - if (configstore.get("tokens") && configstore.get("usage")) { - visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(function () { - // we could handle errors here, but we won't - resolve(); - }); - } else { - resolve(); - } - }); -} - -// New code should import track by name so that it can be stubbed -// in unit tests. Legacy code still imports as default. -track.track = track; -module.exports = track; diff --git a/src/track.ts b/src/track.ts new file mode 100644 index 00000000000..37fd7cdfcd5 --- /dev/null +++ b/src/track.ts @@ -0,0 +1,33 @@ +import * as ua from "universal-analytics"; +import { v4 as uuidV4 } from "uuid"; + +import { configstore } from "./configstore"; +const pkg = require("../package.json"); + +let anonId = configstore.get("analytics-uuid"); +if (!anonId) { + anonId = uuidV4(); + configstore.set("analytics-uuid", anonId); +} + +const visitor = ua(process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3", anonId, { + strictCidFormat: false, + https: true, +}); + +visitor.set("cd1", process.platform); // Platform +visitor.set("cd2", process.version); // NodeVersion +visitor.set("cd3", process.env.FIREPIT_VERSION || "none"); // FirepitVersion + +export function track(action: string, label: string, duration: number = 0): Promise { + return new Promise((resolve) => { + if (configstore.get("tokens") && configstore.get("usage")) { + visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(() => { + // we could handle errors here, but we won't + resolve(); + }); + } else { + resolve(); + } + }); +} From 4279cd82cce5f4e5fdd6963be3ac61b0823fbe83 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 13 Apr 2022 15:44:44 -0700 Subject: [PATCH 0240/1699] I guess this happens after all (#3889) Co-authored-by: Bryan Kendall --- src/deploy/functions/release/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index ff470b66cd7..1890350ce00 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -131,7 +131,9 @@ export function printTriggerUrls(results: backend.Backend): void { for (const httpsFunc of httpsFunctions) { if (!httpsFunc.uri) { - logger.debug("Missing URI for HTTPS function in printTriggerUrls. This shouldn't happen"); + logger.debug( + "Not printing URL for HTTPS function. Typically this means it didn't match a filter or we failed deployment" + ); continue; } logger.info(clc.bold("Function URL"), `(${getFunctionLabel(httpsFunc)}):`, httpsFunc.uri); From 1b33bbc85168c1ff7dbaf60150c86a1e3f52639f Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 14 Apr 2022 11:03:08 -0700 Subject: [PATCH 0241/1699] Add CPU option (#4435) * Add CPU option and improve schema validation. * PR feedback Co-authored-by: Daniel Lee --- src/deploy/functions/backend.ts | 13 ++++- .../functions/runtimes/discovery/parsing.ts | 48 ++++++++++++++----- .../functions/runtimes/discovery/v1alpha1.ts | 14 ++++-- .../runtimes/discovery/parsing.spec.ts | 27 ++++++++++- .../runtimes/discovery/v1alpha1.spec.ts | 1 + 5 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 8241001189d..374304678be 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -3,7 +3,6 @@ import * as gcf from "../../gcp/cloudfunctions"; import * as gcfV2 from "../../gcp/cloudfunctionsv2"; import * as utils from "../../utils"; import * as runtimes from "./runtimes"; -import * as events from "../../functions/events"; import { FirebaseError } from "../../error"; import { Context } from "./args"; import { previews } from "../../previews"; @@ -162,8 +161,15 @@ export function endpointTriggerType(endpoint: Endpoint): string { // TODO(inlined): Enum types should be singularly named export type VpcEgressSettings = "PRIVATE_RANGES_ONLY" | "ALL_TRAFFIC"; +export const AllVpcEgressSettings: VpcEgressSettings[] = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"]; export type IngressSettings = "ALLOW_ALL" | "ALLOW_INTERNAL_ONLY" | "ALLOW_INTERNAL_AND_GCLB"; +export const AllIngressSettings: IngressSettings[] = [ + "ALLOW_ALL", + "ALLOW_INTERNAL_ONLY", + "ALLOW_INTERNAL_AND_GCLB", +]; export type MemoryOptions = 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192; +export const AllMemoryOptions: MemoryOptions[] = [128, 256, 512, 1024, 2048, 4096, 8192]; /** Returns a human-readable name with MB or GB suffix for a MemoryOption (MB). */ export function memoryOptionDisplayName(option: MemoryOptions): string { @@ -211,6 +217,9 @@ export interface SecretEnvVar { version?: string; } +/** + * Returns full resource name of a secret version. + */ export function secretVersionName(s: SecretEnvVar): string { return `projects/${s.projectId}/secrets/${s.secret}/versions/${s.version ?? "latest"}`; } @@ -221,6 +230,7 @@ export interface ServiceConfiguration { environmentVariables?: Record; secretEnvironmentVariables?: SecretEnvVar[]; availableMemoryMb?: MemoryOptions; + cpu?: number | "gcf_gen1"; timeoutSeconds?: number; maxInstances?: number; minInstances?: number; @@ -233,6 +243,7 @@ export interface ServiceConfiguration { } export type FunctionsPlatform = "gcfv1" | "gcfv2"; +export const AllFunctionsPlatforms: FunctionsPlatform[] = ["gcfv1", "gcfv2"]; export type Triggered = | HttpsTriggered diff --git a/src/deploy/functions/runtimes/discovery/parsing.ts b/src/deploy/functions/runtimes/discovery/parsing.ts index 702bfd41282..c944285a1bd 100644 --- a/src/deploy/functions/runtimes/discovery/parsing.ts +++ b/src/deploy/functions/runtimes/discovery/parsing.ts @@ -2,8 +2,24 @@ import { FirebaseError } from "../../../../error"; // Use "omit" for output only fields. This allows us to fully exhaust keyof T // while still recognizing output-only fields -export type KeyType = "string" | "number" | "boolean" | "object" | "array" | "omit"; -export function requireKeys(prefix: string, yaml: T, ...keys: (keyof T)[]) { +export type KeyType = + | (T extends string + ? "string" + : T extends number + ? "number" + : T extends boolean + ? "boolean" + : T extends unknown[] + ? "array" + : T extends object + ? "object" + : never) + | "omit" + | ((t: T) => boolean); +/** + * Asserts that all yaml contains all required keys specified in the schema. + */ +export function requireKeys(prefix: string, yaml: T, ...keys: (keyof T)[]): void { if (prefix) { prefix = prefix + "."; } @@ -14,40 +30,50 @@ export function requireKeys(prefix: string, yaml: T, ...keys: } } -export function assertKeyTypes( +/** + * Asserts that runtime types of the given object matches the type specified in the schema. + */ +export function assertKeyTypes( prefix: string, yaml: T | undefined, - schema: Record -) { + schema: { [Key in keyof Required]: KeyType[Key]> } +): void { if (!yaml) { return; } for (const [keyAsString, value] of Object.entries(yaml)) { // I don't know why Object.entries(foo)[0] isn't type of keyof foo... const key = keyAsString as keyof T; - const fullKey = prefix ? prefix + "." + key : key; + const fullKey = prefix ? `${prefix}.${keyAsString}` : keyAsString; if (!schema[key] || schema[key] === "omit") { throw new FirebaseError( `Unexpected key ${fullKey}. You may need to install a newer version of the Firebase CLI.` ); } - if (schema[key] === "string") { + const schemaType = schema[key]; + if (typeof schemaType === "function") { + if (!schemaType(value as T[keyof T])) { + throw new FirebaseError( + `${Array.isArray(value) ? "array" : typeof value} ${fullKey} failed validation` + ); + } + } else if (schemaType === "string") { if (typeof value !== "string") { throw new FirebaseError(`Expected ${fullKey} to be string; was ${typeof value}`); } - } else if (schema[key] === "number") { + } else if (schemaType === "number") { if (typeof value !== "number") { throw new FirebaseError(`Expected ${fullKey} to be a number; was ${typeof value}`); } - } else if (schema[key] === "boolean") { + } else if (schemaType === "boolean") { if (typeof value !== "boolean") { throw new FirebaseError(`Expected ${fullKey} to be a boolean; was ${typeof value}`); } - } else if (schema[key] === "array") { + } else if (schemaType === "array") { if (!Array.isArray(value)) { throw new FirebaseError(`Expected ${fullKey} to be an array; was ${typeof value}`); } - } else if (schema[key] === "object") { + } else if (schemaType === "object") { if (value === null || typeof value !== "object" || Array.isArray(value)) { throw new FirebaseError(`Expected ${fullKey} to be an object; was ${typeof value}`); } diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index e690a182d2a..f101ba80df4 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -85,9 +85,9 @@ function parseEndpoints( assertKeyTypes(prefix, ep, { region: "array", - platform: "string", + platform: (platform) => backend.AllFunctionsPlatforms.includes(platform), entryPoint: "string", - availableMemoryMb: "number", + availableMemoryMb: (mem) => backend.AllMemoryOptions.includes(mem), maxInstances: "number", minInstances: "number", concurrency: "number", @@ -95,7 +95,7 @@ function parseEndpoints( timeoutSeconds: "number", vpc: "object", labels: "object", - ingressSettings: "string", + ingressSettings: (setting) => backend.AllIngressSettings.includes(setting), environmentVariables: "object", secretEnvironmentVariables: "array", httpsTrigger: "object", @@ -104,7 +104,15 @@ function parseEndpoints( scheduleTrigger: "object", taskQueueTrigger: "object", blockingTrigger: "object", + cpu: (cpu: backend.Endpoint["cpu"]) => typeof cpu === "number" || cpu === "gcf_gen1", }); + if (ep.vpc) { + assertKeyTypes(prefix + ".vpc", ep.vpc, { + connector: "string", + egressSettings: (setting) => backend.AllVpcEgressSettings.includes(setting), + }); + requireKeys(prefix + ".vpc", ep.vpc, "connector"); + } let triggerCount = 0; if (ep.httpsTrigger) { triggerCount++; diff --git a/src/test/deploy/functions/runtimes/discovery/parsing.spec.ts b/src/test/deploy/functions/runtimes/discovery/parsing.spec.ts index 9f34cba0b68..b6136246716 100644 --- a/src/test/deploy/functions/runtimes/discovery/parsing.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/parsing.spec.ts @@ -46,8 +46,8 @@ describe("assertKeyTypes", () => { object: {}, }; for (const type of tests) { - const schema = { [type]: type as parsing.KeyType }; for (const [testType, val] of Object.entries(values)) { + const schema = { [type]: type as parsing.KeyType }; it(`handles a ${testType} when expecting a ${type}`, () => { const obj = { [type]: val }; if (type === testType) { @@ -59,6 +59,31 @@ describe("assertKeyTypes", () => { } } + it("handles validator functions", () => { + interface hasCPU { + cpu: number | "gcf_gen1"; + } + + const literalCPU: hasCPU = { + cpu: 1, + }; + + const symbolicCPU: hasCPU = { + cpu: "gcf_gen1", + }; + + const badCPU: hasCPU = { + cpu: "bad" as any, + }; + + const schema = { + cpu: (val: hasCPU["cpu"]) => typeof val === "number" || val === "gcf_gen1", + }; + expect(() => parsing.assertKeyTypes("", literalCPU, schema)).to.not.throw; + expect(() => parsing.assertKeyTypes("", symbolicCPU, schema)).to.not.throw; + expect(() => parsing.assertKeyTypes("", badCPU, schema)).to.throw; + }); + it("Throws on superfluous keys", () => { const obj = { foo: "bar", number: 1 } as any; expect(() => diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 9feda372e59..8bea601adf9 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -84,6 +84,7 @@ describe("backendFromV1Alpha1", () => { vpcConnectorEgressSettings: {}, labels: "yes", ingressSettings: true, + cpu: "gcf_gen6", }; for (const [key, value] of Object.entries(invalidFunctionEntries)) { it(`invalid value for CloudFunction key ${key}`, () => { From bb1d139a96d3a05387c8f4bdbd401ac7b6f85a53 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 14 Apr 2022 11:42:49 -0700 Subject: [PATCH 0242/1699] Adding functional test for the Extensions Emulator (#4131) * Emulate extensions from firebase.json (#4096) * Adds ability to download and emulate extensions during emulators:start and emulators:exec * format and self review * Successfully emulating extensions!" * formats * self review * handle case when extensions are not defined in firebase.json * Don't error out on fake project ids * adding unit test for toEmulatableBackend * pr fixes * adding todo * Adding listBackends endpoint to support Emulator UI (#4122) * Adds ability to download and emulate extensions during emulators:start and emulators:exec * format and self review * Successfully emulating extensions!" * formats * self review * handle case when extensions are not defined in firebase.json * Don't error out on fake project ids * adding unit test for toEmulatableBackend * pr fixes * starting on listBackends API * Adding listBackends endpoint to support Emulator UI * fix tests * clarifying todo * Adding functional test for the Extensions Emulator * formats * fix merge conflict * Using latest version of storage-resize-images * pr fixes * --open-sesame --- package.json | 1 + .../greet-the-world/.gitignore | 0 .../greet-the-world/extension.yaml | 0 .../greet-the-world/functions/index.js | 0 .../greet-the-world/package.json | 0 .../greet-the-world/test-firebase.json | 0 .../greet-the-world/test-params.env | 0 scripts/ext-dev-emulators-tests/run.sh | 15 ++++ scripts/ext-dev-emulators-tests/tests.ts | 62 ++++++++++++++ scripts/extensions-emulator-tests/.firebaserc | 5 ++ scripts/extensions-emulator-tests/.gitignore | 73 +++++++++++++++++ .../extensions/resize-images.env | 5 ++ .../extensions-emulator-tests/firebase.json | 20 +++++ .../functions/.gitignore | 3 + .../functions/index.js | 12 +++ .../functions/package.json | 15 ++++ .../functions/test.png | Bin 0 -> 121304 bytes scripts/extensions-emulator-tests/run.sh | 11 +-- .../extensions-emulator-tests/storage.rules | 8 ++ scripts/extensions-emulator-tests/tests.ts | 77 ++++++++++++------ 20 files changed, 276 insertions(+), 31 deletions(-) rename scripts/{extensions-emulator-tests => ext-dev-emulators-tests}/greet-the-world/.gitignore (100%) rename scripts/{extensions-emulator-tests => ext-dev-emulators-tests}/greet-the-world/extension.yaml (100%) rename scripts/{extensions-emulator-tests => ext-dev-emulators-tests}/greet-the-world/functions/index.js (100%) rename scripts/{extensions-emulator-tests => ext-dev-emulators-tests}/greet-the-world/package.json (100%) rename scripts/{extensions-emulator-tests => ext-dev-emulators-tests}/greet-the-world/test-firebase.json (100%) rename scripts/{extensions-emulator-tests => ext-dev-emulators-tests}/greet-the-world/test-params.env (100%) create mode 100755 scripts/ext-dev-emulators-tests/run.sh create mode 100644 scripts/ext-dev-emulators-tests/tests.ts create mode 100644 scripts/extensions-emulator-tests/.firebaserc create mode 100644 scripts/extensions-emulator-tests/.gitignore create mode 100644 scripts/extensions-emulator-tests/extensions/resize-images.env create mode 100644 scripts/extensions-emulator-tests/firebase.json create mode 100644 scripts/extensions-emulator-tests/functions/.gitignore create mode 100644 scripts/extensions-emulator-tests/functions/index.js create mode 100644 scripts/extensions-emulator-tests/functions/package.json create mode 100644 scripts/extensions-emulator-tests/functions/test.png create mode 100644 scripts/extensions-emulator-tests/storage.rules mode change 100644 => 100755 scripts/extensions-emulator-tests/tests.ts diff --git a/package.json b/package.json index 92829f5ae4e..20b10005747 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "test:emulator": "./scripts/emulator-tests/run.sh", "test:extensions-deploy": "./scripts/extensions-deploy-tests/run.sh", "test:extensions-emulator": "./scripts/extensions-emulator-tests/run.sh", + "test:ext-dev-emulator": "./scripts/ext-dev-emulators-tests/run.sh", "test:hosting": "./scripts/hosting-tests/run.sh", "test:triggers-end-to-end": "./scripts/triggers-end-to-end-tests/run.sh", "test:storage-deploy": "./scripts/storage-deploy-tests/run.sh", diff --git a/scripts/extensions-emulator-tests/greet-the-world/.gitignore b/scripts/ext-dev-emulators-tests/greet-the-world/.gitignore similarity index 100% rename from scripts/extensions-emulator-tests/greet-the-world/.gitignore rename to scripts/ext-dev-emulators-tests/greet-the-world/.gitignore diff --git a/scripts/extensions-emulator-tests/greet-the-world/extension.yaml b/scripts/ext-dev-emulators-tests/greet-the-world/extension.yaml similarity index 100% rename from scripts/extensions-emulator-tests/greet-the-world/extension.yaml rename to scripts/ext-dev-emulators-tests/greet-the-world/extension.yaml diff --git a/scripts/extensions-emulator-tests/greet-the-world/functions/index.js b/scripts/ext-dev-emulators-tests/greet-the-world/functions/index.js similarity index 100% rename from scripts/extensions-emulator-tests/greet-the-world/functions/index.js rename to scripts/ext-dev-emulators-tests/greet-the-world/functions/index.js diff --git a/scripts/extensions-emulator-tests/greet-the-world/package.json b/scripts/ext-dev-emulators-tests/greet-the-world/package.json similarity index 100% rename from scripts/extensions-emulator-tests/greet-the-world/package.json rename to scripts/ext-dev-emulators-tests/greet-the-world/package.json diff --git a/scripts/extensions-emulator-tests/greet-the-world/test-firebase.json b/scripts/ext-dev-emulators-tests/greet-the-world/test-firebase.json similarity index 100% rename from scripts/extensions-emulator-tests/greet-the-world/test-firebase.json rename to scripts/ext-dev-emulators-tests/greet-the-world/test-firebase.json diff --git a/scripts/extensions-emulator-tests/greet-the-world/test-params.env b/scripts/ext-dev-emulators-tests/greet-the-world/test-params.env similarity index 100% rename from scripts/extensions-emulator-tests/greet-the-world/test-params.env rename to scripts/ext-dev-emulators-tests/greet-the-world/test-params.env diff --git a/scripts/ext-dev-emulators-tests/run.sh b/scripts/ext-dev-emulators-tests/run.sh new file mode 100755 index 00000000000..53d63d49af3 --- /dev/null +++ b/scripts/ext-dev-emulators-tests/run.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e # Immediately exit on failure + +# Globally link the CLI for the testing framework +./scripts/npm-link.sh + +cd scripts/ext-dev-emulators-tests/greet-the-world +npm i +cd - # Return to root so that we don't need a relative path for mocha + +mocha \ + --require ts-node/register \ + --require source-map-support/register \ + --require src/test/helpers/mocha-bootstrap.ts \ + scripts/ext-dev-emulators-tests/tests.ts diff --git a/scripts/ext-dev-emulators-tests/tests.ts b/scripts/ext-dev-emulators-tests/tests.ts new file mode 100644 index 00000000000..c1c5b8783b0 --- /dev/null +++ b/scripts/ext-dev-emulators-tests/tests.ts @@ -0,0 +1,62 @@ +import { expect } from "chai"; +import * as fs from "fs"; +import * as path from "path"; +import * as subprocess from "child_process"; + +import { FrameworkOptions, TriggerEndToEndTest } from "../integration-helpers/framework"; + +const EXTENSION_ROOT = path.dirname(__filename) + "/greet-the-world"; + +const FIREBASE_PROJECT = process.env.FBTOOLS_TARGET_PROJECT || ""; +const FIREBASE_PROJECT_ZONE = "us-east1"; +const TEST_CONFIG_FILE = "test-firebase.json"; +const TEST_FUNCTION_NAME = "greetTheWorld"; + +/* + * Various delays that are needed because this test spawns + * parallel emulator subprocesses. + */ +const TEST_SETUP_TIMEOUT = 60000; +const EMULATORS_SHUTDOWN_DELAY_MS = 5000; + +function readConfig(): FrameworkOptions { + const filename = path.join(EXTENSION_ROOT, "test-firebase.json"); + const data = fs.readFileSync(filename, "utf8"); + return JSON.parse(data); +} + +describe("extension emulator", () => { + let test: TriggerEndToEndTest; + + before(async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + expect(FIREBASE_PROJECT).to.exist.and.not.be.empty; + + // TODO(joehan): Delete the --open-sesame call when extdev flag is removed. + const p = subprocess.spawnSync("firebase", ["--open-sesame", "extdev"], { cwd: __dirname }); + console.log("open-sesame output:", p.stdout.toString()); + + test = new TriggerEndToEndTest(FIREBASE_PROJECT, EXTENSION_ROOT, readConfig()); + await test.startExtEmulators([ + "--test-params", + "test-params.env", + "--test-config", + TEST_CONFIG_FILE, + ]); + }); + + after(async function (this) { + this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + await test.stopEmulators(); + }); + + it("should execute an HTTP function", async function (this) { + this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + + const res = await test.invokeHttpFunction(TEST_FUNCTION_NAME, FIREBASE_PROJECT_ZONE); + + expect(res.status).to.equal(200); + await expect(res.text()).to.eventually.equal("Hello World from greet-the-world"); + }); +}); diff --git a/scripts/extensions-emulator-tests/.firebaserc b/scripts/extensions-emulator-tests/.firebaserc new file mode 100644 index 00000000000..f7b55c6f220 --- /dev/null +++ b/scripts/extensions-emulator-tests/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "fir-tools-testing" + } +} diff --git a/scripts/extensions-emulator-tests/.gitignore b/scripts/extensions-emulator-tests/.gitignore new file mode 100644 index 00000000000..c360d67af3d --- /dev/null +++ b/scripts/extensions-emulator-tests/.gitignore @@ -0,0 +1,73 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +database-debug.log* +firestore-debug.log* +pubsub-debug.log* + +cache/* + +# NPM +package-lock.json + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env diff --git a/scripts/extensions-emulator-tests/extensions/resize-images.env b/scripts/extensions-emulator-tests/extensions/resize-images.env new file mode 100644 index 00000000000..ac1d2e2fbff --- /dev/null +++ b/scripts/extensions-emulator-tests/extensions/resize-images.env @@ -0,0 +1,5 @@ +IMG_SIZES=200x200 +DELETE_ORIGINAL_FILE=false +IMAGE_TYPE=png +LOCATION=us-central1 +IMG_BUCKET=${param:PROJECT_ID}.appspot.com diff --git a/scripts/extensions-emulator-tests/firebase.json b/scripts/extensions-emulator-tests/firebase.json new file mode 100644 index 00000000000..9f529c2ae62 --- /dev/null +++ b/scripts/extensions-emulator-tests/firebase.json @@ -0,0 +1,20 @@ +{ + "extensions": { + "resize-images": "firebase/storage-resize-images@0.1.27" + }, + "storage": { + "rules": "storage.rules" + }, + "functions": {}, + "emulators": { + "hub": { + "port": 4000 + }, + "storage": { + "port": 9199 + }, + "functions": { + "port": 9002 + } + } +} diff --git a/scripts/extensions-emulator-tests/functions/.gitignore b/scripts/extensions-emulator-tests/functions/.gitignore new file mode 100644 index 00000000000..884afa60ceb --- /dev/null +++ b/scripts/extensions-emulator-tests/functions/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.eslintrc +package-lock.json diff --git a/scripts/extensions-emulator-tests/functions/index.js b/scripts/extensions-emulator-tests/functions/index.js new file mode 100644 index 00000000000..2b3d4e8f4c1 --- /dev/null +++ b/scripts/extensions-emulator-tests/functions/index.js @@ -0,0 +1,12 @@ +const admin = require("firebase-admin"); +const functions = require("firebase-functions"); + +admin.initializeApp(); + +const STORAGE_FILE_NAME = "test.png"; + +exports.writeToDefaultStorage = functions.https.onRequest(async (req, res) => { + await admin.storage().bucket().upload(STORAGE_FILE_NAME); + console.log("Wrote to default Storage bucket"); + res.json({ created: "ok" }); +}); diff --git a/scripts/extensions-emulator-tests/functions/package.json b/scripts/extensions-emulator-tests/functions/package.json new file mode 100644 index 00000000000..a7d48c5c117 --- /dev/null +++ b/scripts/extensions-emulator-tests/functions/package.json @@ -0,0 +1,15 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": {}, + "engines": { + "node": "14" + }, + "dependencies": { + "firebase-admin": "^9.3.0", + "firebase-functions": "^3.16", + "rimraf": "^3.0.0", + "fs-extra": "^5.0.0" + }, + "private": true +} diff --git a/scripts/extensions-emulator-tests/functions/test.png b/scripts/extensions-emulator-tests/functions/test.png new file mode 100644 index 0000000000000000000000000000000000000000..7c2400eab51542e8af300886d602ca757d702717 GIT binary patch literal 121304 zcmeFY1y>y15-vPQ&;Y^REx5Y}cL?t8?(Xgu+(Uv4?(Xg(cyMbTL=p) zND2!RDLC4hT3DL^01^?&YS8LR!x%YQpC!!$6ut$oLMG^b3;sa@BY|!mOct+TC<8<7 zs;q2(LK2CtgRbaI=TA#*$Pi_C7WFPU*`+$~BemTp@Rn1WHJaqrW6HF$27tV!A6g%6vepFGq9f0;&Ee{TDQshLT&RWY&tt4NnlAzp!FPWww znqh+T4S>tln4cs+2WAwBXAq)PW<)7{Z+5WsTK=4dn?IxV>6n;7LSeE$>G#H_^2JvA z&!;94l`MAyc01!NY65Sra45VFaO#3!T2N__H33cEYb#J+lwyJ*2;u{1i6AE|$Gf7x zydcB2QTTs4rxf`_M3{y7Q-B)sox-b$LTL+k(AdjV;0rd_I(DB25!`mqYvhYYqLAJ2 zI@L`0>qa&-F5y5Xe5T{m+7Wc!8mo12gsK%91c8PoL`ol{;YX%KEIH?{m4MHqLx06?N(oUxebJA6j$>iNXXAs`_)`DU z!4FhtfjSz;#=D;q*lRt6*C)I#Aa8)-3D54A)8DHWD?_ZR+EV9ph2;VE1(hCIydL75 zW2_O%BW@$V_7cjUC}~JGTh&{I9huFlci3BYIBq#mNEQwEX?*p@!0o^frmYA*WB#(U zd$D_K}(<%I6H^i>xs~Z3k=HcN1r}Nkb^5UpLh_JElHF?;YVFwAardx+(r(jxpeNS$@QebEoW!9S3p41?21*~3vc zL+^+JB(P9o`bbM8xD!JpKD=XwNDuo$LX&Vkr1}l+{vB6fmf!?w`4Ij#^3Q-aA^vFfznQ zW7!RIs8Xjz80|A&pe=)>>7}m5DFa*Gvr@;Mi}2}-;Dj*Sn6Q5*zA4E`m>WIWle&I% zq2o^H3)dMc(9?Qe3CZ0Zye@K1Y7c7!qY+FMZ1@S=0D6OQgLPT@B=}Z@Btf!{)U&re zO=^rnj*_0(mh6<29a$?vK754?WjK6W1dgab(fynEXC@1L3#0?i1L~6O8R0pi%%qi} zjcwMNU~Lh4(lZJ|Qay43%G=L%RP1O1QnaFi>0%mUi*gou+J$vWo+aGMJeB~qgbdki zX}Y301$m`2r8P@)b9Ku*%jmfeR%25i=NBp+v-xK zow-+12xR07_NG&RmhMv?ARX{!yqCq4ttb?9YA_8kE#FlcSJOnJ!=-DOgDn44&Ria( znOe_XPxY(e7oJVd4ebfs3F)uKx>gq~?p_x|mrLiA{o^^V%HN+-zgd;y7SjC4u56XP z{dS59HBe<}kL5kUJK#ECA=XNuI4Kubnp&;~Y*echt(4YNTN4)0@B{jWyeK}iQw-FZ zIHos`nT$b16+&4<lTo0rzxL zVYaDEt$wX_?X2nG821?VZosbL@7dkK@mKwW+J!Q0Rjwnn($StilAoUps%O9Uf1Uq1 z!8}|utW#&4^eLAxYmZj#d=3n;-@YCeKz7Kt$*qLQKGO*}Xeb2*lTv}RWRdM7x@73!P z7Kf+H+GcQv%OTFup%t>^;@0BT^k{wSc{@)aNAM|5mY|UjJEwpb)05rH_u}#<(PP%# z#a+v*$jk1$W2aybb+5K>FN>j@qtC6uuHZ~ufT7d76T;W;p67Az?(D{osmC_hzV8NE z9-J1!9ZU=27q~{iL4X$cQXp-hyHK)FTu^!7yKbJq+*HR1~5n)qH~LQdbT z^sGV!T2_)evO01Z4q7T}pM_rg(etwN!}AK5$?yYY*@WPs;vw{e#->7T{>I29wx3?x zV+loH%@tu4j|vFHdBjG);)>-Lh{bBM9r{MgMw1oNWAr&1srB=R*G6UH-5!~5?l(=L z7v%OM#pNRVu?HkDG8wq_Jod%+nMB+s9w(tmjt^@NZzYx{3VaJLER(UoWw9O`W5QI5 zQR*+bw)j@JU59V6V$?fOfxW~(;iP@hl(}^>Adm0o8+Z}C3@c7)DZ4GpnA^FbeeO{o!_(*^biJ^>kB_o;RXb+rF9-fh&#w()BxoKl;JKsCFbAGwn z#l~RSop5JO;&VEpHC7!c+g((x-J7({MP)LZ*FId%YVJgD#we%tTQ+(+A|(?F1jg77qx;}sr6JZqTrZ6Q(sqh^&90S5nce{wdAR&<9oWa zR*DDRQe9S)cFognWFC5}>R}nJ_Pz0Ju}XcZMya%FrxuID_&sx6&A3&G6}r{ZNWtjQ zyhf!~$LVj+sRvwiV)RG$G;8&?jb5?tfzr%Cj#+E=744S$X^e}MB`wm1In`gPDK@0G zGz&M@UY9qu0*_DH^}X)m2eAx15mg<3gaTOAg0cc)K6Qs9*VfwT$fyzi;EFh z*q+;QK9!DE^5D|p(8L}R>bORGTEA3xY=sQAW`2{+rmT_Uwa8iUKHE-9nwO8^pLOIu zylQ6IFq6wM$>sB;UWx%Qpg0XIQ4 zVH8L-er|U@dYNiVQ{wq?^n7ye-e<6{!=pu)MY&6R0tEszUbZi7hapQ*1F`3^G=9;~ z1<&8FOzg}qa=HC%-A!H(f9Q4l=^Wxr&%S8hw49A6Hj3G~b^LapU&fz*KKEIDTty=1 z6XR>;fAHpgq}rMOy-JlUnLFCa>1}a`bA9crr>mFyg>viK4^F=-KRq4Lwg?O0LkB?d zHYyGRCjy@Xizh0dVV)?C2syzpd_F#QvS37PfOFBg`T5rB%Ck{dtYdsY21V&ao)mEpOK*xGd5givGzi}h zv-el6cz(wL%-~4pMz0K+GjXKo#b_Cz&?C}BUD8xm7C;TUh5ryYrF!^9DEQ`t3FY3DG~VI9u_N zsLLu43EMfE5V6xU(le6q!x0e?@j4ota(@;P`%@hBi;u+I+1Z|(fx*qqjoyug-pA#iytw+Sf$;i>d-r2&=mgucs14BC(XFd{= zw}$@n`L~}Y?iT-T$=2!5us{Q3czeRYM9;|ZpSnRsdEf4GD_FRjSZjz_*nngPYJ;DX zlau$K_y3P4|84PqN~-_2Bs(Yb|CaopC;$JFDo!Sj!ge;GCY|~JtFAwV|M%gag1iiG zBmW;s{43^v?t!AD!{T-5pDKfz*g+AtJ8~I)jYt?E}^Z`t$MM^V{_!T%=CJ zcK|>TASoiG><)I^?q-B5o^tx^EJ%bULcNz&Yw}L3+aSw~cPl-6CA-S&40FlPc45VB zZ$d8qqqXAAps*~=Hwic*uyd+($d=Zg620!9K}toy=-j8J z5WF-Af5`uS^9EqB(12|0B&=cobGM69FnC_bVuilBZBUY>=-=CJ6qYFB}V_Z>1&Rg?dz2XyiOs`^J7|4&wLmh*pwF|Z}TVhw=}6hJyQ zGjkA*5Agh%AU_x(KPuyzeq2ltBi2u+x9-LNfiEut%&cAC83ViZ5c)GoVr2Y3)hCFj z`jlFL2kgzT*M#ro8 zfIL+~Wg*f>oncQp8(>E`*4@ADeASe+LB#C|p`Q2$1}w7xA(k;nAI7bC!4D z>B=;o8Oj}V?}{)Osf_BBS#?0Oqy{F85~+OvhH8`U5t|Lz^YOgXbD`^Q-o{&&lkN9u z#MjZyB*ggP{%wKu-(E(@`G0cx>Ic5?%0&6W{j-{gZ{N)V*qB{&j0N4Z`S4BN_fByU zASL@joXj&WbN!j#5jYLJMe92x6c&f+ldhdp)2w5y;ND@E{YkbM4}k@uKM19q3F>b> zY^^WKN8%sI2faOgbY=DO21yP87DF9#$#6#5NX&Y*wj!V+v~=~y=oI!Vb=c*J0f#0# zs*dB;;H3%HE6?(14g6@_v!s9LCpieqTJYF$bS5EH+4Ke(%uWX#x89@Zs3E(2W~94F zYk6S#A~maPweAngfgCcQ(RDDU?d{>mHSs-VJ+bx+G2r^CJMZLQQ;7SJr{Dt#YZou* z0wWSHlsOv`6Y=|Q=Zjr6AT{CW6b#So4k0pS+yi9`r(VBqz=3^DH zBlxJ3+K-$gpG|%oCU{dNG-&413yU>VZHdX=Ut3{uny&7I4!PWppDL|VA=60W<9hUSG`&+5i*^Q{uAe+$R@fy-uy2 zTc$_1*XjQBqL>iWi)$9ux;i;n_0S-UQdAmRjT6Fq7^|`0!Z&>qBKiY1?!&Z>!^p=l zMD;mL80*HiOdaxkF-GZZ!VSG)f9AQu5on%U`2?Hmt)Nz!XaT8i5X!11H_dL;)Gc&z zYUIVd@KmMW+p57e=Ul~mNE6;HjPEySUbvoF-F>W|UIR+q>X?xHqgkQ=PmsUVj4Ke! zJc{fJ4*`~KhaD$H)PmBEu-^iYUzvFzQeOPfNa~gN)j~Yn)$6n(F1^qv< z5p}Df6r%*d{7f^|>mM1?XfyC5rTnZ@@>%-clJ6q#)PZ9Q{+3F6l)g{L%~WN{Xy@+` z=s=Q~B^mXU%o8NCC95WKM6@z4c-<@SRS(ZpF(KVk-Du;2-gAV%P`T8IP|?wuy;0}- zJN2U8rruDEF3QIb=1iW|#J(^`B!ksccVM-Sx1|$>gL3P!Oz-S&+XE|=G85RY%vE41 z_5Y5S8-!Y#;z6QHM@e1AwWyV3{JqoFy=gnELYeb=*Rh<1A@p_YD>J|EetO{Wof=1| z*pzs-TK}am)RG|ROk~x-g#ALyINKJd*YJeDWC%UAdqr}Js6A|_UPCQFEnsVV7ga>3qh*E>`BHV8uN)t~CQ zz)RT>)ox`^BoV9%{0fFcskGF)uZ?kOkUK6lGw{@#Y-MkaRBo^M;!qdSziYV5{xMHB zSy1l^!@>q;dBCR68GBoz{LDR(mcBwyZBrm$L)mVgMNI_v#hctz=vasOCfQ&7w5@EN0w)IxUq#XXWZ~PWg$RI= z1O8+Vog*_LGuE%#PNz8C)4WJ-B?!x!ZA#M!gT^%)h@eh2?Y zEnV&)xWPv{w08RRfpI;*|8Ri!i28;260AXeP<+`gd&UjEsQ7Rfz`P`I`ON^lM*qmt zmVNbLHKMm9x#>*Cr0j4=vgY4TbYX!sjBm7VZM*q_5dl-F3|C$MIh=!3Z-eUhag*x{ z`>zXGTeet%akI)~v z;7}Hk)1|tFmJ%t0r%Da=b)@gLV5tDHG%}w#`#fbUZZUVOtQAX=gB}+_LIw==uU3da zvc$nd%b3A}G#t`*`t7!W#S_T3`flvv^nIxjj=bdn$$4UW*pebb>GnCZ4vXpL(y~&A z*|oJJ)q|sKFU^ql9|T|=0D;&b7H4KACuBx6tFHMyiq_VeQHK+R(#nK^%W4{0*eps& zr=8^i7@!wuurH&d3SXhl;=tHWkb8612ho^4{VGhlrSz|xcK}UmZn^bE>n&6XEUKrv zdn0SRUvU-_K6TALy@z!<+#%sl`;sc@Ba_c&V=$gE6yB*maIMpoTZKI6v1Ul4?52MK zq8bE<+$nX7bHOA(tjR8p`qs|9r{~Qb9+BOq*=ApbS2f#CIs&66c%8YEuu3(R3WSaR zofTgIA=CoUWD4Zqbo+y6nJ>|)n|fUnTvhEOUgEiZSJJ!IcmtMr<7ETYFKjiHmRFKM zBaOd)ffFRvNK%s!vOL`1dp}ICHvL3iaVte`=g1P}O3^vWOBQl}h07-`O`SA!=c|_w zSAYE{E@D)KRVbF5NigdymFi;0m#OhQXGrK=Cx46rHn9B~!JiiU9DLAm%-xo|7_bCt zvysE4ZnhflFOI%ZuKt9YPbh$lGJdY)LxPuKUnGWw#}U#57RBcfrHU|Z)d@-bq9K3O zX3@Jjt+45I70y(hZhXK<9LCwdZs0$Y3pZD=)diQO%*vhSlFLG>NL<_7iNqrx@%k%-3oMOqsfWSuw4UT@TW;sZ{LBOvscR%< z#7mnnX;xYzLOD8GA5o@{yHmDGBLdO_=wrG;ZyFDc$&ev`+aMy!OWQ?WR_78w8N zT%q`qMGzdFf(SYHPG$4Vz5br1u9?r=H4)u5zh$=lj5jJStGsD%La1m|efIFD=^s8R z48kD%NW|QPu8%Oq3ePs)9IGn=;s;-NJJh@> z5a_V~3a=5^k^@BuPx4&c*J{>P1xLg}>HGz&^1P5yY8 z^$1W=eq??Oihuw}+UXFRCP(MP+zZvKpa7(!Z7Cy_$7l-gl*K^18eIF)gbx5a$82?Z z7Ri77G)@G_u1RAk9+^)et*3>?M%LYKY@c{9H-6j83ZEv7w2TSord9R43UbwLt?KtKp8V+9WvoD>-tn)PufS7v93pqQ_KCmpESK9WvB zz$)LJJjC1PbSsLtucK97sLt?5#_y#-EKz~gTM(R-@&kubxYqOC-q6}P%VQETjY588 zin?#@OU@Okcrma@CA-|TFW4YrPbF3}S$~T7sBA&_`leyv2X=O(z_Jgvmlrz;m3S$4 zeDAxzc@+ICeuvfgbtskjhZ>-o(fc8O7H2)XDIQ(qKSQ?y4{dYpetO4&V z`;;vn4Q-djYzC(K7BIDvdV|sXoIh4iEes+j%y=5`uy>Fc3wzZcU7>i!3lif%BYaLb+LSD<5rM7Nx%`eBr zCkO1A4g=c9Udp$(0x`G~SBmrffq(s(Hi*&olJ|R)F}??%r0nRI*W@3x-a{pu`TsltbY9Qfv*L5Hd z^s{cM2>Os%mCWD3zyQ<~I@HJ^1qImB7>TLLwg?cZzu>{OSD5pFt`GW@1>}K3wQ8y& z#Y@?r;%kH6$}!Tvafx?4xHhcdu(i$ERNC zw@xRHn#p+dT_S(jHu_uSB$QTWxrvfyqSybl&vncgx9)LoakZUBfkztXv*_YZ`OItE zQag3zVy`zVM(BPM_oj}4v^N(5VdfMgsjs-rt2!8GnB)MJ>98BYZK3XXQ)06n$LnJ9 zI*lVY=qP>O+zKmToU)Nvn-&GMbT2c^#AExV?F2d3Cud;w+qY~?r%USg zC+ebEotOc;=*IQ;gd^PN>LPjLK1TR8Cd#d^Oz@9CwazUZq4DHik&^Ig6aI{5K+-;6S@>2T?dfXi^>TN3f-WUYO;k*e2!~9GahbPRaIhD zS2BHY&Wv+<`P`s9+o@kqZjXEFn1V2=6+DhS zb~b#N;!zi|Ir^wmHE&K)&EcnJV9)?gmPAF*@b-w}eDB}M_<=$&AE&VUmW)vfyi#1C z_l=IJ79bVw#nhy1V6tRbtJ^qi*U&Flu&%3ij^~$2*VmCO{t9qA?eG^^@p33G^A`J~ z9y}h`|$iQlWd~Bn2S#*|}%tMAI6veEg@#t1@m5^F0hkC!G z)!4>XxBMlW)DQj*;sF%FDO+qn8?%JRtb|kAf%2>S4gf>kQ7_R zOMfA}HIAgaTx&eF^ggNRk2^qb+N8yCt2O@sy@?<CHa%;aBg@YA7;h`{agC&t3HD_5P-po*_CoP?K+P$FpH9tNF-uh?8zsVAS*G3eOfShKy($lu@!D~g(4?icA{!n>BDlb0QI<=!p<%){m zl$IGI|C3=oueKZ0ka~dPGxG+Cfos9aVc+o(-&nY-fhBJQ#nJ`Sw;di?zn^{Ik6E+a zcJsttrS>Nsih?7qunqJ;w1Pn1|I_tFbXpK36geY@BX;Rm@#TVI1A9^X6jMh*@sMs<;GA(w5q)%n4Bew>UM`WtU?|;%F zBOF}LWu98G+pu>;zA;nXk~761;G9kv={J_tMQg@uFGbz=!*m6aDg6&1R1EUputC8> z3}8!`cJH`{r)8lx?C^qbaGy%Mgna1le9EpS2y3tBOxBD&TEs?(6KkZ2!o}uA=L<-l za@sdP757piNq1mN&L*Zr|Xk=ALhi%{FiR;P)0XG##l|3{J~h@wcqbhhnH3 z0sNT-O22p}8|}uzt+Ga}+?$i0lH;1tTNJql9;TI!Fib-|Bk3oYUm1L4o6Hj^xf#+t z=boQ0GhUgE$VeEqj2NxC;MDjrS9~a19<04{e#P1`%Wri|(?e6%@!ktoLA{xBu@Oif zF1+rnK2NaLjfm@nMt7wvR3G`_w~{jr7Wh1oE&1Jp_g*+c7q`}GMHtMDDS{W zn~)q~4Gm3-j`e!j5qBJ{MWZh6?C?vqiN8SvD#*kSgRw1L>k=UY)%&t{`JdP?*U}7D zFol?%;l-3=`D~^*Wrc$+TQ)*01-B3j*LC(xy!ULo+O44zb$Q)q7%Lq}$(3H7-3tz! z+Lo`#QEUZJV%_CMzZ?>x14Ee@cUg;iW~q2<3HXodQJbt}26ATTkEQLth^pGu3;{3F z*v8gtNkJV^0eN^$mA0}rMcA}Ws_6nHw_lH<#V_xsqaR5`2&T2rX*N|<8m;JQ9#P)q z)DNPSf_S)N)iIM5d2CCmeX$?D?Qmeem~`!NX&P#CxXz7%bz^Mg^m0hKxXVFVC)}(f zVeERkFVUx5{K2{(c6|;cjd+9Y=$skaQKO4oF`?1VZ&V?`;4M(VpfDu?(RSylXt-G& zYUKjIe&uDtv;Z|xa4MF-rCsH$IrZ6>lRKu?57-)uryET4`-TYhj|1`OFAue={E_3; z5b74%JeEshO88svC&0M4!*ON$NXlV+_V4dWDZPX{WHYaVy!r z-e)mm@2}jK-{l3mYoLRcqVV6c*+D{IBkINThWC?E!)!M+A|#(YoEazzT5qDun4%uW zu}(FU95`E{2(Q|MOHapeYVEgo%ObP;=XTGk;RN)*I|)7Ip}1>y&zP}qL^s0>t|Tu| zwgR-rCxH4VWsz@e!!j3y!6$LZbY()|HoFj97{9?m`K$Aq=WOf6RjnY^xfID3iH}r< z*G)3dok>ry^qlb9XoVs=13*h{Abs%?24C=e=8b+V9u80c39w0Fw%zwPIp(^ z^okpvfJF+{;GRe0-KBYRsI06YPzoi`b*<2Z+!d+VD7hmptCcg#cGJa}`Uad;?w$4O zO$#(?HsQ?Bi4rbB*dL$OTi#LT^08DrW^PCes~%lo;GZ~*eZ?<;SlgZ3EFQQmRAm$K zw(Iq&S|kP;7w}CzaH^`m_8~FE84T2Z&#T*H_kP;L+0-rM)=9BZqiN$W;&zSAoBM(; z9Jdl^r<8d<-!bo?HqlI2m9P(Uh*Mcc^L4MfbjPZUal$Cy3qf(R1W3ha3XXZgMRKaP z71eRlMBGX-gIfy(H+)NsBfNz(24N}qr4#|^3BTRTca&%wCyYX!8ZSSIPQEjb1RyV3 zv8L1IAnA?s$Ed8}lxDCO-0{~!oqf~mV_R$hu305VIH~N^XS1@;GHdgqa|PtG`Gmh4 zP^oKeH}o)3drbZOI6B&G1}_qS3MT>T@KalPT<>#n1US?wy93#vbNk>^}yv?7tR zZ8#Y=k5X6~=`mmqh>>7R2`3lA&f{*@7+0EDdUOReZk(E%dycU|`H(JIGhN|h(MMwu z^$d{FOo3Wue}Jj=Qz5RJ;tyD(a5YMBfo0;*x|m%()Mou!Q==89Ez9Ld;qAibsuFOO zCS`}E(8#$cb+C2`2S>q zS>IB?PmbVZtwO6cQ zFiJ3UF+y~Tr9)xGgrJwzIX0ClX1c=AR#d$PJ)OP+XY$)1kQ=4)LDm&wb(l9y3$*$-+C%+Mmcg7AG^7~zugCH# zurkO~H5~^qb6S+u$a{<)(T$Wq%e{f-OZd%Pe$vO_(@E>3lT`K*(;Es$E+g%7DXeF8 z0=^Iz=AF)Q-dK;~a)r3*Q*Uod2O#B2{bjA(_V2q(ocV6UH(HGFwmd}~mYfa52w1$K z<6U~9n_DcZnrUY~t&W^M+;gAFOnfI~aer@hW9H8Ah*Fb-Fps;Ilu8i={e0a=xq!zL zjeW{j7wu$idC+v_sd!~<dKdNR1ULIxu#E!5yu99+mq74wmpV!sVRLM1ra-Ue zBdaM_W5W4Qqs6CS^P{9?#N+FR4NFsuQ{zE1ZmpslqfOato0UF>4E}-Xge~T|5{Ech zx@UkpO2GP1rF=HN`@9c!_Np-~SekN1%sglyWD1QSFLLC%Z+_?g;RnK+-|ADoyRy7d z;&e=LDn1emPRqE{4R{t4#>P@dIcJI{8@&%30_MpsorZ`Dm_VF&K+LX&0DV0M<(w0V zYUd!F_fC0lTzYa-nhrDl*uuv*oKp@0`9-$knu$>IDw*I>0k(G1(oT6&idE2J@Djor zE{1in(QUb86V+g8$J#4%$AooCQfe7foJ!2F)GNXTDN3d%9xei3-IeK3&tcnm$>vO4 z5<#w2v>%dBUhrbWA=Z%QTUt^d?=5Wy;moel38|{N2MYTH49i^#eR&k2 zQ*KED1dP{u#25GCFG!3CN`HaY9aMj>U1(2pY;v6?C>4KJDN1p2ve!XMG3klV{vej& zOg!uN%`Zlv(2xD+*D?7wBFgsWJWRtvLuXOIUd!C3d#QgzKS>`?f}B=4`EQ(wlrP7+ z4{YpP{*B0cO7IS&>FlOHGDB3#NhWS#dVT!eu?s?d?PFoE6?iT9o}26a8+jZ7mcT+$;B5c zhmyE&ZS?rcU-%O{?F;n!p8UT1Mj+Kpjy7wetkFbGrCCP#`(D(q_oN`#7{ALV_v_qF zBHkhwaO9wo-x*G0ASBS_!0z>Si$};K|ew64(IXxYK=g*n{d|A|4bA9sqMw=hzc6__2-JUwb z-D@EHZPiR5nMwkrH<%1sYOdhLtFgpab~rP9;V@T3uSQhvYMC<*zh0U&E*=U;Ws8v{ zCU?r)8pgW7DxJ{F#9Te7cy7wJAJwhLP6r&Ff-g@@2&z$p@>9|mHS{0g6C${$l>8UX zK1g8ssDI$2#C^`~qdy8iF4bsX=&#>JvfcH-8?F$GwE5yVxTq0!?cj$2~tOw|{;9EQmLq*=N;=Yji?Q0#4*Xwx8OyRX6uS^IZ7cH2JJ*Z*?1xos#TU>H!yO_q5 z1rj?>)>RtGZG-1T5e!6C_X^ITt|;6u%;+a~W_+HYIUM|UNDt^%_A40=s6eJerR}@i zQiJBg%R`dov>m?cY=sL;I&xss;xheSc;RUe4!`MkF?|qL)pCvUtBl~{YotVsd6s3q z3_U`DdtUG{kHPQ+hS-c>nuF{WC%Y{<>Rojy0;Lz`hkD=LFMx~ZX@s!o^g1tq{>>J-&-`kB&0r#>)B=$BpRq%YufE8_Xxoi#Y z!v&8flwr~}@CV74P|@eMQ%*QHu^4S%xsxlwYiF!9(Q<=eXX6n9_OFkqsdE&dJTepF zBY1YvvMw;T3SKbnHF>AFA_8XY;R+8?e!NqX-IUD6OnsMDu67RkhsJLdw&=V?-5jxP z2%$7Q(&6fK+QW@$SdON~{sHaSxEzm0oO5i1dagF{jQ-gB=+%dO-Ftq55TLXg4k+)b z*&8H0Z5h^Oy+!e&$o{Z~!WA5R&_!=csl>z1UPDe@p&c>KyEl=lKC8n?4j?^|2!ZTXsx_J4x#ewjrxFp$ZK<8*v7+XE~E~gL1S-i@jD1Z${<> zZ_fjdH_r>7OUgL41uZlM9i?k*Mi`X=9ZOUrBPDbey%DS^bp}{Y_Nw}Bzi~6RmF+7&N5_zq^@U4F z2!c2Mq`S*+MPG9%?^DysBs*;?(Ol7QCsSZ~>$~Yka|t@sG|ET(7pvpW>T!OF1nyXf z@72KDGDPp(MgQ9ey&fC4!{YL(g&7c!U9iL-l-o+XD-@AXW-fuEL;kXH7*yhmp1OhX zI_hB*oYkUy^0Zx znnqDI=}kS}sy#Bi!?Xajc^&@m_Pii>R_M}c%v_UL1v)Ri_R2)H>AZ__lf$1+*b#1R zW(|*F6m7Yl*qGZtASYQwnGy28))P%v!S3%3r^r63vFi;UrdTcfx`0p}q7lh@$WcZw z=}&&{t++7|6PCkNs-~t?v3S^i@qEfX5=dCV`-L@f5s$LQ_VF#w!B=-JWrkFqF>hN0 zKf>{)WCCWIAgL5ykmrFP@tSVn+9Ak|bO86JOO1c}-aP^zs!Ny)c}|`k@lKc0OqVf> zc0U#x31fN36&|DMOKMA$h_igLSzbL{1s1iN+*KZHah}kM4xmnv!k4B|3k$x+nkU9Q z`EmTlZDTsAa&OqFB_eoeqg098!2eF^;_rbpcr*=-dy(6y+kN8P+J}#%?ar15!yu6d z29h{?7f}uTEUA8E>NkxW1s!9(9sv>lKBe7~L$-koLixOP4et-x!AB8`3u~RQUM;TC zA5R@?t^rY-si18Zk=eUobvu_u;Cvky-A>U%1vu{FqkT!CS}=#SXF|zYhX!;dq#u<$ z^^V#@(-$(As8{z@n&LJ6JMgw}d)soipP?2#Et6<7H0NEhY5GbvndB`s3LNh)yDgtC z=H!-#WW;lMB33VIvfOJG-}*WtQA4OgtUnAq2dxUwR!?rd?Ors?sRV(hvpb}6_zqrr zi(PD2PGVP1huOv3TEuO3gTbp&JDDePYewgQ;pk!}H;ZM_E~r%)&u*GK$#T_{BzN?+IA;<2 zEs5D-OA}+nA*rDb>YK!RR=M4ovV?{a$Ch_(8I1)p_`Ybu!#?%IFUY_`c7upQ+ZX`BZ`!|7X)u(Kz7&w2q}IU?eI%7$DdqVd%8UV;zm(Z+Ql8>Tx^J4ad%f6(GmD z1=>`na--p$b{~Zg=CTO#+OJHasKzD=Rc4dyA!=R zyfW?cYZ2D2YzEARvwuKmis@ejkRjgYy@DstBkzq2#bj$Kv0>8dh= zgCAngpzW9cwDP!$Xoc`|(HnfW=J8w>M*{m{@#;}ycNi7HV0`$>y4%tiU!1om-JUO+ zuUQjUV&{dO8P-;bH2US@U3K3+Vz?b$DnnHfYlq5ZVcAE|6I1W(U(qYim778pI=oY8 z+%N9$FQf&8WyPRu63AR#vIewj5%Wk%;4d6el7PA2;e2yK8GHR^Qdxhjm~9qRWZx-G z9t}A@5rjog9)Xs&E%A{EWGndt*b6WPY!-vewxAjNJ^WSGFeDjc@WGH(sR0mYdF;pV zpRN@{c_S%fzH^$FC71kAVMcH9_V*F8K4g9b_r-qXQI(9gtj)yHus-z18PzV%zAP%+ zW1WF!sdFE}%svd66)@>5XYj3`s8Xx@5v!R;seqswcIGk5dnx`pjkZ0Trc8;_&2PrH zDk%_`d1}4LmfzdRpPSSv4ZCkPloFCW+VAzCJxF!&O?U9=PG@nfQ;)Z##aybb*l1l6 z;n?w8v-Mn8>#gg?+F6@WK92u}l`v@Uy3LS*NfEWEe&SS+9>*&J&(@>J@{q^M%a!hT zOEbPWa-tp?wN9(ckdn4Tpr34EdFEWHAmr(Yg$l>Y>6Eme#0sHX0;l%mW$N}fu+@#Zsa%7=y)0Gja=jdy_XnjCAX0IwHhxbP& zLd&7c7BVyuECipAC6{(YcDEETgu&oQl$L&=g)6_3?5hnO9jqbw@|`yC;!?-)!+R7! zAeCWE-@1(ZhdJrkGqMWRKb*uDW|NT z>2f=>9ei!Sr+!$u%d1}eZUWi@eJuvcf2@i{-^5~rJ&RjLPDS{|Ji{`Gb>P&xQhE!M zZ=DlfeUc46?_P&R;v=C!6M>@p7{vw5Z^D;-x1tiOa4q(Lft8w+<=Ku7s8z3rob}- zGiwjZ%l|SXH)fis1tlD@eROMS-{HK#>TR^(T>I1}4+IwSs3$fBqEFST$2SFOM6jYb z`tkCWfSq-&Ek(bc&B5=FH;^1SC~9|wRK|#c230+f)LudawlZO!;j$*ceeLm>2{$(e z3?*Ug!ll|AyEVsXaR2GJQR26SC~l|%4G$K7yzv74 zm9@&Ksw&_sIq_1|Eu5Y6nXLFHz>R6{08jd!{aSW_t(}xb1FR1p$Bkg&1CqY*Qk+DZ zq=pRnhCmH!IMram$WSy-CLd_2jG?h%y@J$ePY&PB3$&f#Kl!&7%Dj$Nic%z&GOfG8 zp~nRaAH7>2U@9VH;KVIa&`-To5m+p|H}7Ed|?6&*E`;YuD?=nCBF^K@%- zKgL!>rDNOwkEyS4i}H)Qr5h<}X{n*RQ|X48p}SiUkWL8!0ZFBini;y0?pA7slm?|i zy1Davp6|Q&{s-s0=bXLw+Iy|_ayQqk+uQPiY2A?7!^_aP%{7NO{qTSDJ z9q_NCBrkuIp{`DD_IK>U9XIL+CGiu=u;5Jg*#18FSLEYP=0!JisP=D~3H>a!q%ut- zLsm_4M=Sdl59525aPM7c=)Wsd80={5*AIw1_{@1#dd%ADtvon9<@4s#gnfabU+i^e zEby=cSNt4`XY(YihZE^M-TrI+%5)vBaNt5-yHI|SRC)W0@33>hfsVmmi(8ZiErQrU zW<5bt!e2!OnxB~P^p~B)#zI@BdQ(e8sY4y2to%^Ad7esm^kFk-5z|UZv&`*C!zf=O z+4(pRwd`g%IbLO&GLkt|qcbh=6?83h&!$ZG9vzU5e~HyjI&5}~V<2~8Wq%pU7^BgW zNdrp+(@>Ns7?XEKglC-XbkujroE_wg;I>q8F3dhl%kGEqw#ryr`u;P|SxM0P$6~a` zU9PRQH#EWaSuG>wOD+Ssxscoc;1P0U$^w({R#aP}0f17Kv|n8<2PThdY7vsg-FZ-< z@sN`Or-AQT2I)rYYM?Iw*Q!VQo%x3foHSRG`oOA_*_hA8a{!6?P^LlT^iC^&iOUwnh&oVE#gtt{7ryKKl#Xgz3<0m z#8d2mbL&l^Jw6k5*DfDtMIDS&C`rd)a(!!}Vb`0zSS1OZ7dM*8;y2%DmT^?hYpb_S z`e(?)+Qa~_s@Y#}#~)tzJJLn`p*ygais84#XV-5^txV;K0$=1={)_nodiDkR|8cg+ zSYG*JTGy1|>0bJ~ymCo-MP8Qb_V0Tp8#&CF6TS>jvMZrJ@(?r0m6hOE^>fpm=3Ld& zs^%R5#Hq-2fut*IVeX(eZqp z*E`^i<=-X`Urj!Z|N6fE?_;1lsptO5-{4R4Wps*f-&U~71`4qYKmq#rmbxyuBiDKF zPj^11*jq;McarZ(-LL@9g+|5GdQ*l3`B|ru|Ftcc$-Q zrsn)z^gsgO(s@SQP^-6T=rfidgyVvXvu>P^c!8VJ`efT_?B;T`hH0Eah}?*O#1r#kK3~o!ZU4h{axUjDewMn7fsmHM4hLN#f79rw$}^Oax)6m#b8ZX z$6PkM2vz#*ih^ zjL4a3lCu`tjFFS<568!I5b8@5!6wl>Aww{j!EC`9C2Cndh!9&>r-El5V3tRZy;uTY zp@~4)snOovE70D##EV}M5fV@D$BX|ud1V!^`T}={@t2TU?L^QTKKD+rm&}Bqf{4Om z;p6(#I}2>CgKLF5`_Uqt?8rP)v>2shrkZyxE0OnLsnz2U!XP_K#HUmr$2dsFU~}0? zUFjZirjH$e3lCxc7lU2zws$bU*_|%Y+!L2!Lz(4M-{-j{k@USvne?OpibkAttEdbQxKt6+j43V(Jbjf`^$ECt9{F6YV{fd6YsMH<$1ZSXU* z!@}eN%1K$>v9Exltohbu;v*fI9?fWwLC9Yyuq+JAp9bGg`a(J85YH{cp)VgE4tD-F zbHG^eOam8D4SJJFr~73WqR`PzbV9UNoGoB)3Hk(jeEmq!HR0B09@Y-KN~NGZ@qTl_ z>h|pc-2o|_XkcmK=gjUaiF5m7*`E)$df6tZ=VQNlv)uXj->=8Nk;}oX5i!UTV@bDv z^cOH*{_U^JC(!J@zeF7+k4H_IXmIJigUlN;9O-NYUX_eee+>i|U|&HR`u%iUjKV)F1C|Ehq0eHpwwBkB?PNNgr$PM# zLCVBn+d!_jSGAHS#9u0b>v~*7KRPH0@_6FSb=uiNT0VP#B%WK7n@~RkF1g~huT{3= zqFNk6lf%BE( z5oA4jZh`GVUF6cDWF`77lpzDY%lNu?sN;Z$MY zKKR6q0Ot19kQ@R~M+CKrdy_1`ruV(S;HrP8UUn~IzQ||kV^;x_x&2=+fJu0udRXf% z>C^d~25GGw`O`Cm{lJ+EmjJ0N^L3I9mPEzfm3!7BDerE0ruBR)`*JdGKz58IPZLQF zOMp$O2d~~^9}h3WZV=W894KIEqpu}nGM~oztj9_%{W!A`rX3YQ3sNcuTs(Vp#yx-dt7K>Pk0j^{vTwyi z3IQSWc9W!Fqw8-RqcScPR-}mK3A0A!Ixd;{*>kFu8oo+%NrZl*+*$FdfN z_7ZjdqyP{AbaEcU2a{Bv$!28=!BX{^xz5IkTrL~7!=N#7abh4_5ZM`^Pbe4u{-DJe z(z5FdL~vejL!W5AqUG#V$1ljsQ$$;fxBSTn-7D}mcnn=s?Cy{B4_6U8?Z|9AD4w>& zl!|m{(tft+JuM9B=38R71b#BNA9MLZ|4rp)ApmPFPUW&ph|=65*3y#YA)D;SCxljc z9c+gNtkd>6$w|(nvLq!CO$SDzq>kLn#+Oiyi9h_iWyY=wX5wG5Wa{K;jNcV!NWC0PJ!c&?kdSUl)m@rIVh6IrooP zj@HUrZ6_g66vu0exMC7Eo~#NuP`SEC+% z<7-zg}1#lG_-BcyT;;RdB$6K3;ZvbMgVtU9<MaxEg1akE zsThGf$kfNPO=$Fj7`DGb_|esfBMWECW|1_xx;cnTj4;g0Vi464Jup{PLbd3r!tRV8t7Rn&g`6bQ5?I;= z_;H2QnB;@&Kp{c-17(3B%=*X{^ccZ6;KL`JCc7L!yD$c5|Q)`WM5ODXgoi z^j;Q z)5!YujL1d_^mY&MT&s&nqO=9#zXaOsq5lVgIXAAyfXh;@{3#t#aGSoACF z>73-z*}-~%8o>U;LG3$2{Qi!s^K*h5)&L;Ci;Pk)6|90o7T;p3@G$%e-}gQMKjg(e zUMx8Q2MakAWlnAo@w79Rx%Y-|Q#>j_{J63wVnWpHz2kiy3^45~BcfJ$`%u z(sHcj_aYM_bN+UmMWr8B03RZTO1_AdblkYVfA1yz1JzNfQV>~lBPpcrCDV4607axc z;#KBb9qZUQxYp2*c=)I~7CSKxNG^82pyLHM#fYaI$Vip-L)48vdG#^JM_AD^uxY7Q z7=OSr=L-qLt1XrExO=<8ew!=jllC)Q~CwNkqYkXsWX{TJxcz^_H`;KNkE3j1P}5j>H4id}mvmWJeBU zt;L~hdgb>bP+f$pmgGtS5qgSXJ$VNEaOs~^W=T8?9l2TC&^<5+0Om1%^sF!hab>)8 zGbr8Ek!eGA02hwO3Q{ShE`F5tr0rd+X{E?@SrG)7VEc=no1c#F*Yt3~xUu3U^ocxL zJA3zay{J75`*9Sn(i@5wLW|t?e5Z8--V-*Q*0rM#+vy>iCNwYpX7?q#?st0pksI&& z%&IkidN+|~v=*vmHJ&P^^C6np6E;vTYSNbHG5FaId$qfTEU3M}rSZ5-bDaG8yM#9)ajN5#XoJ?cdHkfO@~8$Owy6 zPJbF1#G4tc!mR&z2Do|)H}=*CH7bEYsjsaxj_#pDAyADRGCY@@W#rok|^)7|Z zqEOp2E}-GN3jdEVo=8dJ0{b(}+i}E(9R3)D&bp2c4*eD>tzcg|AAO(az=fQDc?q{; z{nwT?H#WDZfR3teefxvH0Qbh@D!;Jt`z{y8?V>FMUFsduNcti#I3M%edeu?+wD#Md z&MuklKSC<5%)?=@1R5V}*FXY~rB&v8Y=b=Po%+*NC!9ECoFEp&RylJGP--{5%R{1+ zT_-x)W!)N-vAfRuq8IUk5r&FC;h#V8k;qOT}IV4}ut#VFJ<}LeJ!4| zw?68v;an;;6mRH##xcp;qs0x$OI11$H#HNcH`C_fiIgNv7PN{8QM!Rjf@aZ>CdOT!*y7wVgl6=UXqx z7C!dXnhlr*e_M`3S~|3*Sa8h#Y^tl-k6Zw8Cz%hl&2bqmdea$fwo%p(6^pjAi(C{H z=@Ry?FaJMa71?uQ8#d@^)6S(wexQp6Yf%(PeX2?^oVh;y#IA!09V*V$zKv!2^IBA7 zzN!uid%=~4MF!h91{VFTIm;?To3v>Kt>GK*XQaDo_z~gorQl`Zu2v9p54E_MZj?+Y z(rbkPegv(%#ZrtxrAvU%m?hhff0~<& z%`M0Mn^y#Xno7?FpidM)oux*A4|SrXYCzGQ;A-c6<;Qm_k1N#sJ#N`ZebxU^Wcl@Q zFuXjvKXpZgr81O_k(_qE*Eg3H)ejD02KsjE=lOwatZNI$uJf^IfK55FY5Mtr7{1Zn zf%-zB5%JWHWAJVS1&JvzOZFw}z8iP(5)GQz()82b&={kYZ$%z zwAB+T3bT`lC}K?oEZvSirmjN27uD$7vmD;-u+~E^fl|2aOfoy>qv{URkI!*_eHdDq zP%tz%V9lKkPsU7xr)8&yj&DNQwa+pJ(|KCA-)zk9olu<)WPqE{R6g!6w}@Izc8awA zols~oY?l%Ol4EdriX*WGp;*b`loPIS&I;+gAA?+rTz}5h-{Pv69d+C|HhmrIb;Rs& zgd?akBcFR@+W5Gqm>LH(t(SV{qFFvt!3y*Z0<4%K+(>(2QYescd|=To-{l?F6-?>Q zBJFB8jM-w8G2>i&zrlujuO_T2e>-;YA;<(gSn9S5ST%;qbx^|W=yyjm=1QvzA4!_H zv@mQpf+Fw}AZSJV%S-{mIf$e_%J_AY&z%kv18Us;8xxYRQf5Tk7PP zu>ONou&3Yp!^QX$MI0Lk%Kv(qOr^{TX!e z#{{E}TeZSQF!2?#Rj%bd2H(FS@-Mq2rfBiC4t1qz&J_ny>$s3h>F&TlkmGhaBJ5+n z9%qTX{rZO5F`oKBQYza2@Mr(GDmZF{#21aPe8c6(g2`k5puxhYg*OvV4s*C|Nh{%o~H;#Cxkry_+fop9ho-6Im* zFrYCK!oK=)<0tvQ>K=3JliK_1HJlS+QS;^}R9*jz3+)RB7wPAH6XzX<(4XW+p(7bNYz>v!KkGb5sf7OfJTcjX2uY-!MZ+dk zB3^5VTAFpZTu=rzBJxI$qznp#>_6otYu;A;<_H9rCgSiutB7717D;TDZ0b^|LvJQ7 zk^E-bZ?7#tGIcQFTXQ{Yc6vjS;3wI#)M3i+OOwHd44L{^5xPKAJmzV~O+yw|(ARw- zau|RB)QYvbP*@gTiU(8!7)41&a`zsDOTIP~=mIVwv0jwlW!LMYH^xPQi2uZ?6i* zgagr%amxDOVbfBaHWk8{%LXzR_Ap)Y$pfcom;!++zjEYa6~QCOZiXn{*6n@G74@aAV3XTth=WNKB{KKGV;NIu-w$W&P2W4z^h;5Q!a4 z@i!9AdAVTET^91>%^22pl--MNUf!8#aA;~)(Z_uj=9(!FuQrRf11yS7x|b|$F@t5& z$aslwVsG3!H!t6MSA@MGn9B3J`VUXMIjB&fRk28OIt|7w@)B)$8{M$keKJ*1 zz{m*P8gtjkSYyB3umR*f%?Gvlk7>jYYl05=JBu< zzIU?0bXO&rTnDs!*C2JIme7tVi)HMCjsxxL=&@pp6rs%Ow#uFG3@sq`(g^;dos4#q zy_U@jmrF`gKx8hVmAg!^h<#`eJIa|6mF#*&^r4`lW~={RhbVY}ixgt;-+4;D6K+4l zut*3?3UdpkdQ9WVHQ#BkT0T5X1dDmQ?A_}qwmFAUfp+!)Q3Bgho)40$ro0#QxEgIV51nS zv9>*zSWA=aQBcY<8ShGouX4SP>n?OGXDwJ|4tVlk+Ytj z?+)M0-xSzw3Yvp+fsJ)oHI+#Dqe6*|GUgZdgQDO?PBd+?IljvhtobvejD-|MM9QN! z;bI8`v%bu~j=TMqRLwyTgO4!8Ya5Uo4?8#{uk6<69)LgtWa4KRwl<|pUt?DNz8ms6 ze&b8GNjA61%$rxxeW)D0DLi3EYSa5YH`PK#Xdf4r9%MwD@_XyAEGFWvnV3y=r_=8I zVMMd1x6B0}OUp5Q@>dKw+)_lcAflcN$d|R#48;?_3CXPNO%Lpmw?vTbB^U-&Cko$Q zgWy>7nhM0f$kNYm@9^q%MF-l`yDf+~*nKH!&QA{R-vvdmcKu|Mij<7MQr)vz!ORf> zi#QLRdYrKar!0MH%#z3Y7aYWL!}HfhAz3L8Ieg4n3hf!2bNxD(o*E@SkY>WJ@-3;b z9W=VMC%ly`_F;4O7on_US$U_G^*gFH3;I^S&RARi{m3PFhkq*Bgs-=tjTudpnwi3Cb-K=XoH2 z+(Zsbc5q>{}YizbAmPJ)+I zL)slSVbPXqxh}%p+vQk1Rpx1E$@wO`L8k*`e}iZSm&+_Vdx}jiO0dlT6^y>;ELF+< zGqWi%JFj445M`4bgt*avTXK{RFBPaM*4jTx9LtB*`;R9jIPqw{sXlgO?)wr9 zhfv65V-mS6a8dhfqWiG`xUif;FWv4eCR6>e{iQRi;stt%e-n-Ech=HvNGmO4bDC;H zB)?Dv9*Q2SdG>c5D*yI!RK@8jK`~r3ye#33x9GcT9ygNOgS|?~e(UH-aS(-;3i1ZdU#IL8nGzSR^uV=dClK_%S#Momj_* z(D5y;Z8$Q6-GT42y&_Rwba+KL3Y;Eh_{lA>fKCp8fyBkIYhNyaO!WSzG?%TpxNfSk z>t9KKZV;p_nk1cpHW_$x@6PTkf>Ps&eX$cr$VrTUaZ$q!XyzpuyMW_=Y+0~|%G9mp zz@SF(4?6Tdd!V>009nT3_sYVQ>ZlLR7;oc`#z#$)(=+~twx5}B)1E;9$%)*Rcf2iV zu}IxmhH_sR=|b_sgie`F{|sjy_*W zdj|{Ry}CN?S)AD1Ah+Wsh3Fx(wTL9YuOK19+1VAc+f^S>+eqa|HLRvPF_HUGfj+SyBldgwD~N}}B`qdw2(quY%#?%4A2V5v z3R1_wA9D_}H<(pYUHVNSw`(u&4%)yhM1V62iR6hX<*^paehSrQwdFFa+UYib>u_+n zr@rI+DU>tTrRZanC8+2Jw~O)BYA!2Ms*1Ww)M~0%b^4Ow=+hL^oK=$}*ytVskoO{- z4=$77tby00o$!SLLuZ}3GyXP}&U(Yn>N{*V34fgTm|E%o)z1Oe=B_lVfd~)tl1(}p z%gbSBn{eFQdV=(4(< z)mjr0pQh+xoI7$YELUBL1z1n=|7?^ z4tm{yO4|XgLbFLN&RL1ywJY8*bg*HD9ePGmHg)4sM}e-5>39AGM%FLA&G8>ea-psK z_>A;ImM3=yOxc;Y3Pew$o2#-8Mae=l`%5MtkG1VYhRs0_O`Dp zUuD+7a4xd^i6=p#M&1bcp$k*tw%3`mDFLUBD-2_IB`7)d+w!$>*+zbcX zW|n$?k}%NZP(+$QbV>}~&9J$$rAp@SZ9QM)lOw#I40=YyNthKZBzYzUv1Hw+QzNzE zEp2eFHwlNpHIokT>Z>TE8kvH6hu~usWh^DjXfrMTK=T;Nh~~dq#FPZ@ZSdvNg&0GMLK?0=w23r&0TjRw1=ya(Ghh5Pukcz_EngF7x zPP8eB^lVn7^DxF2C{u@$gmBLHP#V=0xgraETJ?Pl)!!AWB5cZgI)Ny>Yeu?YaD4VN9W>PEN9ER6uyR+hclhtb)j#EzDtC|4R@YC zIrfxTq`D4KmgtAe>^HmXPj5u}(eP|3N{<{QsBGhV$}-F4w2`fm-=nqKn4w3NqhiCN z#2i+)cwN_{{!MP50FuY1Z|Z?{J^lSdBDM6oA}MWXx|~vVYCBiuKP5D;-)B54AE~0t z&uzc@i`Dbr=;$%^cUu$A2ap|b6zlJsc4(Nr_9yOta<-xdnQ8OxZX7ky6KvnKpITZY zOtskQ8`@C&vk`SKO6%*}rfA>D=8vjtkt|U1pd)CkK#P!^y597R$R+9b)t^fkL_Y1* zCyp~@i;aIcV8|L@u(?b+JK0J4C$@8h%{yWjpsb9%&ns5?_~fm*Fe@gC$7=8QC`6tp zkf(!h$^8ZUQt-p?(*s$}rEcaVE^vf1(KVk| z;G47V$xz+UfXgGnWFB`{LO1XAUJ?Fqnseh zId7xCm?XHjOV=A*CCOw+^qb>y+AHslfpcP16;;zEB2@q1;GX)r(x%RiuZDx6WV5TH zHXnUTOMOC&j0Eci+Ygw;97> z*c}JBjH3{e3ycw&oMtQ(rgvk%$*e63cJ@?+8NfP^wo3(Pb?@T+$_Rcc_2l28qQdqt zmEVPtzt0H!jVeaRvI3dPT~j$zxtaOfpAx zeVsp^rsZFXln{Xb7R^lV%fmlyn^?DQXW#UBLX=$kor##O|i_R7;OvKz`=Zn4QSS!WyytcNly_DCTP?^c`_|@ zw^8tjP>6YLCSdg_58D#TJ!d#o!*@6)0I29|9gGCDEXd;j1HD>n_ zDGdWY0prKE{(ePqI(r0 z2z}ow?~$By|J_JQ&d(fOXx#qx5zZ8Fwg2<%{0H{-=N&S0&wk7{tiU2HQyw*e)N$0O+v7V*Ey8G(?E=5#%j6!#*8kjwpUoTmOT8QGutUS>f=!s9_6qo`)t>&g}W@e|{eEh;f4m=5qEB z&E7`!jKyyChziSC*}tK-Hm<1gp2pfrcV&u5Q7hn1PAKz9FnVb+DVkwR|JkPZ*Ui^- zwxOO9FnKOa3!nh=OmKl6k9u`cjOQ2>pDkC2jb1y*an$fyuBG{PlFZAuAVYc6GL3Hl zdU1s9;HoffrPnnpiK{|3becv$#d0!(% zRC$p|OHk_g(E}`Ie$l2Pp1_vhciFf>NGUKXhY@82(4g)h)U@;SK(3KQ)XnwCUaLB6h_E2hq;Om93JgrKms9CRA44EITQR1%@9S6l|!zU}v_6&N1^Sb;fN zD*N9HG3XY?w>C{a2^K`iAl}1U>JW)97+#qQLZ=5>t51`M+eb_EaZf4a*O%8Hse_@C z7sF{}gs0a#4>k(<1wRy?j`~>69{PS()6DnCyYxit%fHC^&_+um4hmJ0<$n?iFx?>X zwteK`oOS<2Pe5yPHFG5K2CRL=^H1}bnpHRWK~H;z!rr%Na6SLkk$9ABe>snz1R#*dOd5odU=nTaLdt)JEc_{fY#|9M3+>Kr}a$by6>aO6S*7U z>d&Lf0esRJ#@te;OH@$#TRLCNh1TKKT4>2T&tXGYXUQX^6)4?7QJl9x=MnQxJO*;3 zH|Iv$t$#f!^r?$NDw>xa8e&9v6^!A^A6@f)^JTEak`fU+89kQlH$z+zR~;5E>!r7@ zI|+`m%-!F*lqhJ)A8IEaQ}*p0J9vcRXo+93XbYm zq6|p~%g3O1l8XycN_oT(QK;Q18fXW35I~6)ok>j>G-UX&=OBOx>MiQthrIohR+v8F zgvaq&!QOb>0G#e+UOP~viq(H;%qe7R$cdw{Y@L@Lz4AgA@eeD=M6#C1V?epL45`oI zMlYF(X*AUYybaim$Fj(mW3!aK|BDAKcS)lv@;2OVBc7M9L9VpPF#F2yP96QX|hMr0?^BGu5WHag8AJ}=|}~oVyh#Xj`h?| z@=f(C)CyF~j6u;;Ia_NzrK5PS-vC|r9tU82Ew(H_Q^uP#eGZmih(|lUJhdIjg}J}| zQ>{no-Qh)*_YXg`E-kC?8^{t<{=X7aK5EDOC<*I38b#jA{WJMJ&RgzgZ5d zq9-Nw-r@PrRk#aq)5}A_^y$fMWx=HT28)Sus4jw1~pk0JH3&=Laur zlM^L#sjM1Z7SY>{2IBsRb}o5}%3ZbuOKMqy?Wc#~Y5Hc!iD>i3hdmU`w?}CIc^`ac zmF8t@eO~c|aAU!2hdXvvqk)L^uk|8bZP2G%5PCD? zzpFv`;PsRe{P!^LY+`~_!mwSNBKzX|f0e!Xf1h34c1PwY6$=m@uxKgGcWtc#(;*Mz z7{;S)e;vR-_|Yx5{{7G{36K+Q(6@9J&Sa$JruQW)Qvm6s44goJS|rxpdHDm4t+Abq z9yL%Fplh0_h0^K@vJt66+r$AQT+z~iovnUITf6Dp)uUK6-+tmqEx7LK7|ZkD2v~## z7v)?jHyQ{lka6U$n6pAIU9AdS;&WqFKh_OYEI9a8QVj-KmUYzCT88>OQDDNt%sR{+ zvdKagbxzV<3)B8PjG4r8qADz9JRxs>S)MTP!{-P4Jpl=mR_Vt1>~8)%W##Wblx;$h zrK`aciq6=6o$d?BQo5Xr#+Oh7RR4M9h zoDC!u*KQ5K!$=%ZTxyo8MbZsgKqz;xzrf+K`kBF(5nNA;R3EKoQ8)%25+M398FH@# zQF4T6YXcA5V_jB2IY7;SvxMa34_g2;CF^+07A24z6nDMpj8>{X$}UI1--UN7IjXge zi~6HJC57_#xAEcaZ+pwzqqiT%OR@Z3oFjuL)DN|r%{rO231u?9& zWz1h0E{qPd+s6q=mDl}}zd3~qsmCyUn0GvwK~ZJxIkL+ctIr|4Ff^eWZWJo7*jp&z zc9X-34CXJg*L<$22Wj2sR6XgS!q7C6@Xv3pvtr)g3lteM_2W%YdfkKQ=zB~`>-6aC zZ}?2(GU~T5+7AREZcz%~fST7?>k|d-+Db|bq$*UmchXD6pN7El09Y@di_p+9=+xBA zNgnK1Bz`&NOE|$h%-dg=V!e2qwNo~x z;qCd5+aVTc@?sQddDU=`$NY6P959v4{2ft6rQ4(g#B#&Yrp9?Yj|StOU4H?Ts(bSi zbpdBVJW}e;{>ZQpkM5mhcL$L?IUCARxaX)Lstbh{x+`^+li%r$ zjnOdcoIg&12)XC+1f?uf*inJqKDQ*dvuB^G$39qBN94^R`#O1Zwc{sJgk}bkJSWs4 zX2*ahZEb{QnwP!rU3HGsEK@%QZj$qI3@XT@R1Kws`xOdt`4zPr%#vCxoFTzfcKnb^ z*Pb#FPg_B3`Cu5?7eUCL2hIo}KSKEfuIM?^*jz^dGG{BnJ29h$hTGaw==8FpZ?xo?Hp`#D-~y!_ z(=D#|NRKQ>pL~x2y)42~XN~vyY_9(-HahJ_w$bqx`!KbmJ?Jg^3*hyL{Jnj((|Yq} z#aY(YT1E?{WjcD3Gv^s=2lg9>YgKv7WmWsXkyXOy@)-d2vIjDI>NIr+1V7h`#uu#> z(LGV_E!14qZdmCjJ1x3cUoxb@!3^Oy7<()#ZBA9vaX4E22rVbNo!2HM`eT9AM3`1^znisV3`+0E`>dWD7)2o7ATsrMGl&iW!J=zRNT)AW>%QZG=CrFGo7P* zJ{s>Z+UCv(5G)%Xb7VNpj$COekxW$LQF4ucAfkO}H zT!znZ^j*WJ5Vo-;!H4g%6X7)9;7VM7h85;eLj7e$-d6bW(QT)~+w@{=s-n+mmO`1_fA)~}TeURZLL2w?f;bvAq3V3f zk^#Uq7UPdt7Fj#6NhkWVi-tTcrUyG-?V!7U^fL6gwC8{?N{dyu3Qf1#y252k;qv1I z=OoXhCdx}l*&*jRS8;Zdd48#VXnPL>^~D#OX?pTR7wM#sb%F81Q2jhL_wssFE!Qz* zp*xZ_*N7zmHShKynkDA|Ewf$Yc$En8U_K46*nVhBi3p55z{AMbG6$ zEkjkeNP1kH<$i-U7I?o_xeP_lmILHHSXPwcEl{WkATUD|Ta&uV_WTCEGPT$MH>Y_E z^omXzqiM^5ObF8RM&CDtP!oTb9n_-aZa6-R1wx;k%uVsk4=V5A|Bz;8)P=<@X_gd# z?2v{3*CFd+44`Z;Mgn^%J*p~MkaB9Sl?1N0c!*o2dE5S4V|5}xO~2pJ?uhv3S8#3( zE^w5iPuqYR#9#Axcn_}*U{A6GCGev%Hnde&0hCqEwS7@w7w)cG_RBRf@*rvu9Shwd zD69Ant(AVlK8r7g8^BE;v*P~XBD|OU$s?^z-5EXqbmq|dw&uKL^t_>byQ$RFUQhfF zy&3vr4RTeFa>}dyyv~e$jbS1tK;=sY^=IDl?{a=>-VE_fQe!Emm zct-hd_E?ihPsdlr)TqbWvuOiBLvRx>cvk==Z!Dpi_V;SvQI|nn)mC1}pQMF{NntXD zk?UeQ+0)sp87R$)g0#@Yv3kEYkvTj6rfAaRR{z%{3w@P8kc@*jIQgO;vh1Kgt#Y>$ zDg7+?b?AQUX6XU-b};&2dvS4jCpx;_t{PX&4!LmjLgd2H`Bhg3kDUrRH0o>o^^S+& z&*kdRtYVa_yGiS^>7>VGI&;7`cpRrOYfx>@HC4G6Fdu)WEwl7AG>$@Pg|58vs zEfW5UYj`gtyGF2bp-eFIM%(?@JlO}}k3jB?qE0M0*!NX4OgL86c*c*_xgoo*^i>qx zo@zGnv%8qXk)6P2O<4Lm8$N#dF1tvzmB43@1U%ivsw&N5m@tc5vI(}-I3b$!#b>N) zZqWAUMci8iLTV?-;3SLdG9+YH>?F?BiU(OVnQKVpivek<_%z1}cPM)yvV0721z*y< zy&%;!J2oTvn+5frJt7ovd=%zpKIsSmdap~qnn=nLp6;6-law}e5Ms?i7Du>lrE}nS zlMBHFAi}Y|&|es}Vgjt|^5i~yb}Qh@9^;IgW`da($Y{Jn)MGN`u7}Ejndsl*rjHwg zK-#-h@A19L4Oj>vJFIZ40n5vfH*E_Ee5wPWO+)4IHm z(($VRti=8EGmikhB;gu@;b`~u|3lSR_(c_V-Oi8-0xHrHqI4rAIiP@a&Cm!)4=If_ z2&l9}w=_d{x6~lb2+}nuLk}PgcX;3Le!qMFgL9s9&faUUwe~ukf^Oe^736U?Fk8uw>{>m`y*i&=clFwO^$HTGMk-Qx8txRr5tu2`4=Ig&zLM<#Z62BS zm&j30N~|gY`c4xxs6g~|@JeDlM6OM+M{4yPT}WifA|+f_aT*A06>yGKCGo`(i5+j=eL5Pb{t zN($goq)WDZKx#-J&90!T`M$xHh^qdpEPe`DC&)d`Aea-up((E(&?g4u5O`qz6EPk5 zK3O?);zQ?!+M@mFrO%-WQ5_^&aTQLaSg+^EM%A0cbt%AL*-29)VDyI&=Q>}x0IqY* zf{j{ITou)E)Lz}!b=0QMZ_qh+DH09?okXTw1lq4wO{3)(uJ6B=S(w??6CJ#prv~%> z_%A0AICXK}c5+E^?tjfrjDJu6vLaLiw?S-wfHI9zm#45srB7lA{dc;$J&&6a=kfeB zQs?Gb*@qlx+EV|c7Pi0KIAxRr-45jsOiRbFs8Q?X1;T|6Vmq2#X4+ffL>(35lnP0s zSJ)<$S1t7okM7Sv#S(swvH~&~%|LS!+^LGKGBTe6fON<%wXhk~%L2Zk_JZYucD36h zT*fEvAqLM^hF-!gSGc7t^%ITo$>)ap6L2NVkGoaNR8nsRW&)EeZm99Sczx?_#$yEaCl z@q}zs|MaWnmyB_ul(ECc{yj9v0BXwp-ZbnfwG@7obW0I(aXW7DtH7}Mhz6mvi13o@ zY<>jRJOAk0?#Unx5S<4?rl;j0IB_kKVY}MRB+#y&Jq~}*^Y^Xy!HFaTYbBV&UTtx2 zP?LS4>&D5eD5}CM|M3>dkD+XPdV0>^Dzn@5ZkoK?jZ5fzErPRXv0eE>58IV5y&T$X zOeO(kmw*~tSGVIdzXl9M_4>9!<=drVlwe^-x?Z7}A_aMI!*9NTmrr$>HMxbEcC|cfmt9XjPQGWTQo*AAcRYhL z?NC={C;L&dYe#SWSc?jeHe2`4pfiu2c7BNHO)Zcsi@sao|r`%#w4LO z&onSwm>E>&gj;BUQt5?8I(Y9yo$XM|tQ3yl3IN{^p>}73msk()w+w;HQ!8qFXmfEdNwhx)=Y23C)J~A66&pUqaeP=ZzbkOtj(fL*w-NyN= zAY@--?@vM*$-A@BzdIXmmp1!5#8N!u?FUiss~lWT=wS0w;PX&)VN}AtGdv%Hs0HEf zq}k^y8&CLe{)F=OnEP0cODA42$Ugi%vx)Pu%KpjL z5A2okwY#`~U7{yFLUE)CbGEKhnjgtZwNfYMtJgA*7s` zV1<}ymOuMZ`!AnhbTk%GIz@;|p4v}f9Fb%vCLx6TVj}rLRJ$OLzZcZu|2GRDh+w$2 zMVJI^DO?qDOGC z39f<(=2>dcJO(5@P1M3SV;)A7>(Up)zk2S3c3`nTnErb^G~XgP*7A{&xQIi>`g@Qt zEo1yzK-mr>eXts*?uBUbH~ON#A#gg85(&?GUT-h#_x5}4v#NRhEgA13m#NfgK&pKg z*C)am5TkupFqDxj#;aU{mKxi~cah5oli7H*w2y2>vfJXLHe&62AVt(}h1dTSt($OeU)bbX|m2O=@ub^qZM-op?R_+#SKW%wR{(1#O>MSrW6S zDlQNRUaNAt4yl6}sl%(>ThrNRqRa{D3uLAx5!%kAmfEX#uye6ny5%nr3Fr`Q8_5&g ziBjB{x7mjU2CP3z^~e#=S#S1g!5;B`)JazRw5lgrgVF1fTN>ohnc*a|ci ztjV9hr9`Gqzk4ud))2h^Sz0W_M7_gpRSGlaEXDjX`q9;jTEG*M%|4&X2ovmA9pV4! z`odDMg){r5g{5GXDr z7c3F09E(@#WAvftV0257Dk39d(3lJ}2rqVO0%d=_Qvw9(lJK zn4XDZFE>MJw|n*hl-wuhD`WX``*D!p>d#(52wfR|F060s5t2Op z$~(Lq9iXzL<0IE!P1~}tLMnwzDGr3RMm?!QgJl3%oLS{^T&ip~?}WT1g3V3uYO6iz zF5R3k<(vhRwH5<|e%&hs9J2eF=qgv4guT3d4S-X$d}fAy-3qYMKDV%{1NtSbBIwF6BI&uE)+grAmIvO$Z{7A@yZUSe{S^I6Hxp`~BXg(x$qe-QBIQY*au_F! z#Yl4Q^W18G0pzsv-nwZ1rRXmPf7Q8lIoLO+66&f1J(3ZC%YPy3!O|8VOe87v zn*BLIoHqDRjGfjMse+;3L$pKSxFB*bknUzJME6o$*?Y{TfN9g9QXQWT zmV=5V=F@|j<` zuDURL+Hvf`L2C~mVfU~I!1=UtiN#X|mq+L3A?=UsmXF#2emB-WcB_sU1ZH;;I)Q2; zXsN(XRwjBS#5+_Z<9+@D1e(!6u!<>+aGmX$^6u>Y$3nrMI9tp2Jx1~aRv)6aW z_DtC4yX_X24 zPF$={y(^}v_M|#8JIqxJ-pG)QP{KkjTNM1}lM+gvffC&$cWf9cdOYy{uQGQfl!PX};r1#=cnh8{xL!%BhU{O7 ze)Qs}mw|KnYky0Re8nhn=fMZV!mR|CA7GLyDSlRVs=VB4gQqKn=w-eRKCUgcir|1L zEI}GT_h?^ZM(AO1n7F>=bI~7_2-7F42QoMQGnS`vadWBfiDWi&(=s@M)X%3nIf7Ul zwOKy1z-T8L;^*z(2Tl{rBK`Cul?u2xYf98-QT4xi#rl0V8pyTVS8buN;A~1LtYx-W z-CGB@6nOpN>*c#_Dy|%%I<!Hu=<^-MKcn?MPxMLF-LqxOC6NFwld%?`+e2f3@t@SE?7u^H7gSHK!=mH>4F zP><&CMNOWjfG;QDs$7An$Kx1Zy*R$i^Qx<>jAH<+ae{D96G7mE>rRh(MBn z?yG$Dq^;8g4q&NcU0f*aW5U1UgT#+64{+NyL`vG}L%N-!bI2P#F5+7$rTIh3HYOfD z;ThB$_1ElhxiF_z?(=4K3FyPt-p;!x(%IkZJ0(vNg$TJq&U$TpkI#O;J#9dPl$vOr z?ecsR#urQKYHOpcX{UZ=;E?DDy$3cgRUQNEI_MaPnrr(gwEo(d?SkH3NIK{Y7Co}bfC+(>NPEOqR)8* zW@e)|k#XqrbiVOlm`T!^(|!GkPb3EAcvkxHWdS-Lgwny$xa%qOTr>~@r$j_mp(Fee z>wN;a)z6G1v;NTSU{HRPNl}$7VD`7A zTTr>mQQCDy@Dsm=?aee$A{leMv#GbJKJs+X-BH_`_Hth7P-v#B;!a_yfG^W6E0IJ2 zivtSJSk7&?I`co5%O7^~$z@TP;DgVX%&$(qe^CFk%U%#NecLMj2D^%qO&|x;>i?ae=f=pFNYs#ST$P!qV$+o<&^l! zCkIZn2ICxrsfIwvd^K^KvUih4W#|&LtO9g80uOgFBW|aIVRQAaB>{e9=pnx5<-3Nv ztT=etXhrg{IH2N+<@^nb@at;G?+Tvb9c6j{z!%POZSx!mQ6@U|b0hHymR;>3c*AV5 zAn6Myqk=gh8JlDed6^&%pz1yFc9j~-TZ#*H7A|`}VUbPq0ilQcjENXwtZuBZ4&lz| zjnOeN*AC?kRYMK3{VcnAabNJ--CHP!43;>o1wzCM`BQ+@BInv;uCm{?C7!SU~OQkMQH(S_KjpZT=WOT_SDuU$%2qp}A8j3IT zT^G$AA`Nk}m|#TxI;mjB2ztg(-|d6QB369!#;9_}91z)Wk!wRGaL`eF3`WD`)w9kP z?{djbm&UfHHVovQCEo|$-1kt1?eaz&*j)OJFBfipu%srkf_xhDBghd4Y?_vp zuxujD-I>vDnq}Bz6T~~&+M;)-oL5F2GqjGz+p4ohCU5M2T}~pS3hZdr8#~1X6{!C% zGSiXB-;drXFb5>)oYKG$a=KI}_g(Ls2?PAKvv4Njw3KRnY{|etZLO@`zq%zE7tXbz z5T<^O#uBWh^Pi#xgrnzsAS&(kV+}K_`j;j1({R0+2;9IZ>t{Ymw$tjp4%W6G9R5Z1tjz(FECbbJq4Wa{1fsyNmcXnE-fcP2&r0 z?3do{&rK8C!G3 zG2D+kz<4hE+c>y`^sYTw#(3UZtxRhL$5QxaicWGixs7qG>wO}Z&Jtb@T<66A7^Ls4erRj zFzq=0?B|9KHJq2hviSLTEWQx!QgVgeO41~;EBs2r$NAW(x%EJKW}|}bZ6iN^FHJ9d z9sQXxBzvEjpabk`p4c1MVR}Y3^$*R3M5jdMbl@UEG_CH+|LRQUgtI!N+M|CJq{nt_b$#Kg1 z`p@r)aMZ)ciGu`-fAGB+J_s4IJjCx6y0QntIlo^=03kIc#Y9Uy^=d5XU(2=ave6AC zyB>|Z;HXUTqwJ@!$q8JSO_|&ka4OiNi)_(ZG)T=6%l0I=6rlqP%N5WGbWJ$}sPF;z@KyY<5I(gLpo=nK$w5?%#V$#)5;VPp$#A%dv>gff> z-DkWEljOhPYA(15&nU~BpJ))_G6u4n$N^;)jAODp$meRR*(t>@Y>a5~S`>N-z`e}J ziE|lP^vm3>ma_h5aT-u`Ra&I}7B%;pe{6*brj!{vDu7ys^wFgpbnk4QoiuVs)eDr) zY2l{ZwFnl!9e!Er$cI2vD3_7Cj)JJzgvjuayBH7(VKas;V0 zN7l@==q$0|=l(aTozDcyRf;I|4K;C*GGR3j=YI(}Cwrr5`mGA$T@{kq>r`p#8tKBV zB*!i8Lc1%bvpO+ieRG)AhfRSOs4w=N?*Fba`&4_$%@@+C&QUDCZ?A;#~GPrt_aV%aFqcrwQZiVrn0YIO{Kj+30{dYf7pHVMSnyPoVO4M)*=LfU0jLm zG81Y>wC`c=rA(vC8%S6b!j{`vsnoIJnu8{8e>klK6uC0V4NM{sPX&JZ-BVQGO10N50%x;q`M^%GK{(U3cURQ+VuMf32!#%|;++Rgqppg;Rofb= zrDa|(4@`QJ@s?it5G2~+ediG=p{GWwtHMoayWxn}IAi1!4vich03?Bb;m6rWB_hKFB$lY$BR@(EPu3!bZWvm>hX2@Ksl9?Ol7Nj1RVybsL{xar zmDm}G9ar-r8;9wJ8wY>S3EOor8soO!37M@j1WTbM* zb1d9?1=e@QTb29;QhOR&@Mm52RZXS%NUV_Cj>f_7Q3XiR84%1B{N-e4I;exKigy}jYmU3K z>mMP029tRILzZ*!R_6wJU-q8yYd?+rolk10S^?auFlO)Z9aNBe)%UBS9j@VL>V-m< z7PT!Y>gvT>PlUoDG}?{{BeqO1xUAjuMgr=P@!4TI8YGNVNhY(@9A+csjHIKs^a`i z@>$4RreM6`S?UE@?PFi$rX+xKS7+-b(oyF%zT>v!k|?v47>3W>BT&e&J2qZC1+0GJ zMviY<*1&`MXl2{wbW8zAEoQlWa1T*ktiZzqw!C7FxaeieVk;E-&`ZcjNNWi zIv?XA)30H~z8rTnOsh2BQZRP8&2r1*KX8QoqF$0(YMy?=yQ&f=+m4oQu<&fYKB<%r z2h6o}zqkTlvwV+8Q`igqGx!v+145>JmNLlB*Pakw@~TMjIl)UE zz^^_TVIb(4U=dB16`kMEM&XTw+PM+k>a@)EFQ)9slE24+_d)g2>kqQGpsfSmWt=EF zQkDB4Vfg#+I{o=A^jn@Dt!(HBQ2eR*&AMlOEEvD}IBDzpNgQjUW;V`QGMU}05omHY zUu;vzZ@jqC|L&9xd+{yt4UaHq>3s+KrMF~l(Ebg+qnSZNOK_mrMkn=l+anoYAJ@qrq>FL%vV{s*G38Z8YEn7SK!R?SOa{qWK-O zoAQ<4@jlW$e>u)VQFQ)%=M5yiRK@>U7328hn`#3R7wpTRmB?Ip!NtbLvZM!;?sAtA zjn|YZsaPLDyK5vl1L5O<^^cdVr&S^9xXA=hjk?$o23k5fiH%JRQ?8Cj`ayC+Ix1|8 zTs1am$a$xY%!}n#_sXBhet_5V&zj-FLm5Nt?NKcqs1+6 zUKL&0FW;sC@vKX{v=%1BJAx+F5Uuc}J_z@4BdJTw?~ZS>!V1E2{zhO5@v|Z+IKi}0 z@LTJ2gtmT!LCh z;%RcwZKqSdBHJDqNn5oULOEUL(#MoYHusy^{${UX zGQRfLyhx2fhjoxWFt(Y%Q}%8$klk9^7`0n*=3I^+i}90x|1%W#V%PZft05x63G3m1 zMeMy>zPX6;N@#yRb0>=wwrx)Mb#AmU&gFlTwOWCMOXvt!2f+oR?d-_rCqdtzsGKCN zYd2dz=7vTwGFfU@^I!?L5t5%Dc_`l7^#fX{ODRM=+m*9q#%cKrbpq=M%qBX-yJLSt zW>PEd4z1+n&!)02{ zx2835)~~IM5RJLA@W_14qH^xGvIKwm4#6TkY1>%4XQ64%+BC>I)1(>w@aC?^kL_l@ zF2+ACPP1+Z;8u$%RfGCjGA?LFzc<6$RYkB+&B#?Yj87Xcu=Gs#J)hClhlf%R8VLm7 z&>FKk`xUPH#sQH+3<=7JPg{~U_W;iGX9~maoHGgMAC)?MZ|E^oT#dsWZ7gIpsB=FFbT+)>uqta+X{_r1boO(6x0bv#)YwU{eVoP3*6mqqKZ@|l6j zzmaCHoxeZRu3BepIX~9e=XhaD+nj`nuZl!5Rk|tih$xrR1VRG?Vj44 zqV&mfUjP6Z!T-1n7c(UJc4=)ZflE7f>fr*ZL7#Etvy6bOwGVIRYI5!9lgPSxpbpaR zPPfNv&(5!mvJ%`vU)w^ zV{LCRnfdJUt8L_iAGPJ+LpkP`^R~D`Ln*F}9EL2mU9aSiSdyMT6E#sqfglvMC9#jG zr`NuO9wdtsh$QaiRk4@HK+b2QJnc&_MUib(44fnn#R#bK&0vQ@i&=EKg)B~AWc_iF zs%9$Y`4K+4gmwmkD1!ydm$2?NCGO%+pXx|sY$(}0xRBY#&2+Iz)G~#;&n!=_{{Da8 z;Y$A`m#gB5sH^iTezWPjzYE`_igue9EAkV~-?h3XIs2T8ZKI|_n$ifzMJQz*A;IGF zzYAGGiT_HK3>KLw=mq1HUEm;!4xK|*EuWj}4gwvA1 zd!8&orr(}$8MaUd_M(HK23Ic*Vjl-ElzO{{K*gd$NNIp8db`c z$}Z5KoK#v94fU@G=fM@2KZsl5B^j*s%#!wV^*Wd)=4z-|!2%LR=b15S@-t_Q=Ui#z z&E-(mFOWBR;aCXeN=o6OH8NM1VJk`g+RDf7Bc(=SX^;?C3M&TmjJ4Jz!){k@Rvnw( zc(O1+U)-RRTy$cLZ7#kQuFfuOLI-<@Da^zKBO)4;lu?%Lyo{H%N&rR*$S~~SrQvKf?_yd_tPR_gbJe#`{NYmx9AsaMP_+r1 zH-`vuX=j7A?8MSWCluVH0w->hsz?&BT_N({x0t3*dl2N++Kzo7sha?~1a2SoF$g9( zEsN&cjHU9&ZapmRE8||%70F&IB&1VOo)^`OR<=nd9qi1!;VT9skh3k0Sq`ORTs!0+1?oPS#`$4WgO47~ zTU7NR4*w&a&XPDq8mvR%WZC+W?kt~7L`8Lr49;7g_x96>A*C;;kQ}!luu~))n+ITF zJ_W?pUZIMX5#lFh_*YCYd70b=TZun~FO6Nq=NAmwDWq9jg}U;Pg_LPC^&*(RI9kc~ zxNkbX1TFLx2%L|GwQeNqIVacFZ(9|9FiV%aOD@^I1BM=pJ|ohTUA+a2Lw`7L^;n7A ze*c^eZ?PHkmOgg-ZgsI#IPYpz!@i-J@b$iBn4rEcb}#TVm0PHX7iHs}(>*@+QxUJ58Lkp!f0xJV6(`Lr(dfL#7>Hn4-$W(OBLC=Ur1R z6i8zf_Xk3th`SX#Cd?>g*ze@Du=D#mOPb}_C&Z0s!0*b+V-jPyXnCwFm7J`&F+k-M z&sL~DRI1_(Q#t8v-wkTqq`ikF;Dt5Avq`fEi14}Jatdv%uEnBik$l5VKOJwF&pO@) zD?}az@P6)*sh1kTMXG!3u!M`kI($_XE(#|-gTy3$7d_5@Eb%BoWH2K)K=n2YI{#H3 z?1Em-FbvoDm~>E;M1b`gvnjj?A-_c&hPue&n>9PK3OiN9NI7ki{R`r8*$ z&JjXpr(#aSi4PTySL3XIpY~v;nxvoVfBLW?U{s6wz}0IZZJCInnUB$!;lykY&kRoK zG!8m%?#E9F&sRIn=b8lBn@!z46T3v8uz*R>Q)I!XT^$+St1o^nSAUa&;A-=3AEk21 z&-k%=0{@pkJRVI?W&Ft==}+znxt}?+68sQq@PPK05rjl56-dDEIoLTC^45nDbWu_P zyOW=xXOe;M=!*#qxBU#+sR}+03zT%}aqh|zJrYv2xlvO+cc#;K?IK}k@Aa?&`>E@j}@-edGF2-#>s{eKsfR4M`L`P$4 zv8(hNDl_=_SJT4dMt8-xyraFI*FHyEKW<_z!LR9z_5nr%j?Zhk2Wc0j0`zO*G~2lFyEeMBY0;T+PkF*fr;N zzoWH?jj*uv1|B4S6F~zUi<6Cz88h3&BvJBuC^J6=X-z-5o7`jSAWppxjbt>$Uw0a$ zwdWzdkxXtp^4Z0XK@P3Zq+i9N3HPJ5lE2f#pjNvnvqe1O zm|CI$hY1kyf!E3OhZ5t!7bY(hSt(ebVI@AJG|V8sJ=0hWx_)hQ%6$R5+JU2@LGcZX z@kvE(uS(LVt83`jemNNmOX&MQMQ=@<9YM!eSf@@Vv=5+Ed;y=EGejTHJ4YY ze{G=X(1s{VnFEf%?6S>Ahc(U%uo)7>UWPB32F8qZq~lv|8l5@NCB&!kmrlT z=5o1W%Zh-~{F8zgWmSXh(rz3ngg76NfB)`k-Hd1K6MSKb_|y@L3tFVRc!?Q=+yZR}nm@8Sh>7Ii}fiu%JF23LItu@MXS0kj%45DRsKf!V35j)MAHHLF)4+9ZBt zE=ihXDFMC2u@%MB&YXG|t&Y&#jD;e>62*Vr3?5Ha(bNPs9EUKpDpXyEVX=+i-rGl$ zuOD~Yd>ppk;&`WqISMP+t#~Z^of1Vj3JFJ!Bnm&02&nVC57t0Xjfrb6en|AiwZD_p z)6j=M17^DDQOO(EW17LnY2{wgoJOr@xlpJj)_upsiIJg%d%~&vQu5H$wYH5_F^LbSxX|6wyW1~0-@$e; ziI!No;bjJTZ1R4{u~ovom;H$ig@zG7q5syD=I>Ges^Ti^6>FMkpnluUX{>dhQbm~4 zo8}<4I~bZ9L7Hu;5jjBmm-F@v(CD*)4yf4qLv*Va;(I3V*b^TpD~0Q@_$mLjo>X(T z5Qx9{v|_Q#{EzEtu@vruV(0?8n)^nGpGRz_xNnMi71shn&x(yD5;vW(JpTK6)6@vd zFG`>0NZ!v8H<4i#e7F{&Tmc1=EqFQ<<7lfF z;9}DNUgCqI63c5F6`1QC>3y^HwIYk?pS^V)$lts)NVk7W;#nM4o%!kCLx4Ain8HU78KiUz8FjW!8vB$|T3c=EpW8srOmK!WLB+!S_(*P&URz;Fe;q4CQ zLL_2qwn5EP*4+WrylM3l5DrLgP&tr{8ZhCsPu+!x+4CZ z^(PeTy8c*(t&LK;O`1ZD~vO z)E~!5HLOA-76~3tnsBB$dx-+!G;#Hqt?K8nuN=2gxSQ5MK%u)81<*@)6KLFBBeJDJ z;lrT&UlYX8&?lw7J)OtFO661Qy_6|nawDe55t$ai{0lmJ!7DK)+Lh~skdX!{(tS~` z!Q9VmxkjE-k0910x#lXI=1Kp(I6PjHKJPxCcSqmWql~Otsyo?s zKR&Mr*2Kc@*N$iB06W0SL$cFTD#qk7NGJ0E(I2DBm_PoCRYTuaFLUZQ(J4dPg?aTA zK%GVFL{Op`c^2;3+IL4NTM$Cw@jN)|kS7r@M7I`F-c1kj{{@6?g%4o=)=+Gi#7wC^ zp#haN?9u<#9rE7lRyTFJ3Mk`6QdR==r_T8#1uEm;Gsn+X9sBc)!`G8P)~Y0KHzdXcal_XQG9|I^KP*8)fOx za-CR8-a4VzUBwb_>OV|6bF*`O(z)Tm3HglZ8CL9O?`As{lpk*q3lEwNg5r%MFqzm5 z)1sS%@43gJ@9)2Zy#TxeAp3WIwNsb9Wyq?f-myDxd9D9jhIhgizo8FM;^Bl@;r@FN zg&}=+zne)m_gUW)w!$xVy>A~(QGf{iCoj0<^@8lerOD4}3v#0Io`^IXTIHRHF#DET z5xH75bB>uxF^{P(a&~64nvNo493Uw(ZX^x{*hkPg4k` zih07@;BKP>q7Z&v^_%_^D(3|9`qF5o0xTu;L@7vW_~)}N?^j}3&mMF>Z{$YQrJ zv;8mce!1G$K}cvl&g)~6^L0NSC~qYj%wUu<3ecRZH}Z)j|6rZt_Huc+Zi&+;b!91Q z<-*3^>XlTdX)rqQolkaKhkIG0|G_U~Tx|Rum>EXi?)s_ob=B9-e5?vd`PnI)!*EmAJ0NsjRAA#f{A~pMwfbtFMAR#+k{OKPPwT0#kxC z*-3=h7?41&+b5tCbohm`CgLG)f}jq-=wTw&bOovD8@!$$Z$IX%wv@il;4S!uy@%3x#Y;4kC40RmQUidHtyHGScNWRyw#THrE6m3f4(vlz=kfNAR4} zPo_9LYnJy3Weffw)riOXyqZNS>g6(`=HY8vJr}wk!j;R?-aPuo)DW}0kEK^f>5TEq zWACP%Gmmd}{zELHN_?wNj`p}={vrldOfs0P3i23Ev zJ9JJ+=uge-!`#BmrW}}Xxwbs0Se*maEp-E_WPX4Oq}MmipIJ*ogu60pI5K-2iB5` z@V^OiCrROAJVIP~MB8+0elAMQvB)ZpjV>{pdT&1hAh!=4dXIRfy7xRMjF#!{Zbr1< z=Whi@)9;;A`IY(-g$x@RWftsOuym$aBo`DQpg-c$SQ5?7WZDQ6)gj?ew;y^>kKf4w zLmY|67fEfnk~Y@PTf9^$GR)N0l*R*UIi`L%>=*3 z0ZyZoIcfbAb+kEeP97A8=f+i{Tm|QnNYFG)qd{Cp+6D%Tev-wV!$qB`?FJp*o95@& z3nw+x?6ShaS)R?#OIHVX#({&_L(O-qCw((0spp+>66;hb@VW;L#T*xA@f$$V^EKN-gF{J=9%kRI)SSkuBCA;|p;$J)U5wD+iXW;l7QLnAA@Rmpn$awR3d=`s_; zutRf4A-sGoWlVMPZ&C%bJkZHiw3dtlIU0VA{s&;SkY8nAD%@2g$E!Xu#1$zxFj4+G zwZx)p7M+Ll9-tO}uO?BcAn(>gj##sAR7d3?krPld(UDtmd|DD1A3v|Ie6pFhrLvGR zy%8}ej&{tB#(y?K9YobBrmO$^pS+Yum$2E+3*ZWzj`p;4ll&nkvqQ9ziB9lP)iF-i zBfMPNMmbMaD{HH%e>_fo1jlEW&FgoWd`W-0%ih5=;jzcl!>GTJe(aIB0(M5llbaC*&>ybbsx|H z;m~vhFUDjb$c?uF&WHI_DKlsoQVHlkwbf)jIr#JgMs9-rduKe}qtW6tk?_lu1z4#p zXJwkGz>$t-YV03bD=byqCAzFYO*S?q;zNCKfVeV0ziF1(Xsm5S&<%?Y%fV!>?l8&S z(s%nrZR4aB$+*Y~W2q^Q=C#hui$9KFBfRzNDZnK{v&%{E0TCD|In-YmClb-S|wSz*$U`;GSs#(IB)U4Z-Q5bSXJ%pog;B^ zxYTgyBER$5sHrKI%V~9aP6=7$<>PI8ZJoZ}>lGg$?_Y|&7Jmt$rRsViY`lq6IE8S? z4|J83N%wNFImGi9Vx^e7xca-8$T7=Xj?&lI1=Lzx13+X8-k%#Y9g6Jo3h`qOmb14! zXvB>Ge~HC-uXfG)6BA9xAy$vSkWH#tEnK`j*}|IKsG_YQ=bwp!Jcm`x+jW+npBqbh zp}%X*q-Y$9mS4+`@laqd0eaKZh^zoSh*QLpIVFigienQ^p3j~7XHY{1qo z(R&9rdnH?5dpm~VEXY%!U7ZgbF*3ukh>;0<9=+5yvTsyhfD=_9Vy>@VUkN%I2b-8a zdDN8&6#m53N6Ccf)aqGfdfx30e=%)VAPxtj$FuDuXT%d~2ZEt~W~vA_(jHTE%8$zx zHsKz0z$_hZvju35FGdG-9${~$M)KlRU;9P-&=E+WoeUP5NY=&9WkkPFdbMmw`2oM# zFpg1nkf_McTvCz@etg7H;pSQWqNs=y^J7|KL!y>3>FDJ3^@7Fzd>r;eLdTK(tuiH* zg$idSdr@S=u;50=iMy@3T7`5;o}Me`0kBm4_Q<~{sLnx@s|F6pYWV#1cV>gKS`hR- zY>o&_{@qrvGtPE|`HD!6JrP=EM3ml#Rnm(^-WS?slxq>Gq_X7}~30 z0e}7J7Zzx!twbiH4>zJ?Z!gU4C~azj^|BfU&3fYSjc?bqU+CF9_qjr6Vvr>ogQ*v* z&syJhW(3>?!vrwU@6U6RBoGq|YPA*X+-7rLgDUtk3?SAJoaI-RvWFT5TE2%HMAobT zeeU|N2J!|LS`>q>N#bhjg@7Ry9;Mo`I9bQk3sa{-jB!m*r;-H3A81&Uu%^#wzh$>P2Er@P*oE5!8tzLkg^ z8@}8BhlLR4*^r%0 zD)8=;MFuVXxSt~QhDI>+Vm;whsMF$~)NA7@gPF-+O#wW^XmbMuf4unxtt$b~uzN(n z?G-{|uzluCb>p}^iRv3YR3T;`#Q;E(y%|GjHJ44k6iM*R z$4fo$@_KX=&w;FY6MYYD`1O_Xc)or_m5|T5Wn@W|xtbMwZQA?qCxtilkJ@I@wZd^> zYW$x;aJ)tr#R9JD`mUw3&jY2}9XC}8D{P5D^`)xN?+=P@ASq;K76321dcqFPk1q+j z>K0jwtnR7eRuzCC-K_g=(p>tafm{A##f4TM)Xdhh$U1|f(VbcT`qN>=RNj4iJ;LFbL#{cJqT{=jQwM~E5>|61{o&^~gn`XS>_j6HvvS-Rz!z8O-!%JuBFGiW>(yktXcyS0v zEv)6E+J&ff&g?%)Rhp@}H@obz!JI$uHBpx~N1K#2Oc7Sj8-<8O zfXn334{OshiF5cXt=FW@pR#O&PPJT~)3`zHcuu`Eow)pUeiz%;KnOa$&ZdMAV*@i# z+m}`|-=D!CV!^w5e2&odfSu>z&UU1yRgHBDZ!A~80SI)7cdsGG9jVS6DHKmCprra)5t&%qu?GI ze!H&3)7GTg9&Xz|x*y7$5?2NEc**cx03o+o?I z06z=$T}7CIr0It8Sbnrf(*6hP1j^>5ypWSREMw_PZ_G_vb(a4ac-0~*FgI(F)`(IT zER=1X`Xf8cYsvrRjE8|k@B{uaB72rQ+S>8)*L5D!A09{htYOc-I#FM(8;~HWR35OTXabw-j0%cHvBu|Crhxa^Oy2S7uT&W3!TvK96a9CF1>&eVJDI>a!ZVZ^0@a?yFw<(|C^Y&?;2rYurs1 zb;Z{qAehLUPdIrC?SN%q!Rk3)ly?Uy5dFe%7@8A?H>%*77*LMb{1&y#^U_+A567?%!$fF0I64twMZg0 z-JdovUU@6~pu%`^L@ny?{v-1LP95Bt`M46CX7zA#bIJCrpwYlJ9Q&1^fUfp2YlI~p zW=FSHW?O{<^g#DK?K%Iel3i~5hy+p=@z2b_*YGEIFH%!SmaCJLxp%$80Y92s-lLg~ zU!%zXQ1dG-tQL&qt)u*wx3R?$JaTH;K+Z;DFjV&`Qt4dG#scM5tGZio%7-p8A>ALQ z^VFqv>_C!;DXd+74~To!?*Q>0LYiq-6co`WWjPrD|L1bS7eme?W@Inf=Tx}HC{HfZquBNo*jPu~{Ia8b*TG8z(X2R2ub&j>)SbMb z@8IoA`2UQ-q#~?cEB)d&Zk@P~F~sbyu$wDD0J8Q6XVdodl^(Vh1-b6h=|j0fP%J9Q z#|p60i>kL;MY8N+8}b!rg3}`SJvH4RI1l$rJ5n6e5D9!7Ds51Va&~m^c!_JKqJQI> zqug@L5`$8z^8P!Wcg)XTGq|}Z{HcGYg0_R>06&k});61h(MQF#7MoswBzGoGMs!!7 zc@mPwxdA&{WN6i{rL?hZq?2m*D0(=@OCDYPhQ8Qy=X?nD7SlQ{Z|$k&aw^7XepGI+ z=q$YrM7RzWjXTdCb2Qz)ZM>OMafJWInd8F(4oxbMd*hgQg!c_iksncCiZ5cvdlE+NoI0Ai4%JKGU=9G@@SL~sLE8IUpDj7#UgIE~~zR`pZ!MmgF(faAYFywrv|7wnW?(1JBe#W4elMTKvZ zxsCJXgP_$EG?FJ zm>TR`-?F(|^=p)diT=;55ub2FafHe);rPc=(HhF*3*)eXMsIhN{6D@I903SbF;Y`tXvCR&YtEL(m6J zXMuScR6f(&rlm6C5Nm4775*v4Z$W{|?2?z>O$ibnIw>P{-Riv_*AgB9N0DmFEZ?MG8}%XN>P{# zVCUv(HbeAeU0-svjW0I>Vbt-#vCB z@p%uBkG#ThwiQ8mn7XZ+tcf0qhPCP<7P+esZ#9@?kP|? z6wFHg0&Qs+%}|fCaE|BU!oV+Smx zN)V}X%mJKz6!`Kz6!}ig9f)_5$XiS{GNfw?-Ta0xz$|d_T!d|iT89G>t&s*ISR91&fCg=$$(VulCi>ExS@Lm$Nc0o5*p_Hppjk>a+Am zVTR>CP;uwx*M2^*vWLTgTKLWY(n4YG^cB|QSg+9s^WOpu^#7icl-%wa7e_iIFJ$#F z0qM^~?oSuYo7c($nlLwClKEQSf?DGsDZ?h)IvVFHy2WV=$k8ZXwS2*jUXB6ol*Yv? zf6cmyvc{T^7`9?JlM|4ue(30w8t@7sb^)=oFf;wO_!%|FOkU$2A*SI>Tvo!IU#HTH zq=j@gl_WZai)Qay9`b*Zo=sukNfF|h_op=pcuiPu0f{`aq{*koUW!J&97vI55Y4pg zLWtIEvPoq0_)b3ydrBAfc&QcUcPSPcwi9Y1)5yS^wT4Rrq^IS9_X*b*S@a*QTvqZ3 zZ@24bDb?ih$BTK6bMnA8Ot|;G?yGo@HQl-`R~$du*;uJYa+)W;NHzBPquajIKn`-7 zPc4lvKZ~I^C`PB>clwwDsX z@O%pU$4{bMQ#(`TN;=s`vX!j6rRI;R$au*bk%^ zuaAj?i|_QGk32uaLWtulR1^u7g4{6TzL9h10KO!{@G5Ecx}bIcO<#Ik<# zJo`Y2PsR_V7yI6d{gYFQRhne2W8&Am0Lb3Y&20Axs272Ti&uB5YY1@Jn&aLf=bmKP z6FhmtihOGB?9^J-YA2VMY|T+>c)?{hj{4wYfa~ zRr={@f`ta;V)otK6a8O}jmqSox-pM(sykA`*75x|_rdw13+YK5q-wvgpRq+`$ZoAA zHgu7udrgj@!h`UKYkb9NjhT9frNq}t<~;jR+9DTYK-D|j$;zG`XM?1$Ttc?-v4|=0(I1XRoxi=51ad5?w?#O7 z;_|elT+Glhs%y+rT=PSUf}Z}e*feSn@GYDI+Uj)cJN~&d)|JytuX+FTu28dDecr#d zMa0i(LeCBfovxLg0jO`SSCjU?01Q4f=r0e{Nj zV;<$(jF89Ym3xwBr6l%A10y}~+^pl(*Z-W`)=u%nBk+f%zPumK{&4+rkoRQD>XXlN zt8M7!z$a@0o(VX*^^&RN`z>^Itc1i<$@Q%AGksOCb>F#dHvj-u(NSiq0{<=P;^i~% zj!Agn<=vf!#86f~$#5@&{dla7J`*POW6^ENd812Ty$g(%R^EpyA$UBj5rlB}iQ7J- z--bR*A-@M!bx5ymPi8Xmtdjd1k-BYAc+8yR__-O*Qp>@Yko`JBVr@ND_8O49q3%W+ zcydt4NkN*nm0yUKT%5xJ8_o*$7@k+;J#@DZ_Zdfc&9|IXkCA3sB7*uHF;Eb@_EMY{ zB9Eb@i+U0JaZWP*u`pw;OYyvK((?Oz!!QW^Hyi^0KEUDe1K}g8ODnK-t3&O(svebT zsQ**S;C1P0Cj{98M8O!A5^1n>oQtBOl~~0y4KbCwj$YnkqJ2n0}MkP1h!9 zkuH11ci1^MrCR$v!7_m@8Bt}S%(^>U6QXYa=Gku|MBHwsNgA-0sLe#IK2q}S_@^Pj zhNzuhtD4jPvDyTAVkg!nrvEge$s?u;ESOjjrZZ*{bVt)KE1c8_lku&gQHe`q}Z%wZ<4{WpkjQ?hFE`(Ot@0U^qyUVCd3{LaB5{|%5BDW23+SK>7fnMBAbn>Y9f8&k2qgEa0oshoijin0~9Um z@M9plogdnOcj4VbS5$pR>+M`OV|4v;l0t>GuM}q5u-){)W>LP-5Ww3g`KwBOa|4IKWZB81^e+3O)P^It50b@VOkj;h~wdxamhnWwT%QGj)^4U3%EXlZMUonhPm3k$z${j1H16#nYSx zI|?oajNomr-Zg#Ib+P)Q&w=#Fhd6NKJgjq7=cD~Ist@-?;BrQMJI)n+F0u;WYEnt*-T$V9> z0R&O7fO$P8&DqX>qYjAfWG*mqjbl0~h7uO?s*@4Nu-q^+5ZjK9etvB>pZUvMSwy_D zAUQUn{yGTJ?F3#FgkCpn``-_rvQrzu^R!U*8Y3C z7(ao)cwL6?XvN94DB|l3-t+^jT@8}P5064 zqVi#y?NzhTBB$2f37e7Lt5{IiAqmjHS5dIslIVf1L(MLc5hl~A`Mbz}m_;$d(x zG*xc08Gd7ZQmDd`m;Oni=%M)|rzW$Zcrg>OY>~6t^rK!Pn9}L?BZ1B^JaeqiP$%Tg zY{0m(ZoW7$pAUbWma<&CqYm*L7uLZY{X$8n&c^h+UJD}Zn=2=l^Q`Akd2dt*Xo>m(eJO%rMhqEd zt_f6ms-m$|KySI9^^!85IEz9Bt{%r|=jT>NFf+3grCNl|b917|tpP`MDjT|%nZYEY z6936&G379V_Dof}J_|tObZsZDqk+Xg8><+>5hS59{s-BNU7wWoqMwkoa*s|)Rmb*Q zwBh4{9H}?nsGOZYJl=g^owPc=dO4j0pUn0$&fAh4P5k0WVpB2IE(n}4zcsbHC~jkX z>(#>PVra-|!d7VeHv(U#n9f4VyDw;WV#KD`JoEJ^_=RF|7PheVDO0Yw{xngs`sM8? zhX-k}FAL9DAk>>{jal`>z(wWD{6o28HkzkOv@i2z@>1Z3;N<6pQ~p%;$G?QC?4mMH zjt#|2b<%OlikNAOs0?7QeT$x_;@~r$j89M{;jW@gz+FkA*d1BxSu5(N$sfO)I>nlu zT$#|?msLKxKM&G|I5>x!+A8stSM&Kelw&!%Yx@h~_s=-35hdkCwL?(-)PFX5|P{ z#taHT-Y46OiIVYHeQeQ}#p=)HnE*X4feoa#zvbJ?qfy73B2hXHZQuu_Z@8+X|7IDP zZz3wc@Z~`^APp%mZShNg{EUzNhh9#QA%T-VxPrCK&*uvo`v|=*_JHt|^|zqk`KGZ| zNA&9PqX)C$LGw-+WcwB30e1}W+GgJ|=xW)7V8hF1C3$9KCY6|cD-F?aKQ z_ifKb3ZQEQ zYNSWSg&ANIPjq{Bv<3~dcS*l&iQwJ%uS>q=&pZJKng$^HYKq|2I~61;{8YBicjqah zCB@K1SutDiT{hwhU=QEDTWHE{F4IvQsl!63$mwxaiPDkFd#bV5RD=thQ{R56LoKKX zhL5N{%V0YyZ#2us-2yk#M6ugyG-01ZqEbKYqsQ8$IPeN($bU?y9-Ek`O--V(bQeSZ zZ1`>V5rxo3ka_xja5eGmY)&3Dwpj?WhBvYYI0e_%B^8B?61kO#+6Z|Wa!1QuHCsQk z-6TKCF&|Uf23ZO+rM34nS?hjy$|wVJSCYJZ5ivw29yIYHC_5Z?4@o&Ha@) zw02}%&^}$npmDi7DYrG`OE?-&NdT$m4v{v`>9Xb1JUv*&LLSr&EFpg)o<>eQxD-e7 zXeEb~QZ?-zPvWl;GC6nZQgDFLL1iOTUbj5Z{Z0+~u}btfq=Ffb@_sE%#r`SW;%fA5 z)f=%_HIuKN`d1!dn_gqK^*W)k4mj&ZOn>}}3(*e4$0Hb06j2B*#5k&q?AMG9mE{Hp zk+;aD6SCiOc>sCeao&a&)@PoZ);DpI&Qgiw8gV~A>H~gGi~K?^k7g%N43woGNnP$} zY2N!Du8sg8qG7)*MhfSI*q@;Gk9>7(+2Xcxz+l})4iu@4wqqq!4VDsISaj%j3_37= zI|z25RroqEKFg?T|1n4{QP^c9W2BSb1-d^yVuOzmXTEu?pf7G~VB@<|7BN|4 zN-n$uJsduq=J^Cj&0b3*%c$%2X>@NO&_-ieC2Mbc&KLS~Cp|lA`uyf_e8QD|ltp%+ zvFd^H0R&^@ICQUZY{cuUIuS>5hxc0bQPkx}lm^ziBe$`>ipIueE_^I!MEYCM_PF%D zjrdtvEmsRk6V*~5;#kwg_+y$%mMgCdsYrOieDEEYUk|4O77eNxI?9O9s#6k`ee$!1 zt|E5%rOpU3{9OH}lOj8&wu{dEnUobZWb2fWlXI}8?`e>IkW)UP&7gHOL*fQBdoPVjBuMDZOLe#8t=$h zGzE?@i#10oC>nPbM)kTD({fKh0|5!-m9Tt%=+gAIKS}G3%59BpjZRU0LzwVrBFOFn zRH(hgPm7V!_@7rgd$hf;5zXQRZzW>KS(VB?oYs{ct-DQG*)Go-e(owkw;6m;r!LV` z^yL;=a)>_M_-)m>f&EmjDK^tWxS!P|^@D;NpZw_4GCX|5&~cm;%q>xC2SXggr=M3? zo(Y;CYx@&GQ(+nA!=AyCWE+LTND{pcFCJ+ zCOs+{DgBLOemCqtVa?ZM*evz*Bu7jR;v{zKBux~&PNJiWA9;-gg9GZ66!Fe~;9O~6 zE$$xyG9k;!(x^#KihB^2w`u43i1#i`379Li;>SE2s{j4|ckzDT^zqd9a%sz3l*yuK z?IL{(V~&YX47HR22$x@q7fG1_(Mx+JVMtLxLTQ9rN*k)`7j#*yyBrXOZ^Ae>vxa+S z2(SLn5-d}SE8MzKuBrg*ug&6U3gU^HL0dc9i>LoY%c z-@2PR_94`{`pxut&4DDD3bm#!ig98JC@4zoS@Nf=X(~IP@Lq>*RdALv*cHB)L#9@+ z-E>LWBjCmMKr#+h82-iWoMp%I%+%{~K^sQD)DG_bRu8^{p)xI2AMwiQFgcVqmpbjt z`l7q+py!7Ik8(k7&&hFV^EiXfXJimr4Y{#dTR!}Z(DeAScLPt|lSfEzC&&94O4)t= zLe{qO6hB^&tfR_4uRAx+0IhwMH*WOAiC2fldX^`B>;OHKWIHd&C_~HAwo`WYi1|oI zR9(wucPSLFXS17vYHZy#(YJcp{yUT|wDtE#0F~n6W#m$fBSep?V-ols+L+=K<{vID z=_)xLx7TX6!o9|hu}{r}*sl_UMj!lUIbXUND<7V!1;-Wn{x9Oa;y!2{uZJeVJU!yO zxkvI`ZL>1eOK^MM)KW95`E{`Uk z+WZ$rSIA5r?O!G@JqtNxLnHjOvY?qHMIQ*Gv@viN<)Y>pWO4zAn}9PvfH@W-2_C8b z&P#EEybd=pl+_z-*rjf6XtblQegvF^W^8i^uvs-35CcqT{?z|W%e9n!d23C$%Sabt z`-*TuB^GbquBi2K2=c4P)qWPB%IsyrnIB_vEljwUTwvY!=}bPSmc=Lz zoiIqbA3P&T8(+fCPFp^IS5HjjnkCInc`_mTO?n)Y?y;9_)XkT$c+82x9P$mom8|qg z-PD6`vZ_BVXH{2&3V?L#p)rFqOR-#UF@m9i7tMy`QS2E(+#cj_a)Bp%L77)E4L{_Uynyuw_wNy5`Q)54f%JB zrEUhCNQiDtphR-IUl3&heL(RbjRZgjXk7DkMsT=m{umV$|Y=TucSx!SqnTZ!}zm-l7a$P?6i&^}Hy zR(<6S11DU7@;5Ag{i=dHdv&L|)yAotUqHL;&rN>Aod8dii1|0iU}~0qv-W=O~9D_((h0rl|(0DEI~{v7n7&Y6*D!>2}pn zw)9yU`iqS)NjeLgvoUV25A!WEu(YuX$WPfOUcgyx92NAN;L4E=UG#J+Yq9blG#P^N zpWWeJA3weQ>wBnbNS8sOaG=xk@|u${(Z2*HX6=Urdk)!a9nJ-)1>{*#(g5M(Gke!T zyQE$kTbT>gnd*<{0kr^EJ|lSs^~xTT&ai~6@cXo!@*K2FE^zr>3UVs7 zRvq!h>r<3>{U-SdNGhib!opROPP1Pfh%8kokt;mKj5AlzBw}U4GaT|Vmo&NSeGp2~ z!}mk9_%FLU2TFSsY!9<=@X9iN0 zCPpGsH-G3uuX`gwJM4NC5_nYFOlSyk3L!5gzy&1Ba;$grEp{<}e$FPFMZi5RH1_gb zvAR)8t#t%?ujld-S)B-B8Sr@HV<#}pHxU=m38=DjRf|{HW;`Bv ze2)TI9rizv8*9&2CJkh<27#{8wac%5c%ks!SH2&lmGWbZWCHTtiwtg#lyUopNYs*| zZLerGDHp6aTRqS}KWNqQs=go6!tzy+6g>j3!=1hji6dUATI# z>WXwV==KJ|We^vLwGUD)F+AqiBGvWvzc`jh{_WguUh@W6P*0pIP}Ow|eW((eUS3Ig zUpGTPYz>ITm)fYeF-nYOuXs{*#?l7_J@F*`{1R*Ql&DGdjiZc8$SxTA@2ujuA0C;o|2H= z``(YClhHRBHHc}q5ScxG^i|buS)VHLn$%dt1US;zuRdn@9)^6P7Z;AjzmU{g_aj5# z6i)&~NOO}(6UjfTa0=X0AuUuD7e+h&mraZIU)J=I)c;6GV~X8=G0=@g^NfG?ktC%u@2NMcL0|p!HiWVM7ZT8jE3&u zA0ayIud)`eA5;V4?MI5HwK%#To|%gWDcokT`V4&NN&1Pp#|i% z8+H5gwZuK#p5VhLx`f6p4qP)lufCe-OGsU~{d?qv&vvy;hoaf1#~!UU$B9MQqG9$e z_3yM7rFYq4Y{fiYTZqSkzuvJBJO7d+f8%1=Ss^Dd)oe84s7ynhI#)=UUfPvZDE{V5r%atzwW4|>v6<}fc|jaXX1V$rn31n;je8NN=x00Zha zIBba)%i`S2a+rYDI&BpiAMAp~>9B3eNcZUQJ|AQpFJqSC&&F`d4FrW=x*MxE#mGMrSNFP@g@6PUyRO={jrV)ZXIxicYmtjgwXg! zMH|4r4ui%5j|Xw(%J%r!^HD8Q1Iozc5B-?e< zbZ{~O3u#jiM=S!>RSx&Ri4F159J(}=nQ^SJ71So1R!O3hS|82DQG_cC^rwFc2UGh< zc1dsEV4{8b*uC6+ute~~Dt{t(RBWEf-}^2_Jop0D-{GmE7t~~6<@T?%>}byF z77mb=)O$(|`VLn|3byXQ9znfdm*yI4%ui*VRZ<)Rt6oZ>G8yvD@_EX$)8yeg!78x) zSXj^Bq+_I4u)}6(xk1tzi)rIjNQqF0uItE5ftoUX9Xm7bu#7sz$kl%s{`}eW8u7b` zfsf`)72O>Cr2-*tru7y> zng1|KNbwCRHQG=aH?)kI&{6;#4M|omm(1!cS#T=# zzg}YE;F7926WZcHO#-k2N~Re6yNvzVL$!t^#eIbIAA>|9lM6|ksnqCU*0~?2!f&On%aAyVScnjz`wfykq z45}32S^5=CYWwLuPkXF6eddP-oTSEKRHH1sA!(BTAD6T?&E~SW8#!_tJUO6kBUk zSmIwiQf$!eYS=%T6e{)R%1Y3ZwVW*4xD|vcgx*j+o79vi=uTDGhpd0jKmAY^B_5Ku z$zvgL6)KKiB5J|IVHEWZnO`~mdjars><TM`3E-^y#w&(WV=5p1lOEm7$h!9B zCLsaZD#RDWJ-;Ju*N@^a$wn$C3hpuu>fpDVs&op2jB;(%WDZTz(!;pGT}0hLoQlYpr}juD;1`+x$#Js^SE1 zAIu*F6;Tp?V#_8(8q3JM<)6%sW|OWv$`tyg?z_@mFpk9hq6ts~)aVHpeTeIl)Y)Bp z)k+C$SoSOS)+#r7ro%vm)wF3GlNTvtzDd%)|1vECp5@;t7pDThN^a_g<`{=o+nRdFGZF`K~UtoBU8CIQ*hYowKKNHVpDTli(y{XF+(lY$s|x zU(+DJ1dqKf+=c%^R$nrfft!sPBOr>5HwB087e?3x-Tt;!E{>EGBj?{;8O44V-_KY^>B`gxR=SpAfC3PWw3pTISXi-8Sa4Mz>0)pP>00~S zHvM3p8;u9GB#4v8iLDbAOfG49`cGR2)U4mb<3 z^Cl!>iV5$XkRE5^wxdL&XTIw$ZiyMqG>v8ru@2&ge}wtFNH4{p7xQfpPd=fsoK-#E zwftASp${H{c*xk97xkm4k^zkt)J;dIAx^(6_&Y=5(tdjgC%v*MYj5rgW0jyFkYXNd zT|;1tPftOW4*XKRYBxOuUE@heG{aJci)NK+owD&RgCQSL zqszvK8q28G$Z$GQi9XpvVb64NDbLdG4Jh z)pPHgnu3&zsCd7X6+E}2923qen{tCKAz*s>r!h{o{zTFy56e*Ihbou7{{Y;~TCdow zTdLTq-fXZ8;Z7&6&c?wq7s`dEn4A=)i(UjWqkXV($YCXl$~<5`tRfK1(fh$<{Y$E9 zRQ$FjKlu(4)v{+)C$5R?B>O@9>;0!M`kafWtYl3VAn&*7B`;@Jp#D{N@JHBDPFA|W@-vh0LURo!Q6%JFqVifDL;7!Xdv(s&_{V4yZ zMEyNh3#ui0(RLnPow}i+x2fag?|g~6_i-FnLshBOK7(nZ^kaUQ1dkt5s|nKB=_Vza zTW&%LO=4Au{IoyFN(b7e>uGaVnlEIY&H#4W-|`SQpkUa1Dg=7%csld~1&zLQumuG4 z(f7B`%q4}>b(O1<&2h{KlEH`NfQVO$-I=yB9g3xO7kvDv#0(q9>VHMdu2no?Szs2PKim#j1i2)UqMzodJ#|hsWaV?non-Z zE!ttb+Uc{zY^NLaKn7&ro`4YDQ5o|mk}cKq6X`yh-FDaZYZ1`|-arv*@1%)kU)4N3 zT(fvHm*b0b^sD((q@_qnl`TV8=Iql;?u`oU!)lHnTv5`P-z7fw3zEuFeN<56n%wzZ z)Dj?5!NUTF|J6q9cM&346UlZf5B%h z*L2a>^H~thDr&wv3XUs!8rj|tiHLP{FJWg`K8Z?Rpiwalv{L3uP-@1m{(_FN7LEV+ zM%4i@w2#PDKRVal{6rPzKG+i zs*k|5?}s%hB0cVS+>+Hud7x?qtTN;laO%Bv@`SgA&rGH*;Uk8h#BM~@mvYI}>5nb{(Ac$tm32!zgQBj(l!$5iQROjVFL*9_Oc zvmifl75GhB0g!Gn8p0#Vtm_ecQNFy-=`8iQ%T;BLw#Csz8A@p303V!Z>_!@} zX{IA7bu}-wKt^@CFez6!x02B=WcJw#o^3JsAxun2y;YA=4JWjWXzuLp})&S#i~a#%Sk6 zlsv38MKwTe2kU!!tEP6Gw$W^qu(51P+ui`EBjRo>IUHoplXXn0<2oQ4hg>w?zeS^F zR$o(uH4Y@Gk!y~bja|Yu$24s!Cw}D9wG@Yg2EwGq*kc{hDRl3U^TATjK)K$XCEGhEA^}dUM?+opF zd~dbb#|$|Mbi?RG<`j;UpE?{}iT}&VPnU%*?@bN3LU-XcCn>62EX4wPec-hQ<{)y{ zKh2V+S{Kk25JjU7TTN9ylyG2mJ~XRQ^l~$iN&CjqVZqGWk8Lm~1ikDPq_h(cTv*bB zj|*wjJU)q7Ph(3$a%YB`BTUU7nn{=M-Jqglr+>v z=(K*Q`g_?qkk>IO(Ed4(p}3&Ch584}=>xHWKX;u&iculwZHQEZLEHL9#Fg zs1USu_5iAn=;`*|)}wOfp7NVcRxhU)$cbhm|LUz1<{7e3BN;vYxV;R)0oW!ea-FWw z%Rh|gFp=w<7y>rZI=`!1#o!VC3Nrxx3ZouJ4t=hXKJdVP|=XRINs{HBI0o`RCkg|oA008 z?RJ6XAGn=u>XLN8FAl@zOh)c1^x|RxUvBGm)X8J|*EDp0zuIFGC}bO1v_Pz+;~>v9 zWUV`EEq2_Dt6B3_Zfh%k@<-k=?&Rc37do^y3X4=ty*{30$|rW+D=OuZ za>X?hfQ-roiF3lIFYL^ESGmX}-FuU9xKyJ*Ew1lQCVx>A*2z`H`0h{F_>|>_t@9TC zMTrm!>hZu@J(#Om(|A&i%sC)l5vFU*$(|mN{re|{{<9Dk zG{+-n>Nl%$>*h5$pLH9r2Oxwv?wvH$qN?zi{G#gdqRoOdq(dH~rWv9kzv}#h^QIVD zHL{+Efj@baCrWRjM{SNCsBWe(L9Ue#U zSdE0i0wg%AC5bBP^xMb~;b zin7UuhFcV`@pzO=B`+Z9?`@C`TgCz8&`Txjh8B%6p*y5I`L>VI?Su>(P}G=w9>Q!d zmf$gLtNr#McEOlnS6odx$VD<0k1zJY@y-shH1q;GnftZYaqNg#vG^ZYDfV#}hdQyJ zh7SC!|3oN~*1h7vZmdf^OZkZPP&$XN=TF8@&Z8%K?K<(ZWPJKb!tG4+*x_g^Bf~do zP?H_I{F6^e!61?FV9{HwC2FlBw68C$Hq$ZDmP}};``oc3rwR-Dc+t;RAk}md`n*Io zC;tgV1}*4Ot>Joy7ako-j;9Y*Wf3kwAea7Jnqp?ikSI+@Bm@kKvMw3tcc%Ey@N&kE zc=a2xjuX3n=8`)uxDi1OrV?JAY!6 zeWH2%w7m?MHw4sc{B{&EQ2|g|Qpd+r0MGNZC3eNf3wx5w#ByGszH8$FvijS zAUnadMXaUl>=h=?SqqaljICsVCdsbC$ z0;RJ@I&^p8h)gZ(D6=oU4swr%+M~Yi)#;n!6yx=(EvQq`bTPcHMQ^fgfEf}UzSC5* z!Ej%1=aR`&^icL-d)=@=oo$~|_$4Y_VuhFp@r;6k^88%aZ9nzDN!BMl2v0_3pG`#K zD5=uAG&wHl9#+^}VV^5Y5G_0I)Bdg_ZN)>qb*|3A`MLQ~hB-IWE@Kp!uu%77eQ}QU zt*l4I!C_ZdXXhC^nO1wUID^OLWdbER6)?6m6oX{6-o#e!_VQp_Lh=mwv_-Xj_IUm5 zQ;&m1BCLQ$)fZM`F?{o2u~ifp^3C7%C-Cin?5lu3vytaQ#DZl`<5E%#9<$g`B0%0} z7onM&>bv8Ynz9D)hi*zHw8=6vml_dcAwcD>}0-@_f@> z_Rm-6SplgUYitE=T-xunJ2}S3bqg}qfh<-Ay%pyaI{v=8w#FeuGZo9Ni$0klb;m{~ zPNvYtBz=*&dDX>vemtkd0(Dc8+(C$e z>6pTWY>hKXzXE!4>c3+6GtRsn2metm-e<{Wy=`>c&>&3hb`qZp8Pe9X9_!6ZQbhQP z=3$i7U5-y0Wi|9xxF0uz^E3L=5Gdg`zMg^%VL;lPJH!&#!|YD7TU!tn)Xi}J^z3`c2^FnHfaL~dE_qZbol>(2q&h3;o zSU14kA{c^hd8LkgRdsnMmGv>Gd`^OEf8>rDB@!VAK=`$;ex9X*20E&!~}MK)KgtSfpGV@5we@-dmRrCckL<900#XSyMih|lt{*uJ-6CAcdK6~x8*4c-znZK3W#@oSf;K5AOSapI=!-hJqN$&D_77C(r zP}sG-;C9uKvfN9KF!a*t2nTGEbN=6d?BgYV$49j7sQ(dP5K5@=+|%gk7DV7&UmI*b zL^;^SZuQDh(I`iEdRtT2_vs-}I7^h%C`b0N3+t{-OpcZb+p-!ImNwP&`Cg~NX$uZ9 zvJWLM&Ir6fPly+mm$>;kSQqilnGNrEG9Ms3KI3}Q{km>WVe=zPw|(QBnFTH0H`58O zJU@mdxOTROwhy-AaD!OpPL09hQkKo#G>X%=<`k#BE%wkezoF>`moO?>6$Fl!-EmPn}o+PJCO`F)3N50 z_zcC-Q8ilLkBp{U>reJnY_+V!uT_vXJ6tT<@`Agb?}ddhSlYhNx{#)k4F4I7Z{TpE zH#Jg9950apYjZZ(g=e7*-i}*GOSBBn637|%$ikwbh+mI2p45srnu$W;=_PQ0XU$qm z5mniEi`VeY2I`Z4g9p(&hu=R<6FVZZwvO#y532;rqZ4Gc{`%L-N%_9t-NQgmME(u7 z-0a2WeF_I@i>8TkF1M_jWKQNhuI;HnN0tS8HKIDI)v0jVC!DZG zn(HV=-S0V;1}zyqyeG}G7S}b31dMqt0LlHEA_)?>7!`!S7SC&0jXT}}E27O@akMId zMK(~9?O*Hk-f1k8T62=pMimxyH=r8x^SHXgp$~`lDjVJ9o+og_j_6V60UieDm3&p=$YZh2i2QPB*yx_sP>o5<+|N+PFr7{DS=7 z%Y1owzt-R<-66QfZ0wkU)>@`tJp`aM>cJAUZ>KgS#r(!gBs=*1Zd^NOB#naix(;dM z1k6*eB;7YK&gh?yNmoA`xBe;7==c{UqZM7U>|XOu25!}zTF{z5Na(7zpg?Blkr_?3 zx#GbPirD?&-*zGP;ErglejAxiHqKbZj!xayb@MOW&)PU&?mnj9H#Qvv99U_BWX5+f z9#IZO+*^cwLajaY7AN^j`%#{>x zc%zYX$)=tYH~swAvT2!bV8HoV&bqAM9mG!3$2#!|^)&B%)xDQtHjA(tm~Q!`y{BX3 z4&9Y_3JXI4K5Vs6RsH?PT&L^SH0?Cm(E)olYt7qg^2{Q@+(2cpJx>ARy>;hQmI1D@ zHB?oR@vV4R#oLd5zh`3W{>C4B;XKphEpCmp3m|H-B4?U*1E@{3oK%24*VgS7S`hrx zb!}QASuAOAp<1yA*K=X?=$prv5%qZb{-5EwRw>?*5|4W9wwfq~GHLqH2s^zsyxX+ABPM{su!Kvy z=yOWBHid#2H#%G|>3hx?7-D4!80Prf*ts?C372y=(l1$Ewd?)-lN1NygG^tP_lJzqs(F5h)%gxc%Y=u3jpmxj2LqW&hZ z#5!KON0m`Fy(F%$-K`r0(%LZB@x2DcmwGkEj=-&yc?vhIRyJ+n2=yJPO7NN6He^T# z>aK05;I){6j+5UM8XIBHHZ}evmi>1V&7pj*exlp7F8W#?xRP({SBf+!SxXklfVM*` z(pqA2olf@_?wcdD-o#sL{?2q9F6J$%QG+hLw|yt)0gNNS^+o@y6n^8S`O;7y4-a@= z>g)>EqJ84IGO~8pXcuy@snMn?AD^YBJc{gdsm*W99Losz9Lc|qr%Pf<>)Cr?&NY_2 zjcTa9+k0c=`RgX~92Yp{%QoZ7*4R{J^T8O-ZJ6ix?lQr%(sk)E=7RXq`+ni7yD%W~ z=Po`}QnI6b;q@DaAX0RKzNd}6h?bbw_`N0SqGr2ahNCJNJYP0YThh|`C?;i<_v3Dn z(baWq;85!raquAF83x!_fxhN4&C3>(FXV;FS4#%YA)2|LeZrNri>Kls2@WvGE_2@$ z>|q;hYW0^ruA$ z#5-z%(YBrfDnef{WGf@n_P<^uA9?S%9>{cmTUTI#v?MQ`P@}l139MkH^sE}zwEukl^Y7XKy`k2=q9nKf! zuRi8^s3h+@G1oqlSn+c(FcIA+kbVq zSj>0)zQtF(Z5h|3PJ-=y7FCBzf0-ceI3V$tP_dX@LPT5^h3wiSJ`V$G29lwelyjSv zaQ${=YD|N(K>#Z^e?B-_+>+VB85CSTwbYVG&|YprImr3*!hLH-kJXs*_K@Fx+da*aHO3s#18ID<)kkEf44bD#MQUM^;(b zQ%+9Cgs;4UF+LDwK)s=vA&11KF zT7oiy-VTbN|DM&frRHUj-K-p;73ANgnvotY=nFvr(SDi(?YpHcC5O}-e53*e>Y-G<24hU8)(YQxXzW)lO-~gkAyP+O`-lo7B(`O zCB^QPQL**)%{Pp#Fy_pbizBH6J3OiR-yErngL(eeJTS!^VWgZs4C-)Frv_LqqLL-P ztTW(T*S91M_pX#7*>0*N@}P;(t7OI(`%zD()`a zUnF#pA(;7u16p^-?|<%X#|aC*6z5i!wcPA|-)Ka-(jz{v^VY&{@-ma5l%dl1Gg2gTg|;!=NeIf|CsA6LJzEL)f||IfeSj(L#@*cS@Nfa z(iR0ock+l5jvh3L(znPauU%$<#upLG7ZbPFxLwnxL=er3#U}i0}4c)_ZLcn)dEr05-v_Zzt z(8vM%WkPgF;5L8U`P-&L@t((>M;~9xoHjlV(-z170_M2tVf2sLBiUq5V|gD0v3KIN z+eZs_yKXN=uO_J~70h%o9Ag{(VQ)Vei@Ty&9RUK6mL|RFAf4b|!|I*Xp@rn+uvE7= z4)t3Xp2Ldr=wEGZB7Rt9m3pl#M-myclfVkVe7|;)jY~Ewx~t$H_Ya=aTrddftBWvBHyspc>c}!@5jK)5sjp!;zi2G zn`aV0|4a2lIi?j$Av6>lV)1muPa8D9)=cWXJ5SRVdgp})&0b-8@rfEMPtugV6w`W` zHwmNhqv08+nqtH(f9yB5=RPxui6}7l9 z+qs~6t~)-+0rhX4Q-K}=l5}S9@fnX>unNSRgMc?_ZgLF5YRNlx)%j2)QiLo9mF_q% zmpB4@LNdWscrVp46MxScB?HnHq~K7Wmt$G7Xjg+*&Q3mXhcDSvTgf#_Uq6Bo@DrzG ze?rQ0;PFZRBi_HTM(&6+Ed)iTX}{-Zak{WB zmXC{5kLE3rpLrSVFd*kk{fpn-8N&dNIvUwlG^x0(SaieO*!Igibs#INc!Jq{la#2+ zs$ol2cWH9tA@*~faxnugj2~>Tj9uamivs1n7`8*U_IfI7xgp)zl@V4e7Pp@sJ4#pn zbQs=omhlx?>VWLu2|;)uSUZrHw!`l>{H!es+tT*67DpEU90@$)I87bpHPb0;4JvCp zNLrI`u1t)+U|nu`#Wbj5`&GXE83>OJhuBLM1E5@xUs92>hjYC-0KMmW>yTxl-4|pf z%Rs3U4Xn+N;zHPL>)zK9)b_ePl1wzfDhdx+ALqXh@f#y5vhthW>(kW6CEG39>&=^3 zqhG`Wg4i1~2!=B%)HTH$-e1%^;yw##zTvz>PU7l8OnYoUcm`%_Q}dw<2X1+<`l|m6 z$hzhO*Yb#O(2D&7J{(FY41gY{O`YC|Kt)mj`tjWD^K?0iNlqGbAf=faV#6DwQZCEh z4{xyYg0yfA$?Zv1d$JS==hXoNSt^@=&y8&{$t7MgYMUS^UrNQ;{N#pMvgP(X)2!K39h=VIkz&qiNV9k5T4mRUlcU+zzY3DFzWkXP+7-tw4# zIN|s4kEY<6Eyymh_KjS?35xHzAXn#bL;Lk2pd@sDa!MMOf+)K)s@sB!tqgs5g>H)Z ztZO$^_aw?0pl6M<8cE*(PKJw)iI@E<7OoX^ax&^O!Irtd;3wuE-g z5Qcsu{v6^m&A5lbWnGQ+}fA{xJB#aOwC zw`6gC_8!49(i)^YWH<1yxsN}1SMO9t?_IPzMZeV4LA`3i(Se9ksbA3bNsncN?^fx$ z9MX`0RmFL1{x;OQ5Us%%w5}`;FvJ!gxWqUDCW`u=;W45cJk`lnkovPoaK_aEn)<8U z^pAdlz)tj-`MWp?(n8p2&Y<}c)h|2qU@z?ZUnbd#)B00rpmDJ&@a>F`*cr1vs*UG+ zA7;Z4A59cgAlLJEzx#6C7mLzz5l*QCWoKZlG*u&@K3(VUx(>}iq3lu25cW8$*T=C7*L)n@B18bPKNIbWSdp#Vn+}%Ojdta@5Rj3J~0C4Soiis1u26Kc&~^ z8mx8%(9FTws-uV2RpP#9wG49lHJ+NI4y{sbL>Y4Zq%uao?=cX6OzWE#nlj`0Eg<^4 z!8vtHeH&ycE5*)Uo{vb+p8i-dEQU(2$bvN(wIwx?Bv&m_b?0zCJPByK%192jCieP? zF7LXrpPi?$imJaqGMyeBV}dtK;i}W7XZK%T>gx}5IA2roFkOR}M+=6=?;V+^+ht=~ znCm<*o++4C9}){^d8foJ;wZJcAg#HB8JS^Aon?LW&9&^fwRSGnw1uaWtmsnei7_8D zskVA36wSqdI9$Q&*+BW2^D3)g-XB%Od6>+iCBrNHL);r>cokgys0v`&V0=ZAoBuX{ zRkre30X0xOMcxF>KQ-Q=A4G0Td^ey1y;~eln=;}5^JF#(XWA=v+E5L@?j(>x2Yx#K_qi?(BzVYlH%h5;IBfM{qb zPx04qdSzi~vU8gtjx^ODCk#xnN!x1Ux7^3vZI7588h1h{t%QNd#8q;OLDmdK&|U^} zK)k5LYm2O@qPriPKW|)<$A)M&Gf+T2Ib*u!5%%yZX`DMyxueO{hE_X*TuxkR z_j!9gn&~)|)_EJ=p#j=gVbooafE`i0cN)zXSMY#pAMjVIUpr-5qB?h|s{!rxL2IkFdU~TZNwDB{6q^O5egX;Y2#@il4!D&r%2%^CN4EIhW}Y%5GZ>YqsRcjh?U zkv&+P%0u|%KEDKkrErM9v7k{#7VcMhg-#M&clz3}>wShhj}vL%;CDd)8azg+>zk2!GzTPG1-2D> z*&m4ZAmsyUJZW&Wlx5~uP;UC``w(!eJ*1BN7YBu>K-r@ll1(da*T#ReHfME2rlCx1 z(AOqI2VUS<#z2vPD#&t#`y{P!n&N`}^23K*u^$T`{O-1o1*^$MzA&U7<|_WpMdu!1 zM>IF|vXU3d6hNJTf<5|NS00zIg*JU`339u3xc**7$1Z+P?n>wW6)I4i7YfQl!enUQ zb~;q4>WT^t^kG5}imbvcogz<*S$}CP3O$Ers5IIomA3b4ffpvV;Zi&?gI;3iSS=t- zxOl;+acxBWZgWNaKJMT8xesqDoaDhO4RE0>7UM`V$45av)!-yMm%UbIC!2at&xFEp zBnpN!UN;w&`D~N>_w4V$W_6WuSAPA+J034wal7`=EHiw>*0+>JQbHay01aNe>32Q? zl%~rQ@XoO`Wp~89d+8Ux*NpwX`PUb^9*dG7BelFSL(!1u)nR{VS8;aFSUDOGarA{2 zB$nl*@WIgC0>(dJ?fX9p1O2PfclIw905c=G0 z+mESU77KKTsSYo_(=KtXf0-o(aMEPSN{(g$UA82*jppKk)IiL@ZiC-p{lXi1u{l@d zE<}Xig%%UW$*eUm^MsC(XrR~rg6iFql^1adz$ZmD+|Hc0uN|$A)(#dKMv@vn2ock= z?>)F#onOswOeJno$y0zGLSR56Tr27*OPNUA-JYGrB6eG={DY#_O$V6y4=j3&|1Kn( zJ^a|?ZBk(Emae~b(sm&u>rk3K8P}u(GLwF}Vv*LQYQuSRdqopJeP~-tLaN=e6Xb;`oY4=F9MArd%f08 z#@=BkBMk+>kC}uY5>q<5mdFeJTY2#Xo>E`)>zRgSKFw&E^v{EjQ6%lL?$*rGB{SLg z@&GlP5vn^)cT4MBA3+KW3QWB79au)Z!+*|;UryIK#_qF2lapJ^;wC2RDw%|f2D2Pf zf`=0ZtidgVs=#p@;|RNS95k$M)HLkB-(fuO)zM3)m@>fp&(}{k7+(k4tp`zG(>Nfe z5!~K%qfs7p)5EiR#Rszbj@B~K$2Z7HZfb`pDDqts#mvHW>s5AHHGqX3OU0ZT#=x(V zvRUc80t-0P_33$>N(a3xiK^1T{TnCbp)EU8#NcT=Ca=jd_LY&|Fd2?eoVBer2TyJ% zM!EBB+$&48CN}*39}@KowPO84Zg{#njH9xbUo{xn(mqXmaQh8x91!JdbFb}qjPSn-V?Pa zv73qM83s*wbGN_-$# z0uCt^leQOX&*S@BegHx$uF=l>-)nWMNrl0%IO#=e2JB%wzrXuX5pb3od>_p$&?gk_ z=BcspB+u~Pi;hK=zlAOkl>N-t!)JDBbbTE|c{+RCS2dZLxUD&EZb$|Hu=zsEC)dIi z_aC_@@!yWcv&ez`CoPA&j4f-rJ1aKxA%QtWN@>y;%#L}QOQw&VB z`T1_z^%^7udpP!rZc$mWjY-sqXQ=5wc&Xoo`2HDr3BO&w`rN^;Q!6^ZuOk z3iTt^<^kW*p>_rZ*$PYdny32j4ij}@eC`~&o2>{CMJv?>TcdPCkw|4JFXX>g(|3F9 z|M}ALLQwJATWtO&*8=S&#?s|!ZeupMB&2bj?f(Q7K{mFFbO|yb>0P*w4rp;D9*B|b zY@+k*$^+Rh(toq~x{}){iVP9=)Od2Jy~)S;hH%a{b2qRwcmL8oUrl}D3MXa;l(&~{HBVqYB zLH1xrQ2`qgLPTgKNg_3i+gKY`F4J7LhNw;xQwx%ZRHUEDzcj{Gf;Ctv@e>_Pmzt z4+_76>RSx6S_e6Q56_L}3P`z#ARPQnv&ENCRW1AGTK@AZgE6iA8UgyhqoSN~4A*4H z*+~;R_lVHVocCht-kWEec@#76^@Bmowe*J7Vx6z`L(i?ui4yeZmXzAvBT)Oh3Lot` z9V|_|`acVjxhP-&0{em;=5_dHTiq3F{{W~P?b$p5-mwaXi-~7cg~&q6PGMwU8Zg5A z6<)!uX1e73wQ^D8IS{GbiZl>AaGkM%UGopxHysITe4thpE}DSgKq_?+mJoa>Pq23* zAk@^+xZCwp1V1<-0O*luKp>~#wDmWZ)8vi9Dl0mGM3vI(rg<@B(^P*aw#E!MvotY} zSC3iDeAsmQ3h4NC{om+J$Wt(wOCkl&Y$pH%aMg$D4sCHXn{fxmsv8=~2spRe$I}q5 z0>0ZBf%yJXpRHR~iCbkxiMpc|@o8G3iT1S?%_;G-<%Jf_#B2-V%Vq`0Nz)PYYhj~| z3bJ^P{>_<{g&Rj)Vfpv*Jl*Ldxj5y$H^abp?0fq9I=1+wa}yjJEBL!;V!2k|!Wfz=! z&nT5)xxi>>-0HP9(VGOO@~xT}=cRzKg*D8Pn2L?N&6jc(ilzTmVRXa>>glY6za;&&!h#o78Y2_gx!iDP2 z)hGD1)7YdI9zbh7!&>@7!@H)^0h9;BDvlbZNTJ{2NX*L?lnb1mH_J29fdqXzLx4!04Aj zFaKkZ)yLm-xs*5%nD_fz6_c8l(B@SGeb&!5hZxgVo_e(w^(-pqwma0Ve@AgMrSQ~s`;UF?sD|fkL{7O@9q$3jZ?$R=HgXR>223vv zo}VkOo};tU&(C9Ijs?s8ed!Fo9d$xYCVIXQzWAFP{CD_Eac{lHB&oUiKfjHCDv2B7 zm5WTEG~=ho0GP=;Y|8nOL~tO9QuBA?^F^SQ2k9Or#)h7;@}f`s;_{Ml0yiO@v5icnPwiKAF5XvIA&8#nQ$2h z3;6n0LZu>OiLs|B?N?PrwP+f4esV)Vjdi{d78i_67ilQ`&bm}M_~-sVQuemCsy3@( zO?nctgV}>gu<0*(xygpF`3L0YoDx+qiP7=Awe4d)EA@9}s;gitniuW+98BZ6V&&t2 zZEb38v@6jDUH{cvu=O5@NWR@K9a-sH%67bW(b+~6mae?LdFF(8*HVL8FHnoZS}z+C zY!xH#boRxvr}gC30}Qap>ryyp)t1!!SVStN!&C=^XS}&7%Ml(1`V7X~_Kf>9khbWg z+~WeYE9o!ubVFi*gCruo!WF)f_dnFEIcjs#)2e&&0?;bPq*OsDKZHcJCS{CdjMeHk zQ%~+u@}s9DvHDo5$WvdoXL_fGTFmU_Hj45< zm`V}ch30qc561?T3h>2qpE+&AqJ)NwtgaiUzEA*$pqi=JigAz!O()zwVqhgbSr`T;+UeVq_Z_E| z{k(xsUmn-F;FU(bK;d+VVfIR3+$*DN&0_NDF#upXKe#oG-p zBV5{@nZt>ppEzuF(9CLe-o>_M}W+3SGQ;Lt9X?)z* zM)QNf^RtwFoB+z7mNHSe_DMF2XaN-IF^p`p`8}Xx2sZdJpR&5?Gxx2eufL(KI`Lm2 zUYG-wL&`ZTKrEe55YR6A~uoY<0u-nUx z1Rk2RdNC_Kf>oOOY$ld+kil@M9tY~xiIi0x`>sks2U>+oHiE(&+1|&)r-}K zwNCGOks9@!7KtRxXZFZ_2x{RFL`PBaFEk+rn=&Y~Q-5Z0_e7{8NbuD2*k9745h~u- zw(ra@Mi3dZ*EBOV2=qeTK~t*#=CJ^UdgVY9(?r^mOmFIiFqj36TbIiLLdlL{^?B#SuxBSLGT)6x&zADd-MOUv z!G>PZeweakAHONKhO6;fc=l}~mgq(`y9Vbg$Vc65%Jh%S7Ir%WZ<&+jQNDJHJm%)dppp`t zz7SB#-i)S>30JqZo~g}C3csb*%KUOl7erZ&!bdt+;;2?Wt+2C}Y({DOe64I&(FU6n z1woHUVZ?y}D~Z~RAoo_o-}UMS33iWv{c~O5y7^7J2L7U@*)$hxAD~u-pA<}{LFaK& z2?$%6jbFLEkRT4hLT-tl{y3$S6m5Egt7#lFkJ=tdH+45;hf|JdlM)3M-O84F?grvL znkG0W6{IO$ro_-+;+rTnm-48e)Stl%AR^}(KhBEtX<}x_CbhNPw5$#6@_On|mu`Nb z3REirQkpp=CdJJXEV>Hg#kC5N3!2|}`uQ*+#Q zDkdZ$*op5G0Jn2++K*6eki&y=sL!LET+qhN!GmDN za#5C1uo#Bi`IDGu7h(HQpr%=2b4y@DN!IvC$;H(df1xV>lR3pLe_o=NtR_Iq(IDhbg^Y>PzgGN7yC-h&7IX&7x^iJ@pUmP0?`xDYTHl;Kk zNAkn9*6w#3O2@Cs0S<-V=e?)j zmk(#$c6?jzA|DkzRy|PVhzK@FkiFZ><`hIYRcGsUAJBq(&QFt+?^&9V^x)f&!PlHF^VMR&+ky=YJU zBVtZv8+6aflZdu_2`6rwBto$cg{G2*K4c4ywH~M)-IAZ^nIs$7j+hq z+s}oA;nuy6Pfe+3tODCi<;AqRV?z8RSEz3)S#<p71U{NQwC@l0i? zrL=aAOw{@a zSPP`}s}!HM6T@GVO(G1pb~48jl&sDY|M^_}6z&D#Gk@Zwd_}mb6cazoEBvC|d$Ul=j1>gSnnZZ41X< zrOA+3e_7e94=Fy;*MA*UHiriIp#$3@E(UJLHS1nXxC=B>)+@{o$Da8uh{-tP;OMbI zGuJpKF2k2n1>2d}zXh;sdia^c~uyKg8p&w zxfwZv0aREuCYT!!Q2XAN!v;%H?hKrfwWk$Z+3RxVL|+S;Yx2QfgG=T1pGBgdHzfU+ z@6AV>u}CmN?+J3v4@iukg8kJywN=>Bqp2lY5LnWQZV-WPsH8?GeLZ1~nfJ&`TvS2% z3Owr>wIkOE*qP)s5Tr&LB*W?A8k}7AR%{mr&uHVc^+)BxZjwiGXEGpL;a(;=J>-<+ ziwk#q%R>4uKyTG8dcy*VHG+Yx?0FS~@B zJ~RYZzb9-~2`nq4&5gnE(g&|WO}`7}3VdD&q#~R&(4dBJ(rLK3H5tcKO`iPN9ajFh zpZR$-abzP>#uiFq?7?xl8h5qa^;EVhevEyBx37w3XqF=(k{I$zxq)nQ zJd~+tGtSF~y;1-5d%5k4jr~+kt*E3GkC%5;F=?87>MS0NGPku3cGy2>ijocX+kYiz zgKVJ{MXnHSL0JfiCfP~qX)2byVpsix=xZ}u zspM8y>9n%U)C3A0(;Rcr-60uFR}qQg{nkIm?wR?tN-jpYdZQU{N3*gAWlawNNd+d_ za{IpD=4o0q$g*)F124GYqInN-ZGP~=yt4ukzonV+$zLT+7rN7puKfCz7w;;|J4%hT zgI@)WWTp?zc!y<06}ic!q$gaBwEn707d(deWA3e4u#?0vN?_oksq=G*CWs1#ynJT@ z2q;SNOa6`;90J0G>2*e4lCE$q^|uy=vQ7=N3fQ6b8Rt^WCdnzB<@{1PPh~WvD>)mr zTK?2X6xT^S+IGHd_A@Uv&TSp@2IK0p)NaUq<<}J#DWq0jc}jWk@a_*bag{KK@c#-p zFCYJ7va_H0Jbj?XZ@l>;wWoGV$XCOU#B&c`HBUv33nCR>3$t$<$+;nfQH=;U{fk}4 z1@ugTNC`!ghPUS_CngV+ZCR}`aW`yo)j_9PpvaV6)Sc~)Z;y(JwVov@2y;tg@kdmb zqeV1+1I`!nk1htIy(Ci8b_q;-Md#D*F!ds)%C8KHbd|V2B@8dC!Y=R#h}FaE9_@^7 z6SY46POL!RUQ3_0�JFxzvBJYnu4nqPgVYfVn|HqzpG7MbZ7`P=t(!{1|+oDiWJ> zw^u{MmXgksEAqdI{Rph+ObIy}%z>e{;V=$s{-WBl?6Qt@zYxNZqjismYWv}qU@0{k z^3+(0nLLhX6)~7pYy$)PINRTTctkt#eQEt8{`Sh@Nle~JAWca-pv$h1(FJ~{21u6| z8~ZpRb@^5L;6vtHjIFcg_GD0!Cc$Hu=Dx zL5m(oiq{dY<(N&%@Nc9pNHX0tY>h||(8`9bZcjni;Zqz2U>i;DPH}uL(*e+kb?hi zitG*VX%npJSEZfcMC^eYrv5}u5SGH~&c7}LkX$E&K0i7~s{|+=_ofxmf{OQ{^5WRy zpWCL0k8?{cyYk6)KazQ;p)8I^SiWt3wD=|md&*<+v-rGI`q={lM=>OpNG#Q?>gAm zB5r_p-D8wqY|5&fg4>pyQ*>#nNgXuC{4Gl|U9{Yafmj#+4!U*?jW{f|Xo|2FLKtU94QjI_fiL)){PuVe&*Tt0~leYTd!<^3GhnoB{^S8ZBDLm4;#f~_-0XT z-O=P3Z5p4cL1XD+WTPdI`S7yxS3DtZb?m2mxX5;kq(*mm-+QJA0JTtR#o%ZN=##3a zjqA<@ZXAN&usTY{CZn)c9J`2$)m6w#~^fVRGUz)|D%ST9Qe!5-N_*2-`m_2%Y z;u0Eho}{frFFg7?nv=-SRiI>?_=<7ED$xfcczPx@^8|iMcyb??vrg9jr*~H?GV*pn z{k0*tzK!vst4=F^wm__~wN_SO&%l9&z-%$%i*U)pLA&t$s|=k&0ZO(b9TQw|BhULI zaSRt>JIZ2cv9U4?=y;H`9j!`%pbDi03ckw$ow+dT0Zqo@(B!!zbHxlgb zHLsC~IhHoZG&Hgb{O8;M`O02ZZPgX2=orD~ser1a=7!mjcXEo}wjes*)CtdD zK2$~xF9nzk^Et+Ihd7`iP!a1JFCYAITP?jVS&_J$%OioUW2pwwC7WovZ$p*j_~=u) z|DVcIZDvKs=v;?o(Ctp5-x1Vi+V9gletiF5&brb8(UxThXlL;iDxr5qB`FGN@N*XL zK>Ho_86sikBg+hWd9U-iXb>7lk{d+}gCdq_s}QYm@u)^U-+^?xmE>*GT>>au#kHw+ z-qjB{R?*Qg0tDdz#ol^s9^4b&H=YnJC#MuF_kH=Q11;#V`}P_06Q;A<|2GTZe^w}q zUZG2zl@Q|uCPq8DVIH&=U_TYBNu0JneLse)?qYX`T@hJ9zXBQJ*+30+qCq>|J$|Xq z0M62Y(8Lb=Lm(b;OM2y~2ESpJJO8NEWXRB@3!2ar3v(>KOQaSKieCEj!yv;VW2j!v zxQ0q)RGZ`P=4~^@aK;z|%3!Hqtyh{E!eCnYrBL7p!a&g@t|hFWAWin%>V!iiCTTU3 zLkKvpZZk#qlz8VUx{VhNR7zlU*2L=Lji@P@#_-p>dedQWg(00pkVXkL^C}8lhX9}` zb=;#FIzXvbE(18RsZw8q*8;5NX@3E0$TV_KK$OU@ufL~6eluKlr{C~XlvtPsaUO7M zc5jwJ`s!8rjn6q81;2nfAJ9${@RVbXQF;9AGOKlULzZ-rkz{*KRPj%+$dGB5`cMbG zU7r7o`p*wN8=6{jIo8CVKK|1a@I_xR2j#PTjvd7aM>VtVRgMi>*Ii@qrZKe%T~M9= z{1(E`5Q87Nxw>ufTb}aHa9PI3h#H&Ry#O8?RyvU9VXhY1n-%(sUFZd8C7tDPVZk~5 zd4pC9jl-|VGlr*jGmfg(_X9WSNaSxllU8nJux9COIVOoJ<8CE1zqdu2t%a0}b8Cxh zj;Wq!Q@VcJS5z}VX7P$_TeIkJA`( zmjoo#=d&sclETPA@*RjbjLVhicaJU zU7soeMrRAGv+0=cx_wn0QK#tEJ5z-0!_iVd(8b7UJ7-X!{I%eplJQC_`BSv~l3ccx z*kVvX_17z$J>7CL0bShnXz_NNY`u9@qvTvTi#aQcK4wy2%rrRMD;{zk5_%WU$kU^( zi*TDWjz8{MMjLA0tI~Re8Y^XaGshakuTLP%hS`orJuqjJ<(-`V2xlDSzxcMZBKr+J zxD_s%@sVW%OKRd%Zew8-2qVN4id3INRjIf+LSE8ExTU6GaF3Fq|l(KLn zK`lh_yq_vu;4>bJj3~aFo{Au~)9~;>2@9Gz63X|Go`2YKhGNv`}Jd z7;;V@#K0UYOU)FZOoKbynb^o|SR>X&d0pwOK|}Z{hM%;bXCP`&Aa?gd(<`BjE&H~0 z3uyc)>n0U4J=pwu!<}W-JoXvm?RTLJ@5imO^si8FmldTaH5Ey(4z@J|wkn;pHIjO7 z?5^oJ1XxZ^wtymKqsrXkq5QwNalbk)z|!q~hKvg@q>})Q&aUW>nN8)Btb)NJC6Q7) zboI2tB4?FapJMgP71s7%L@M>lpM7nLxetMGd}7|Rt!BCGU?J))qo&rKO-S~@LG^?` z{VU-@jmL==CThJ)-MD8au=n+b1ePo%RWtKp^ocN(98T#1cA4e)+ONB|$A}wM5r}D5 z5yz}4`F7$zq;Q6I(|dc*hx0Q9G0^3XS9qI{NSnxJYb|wEaX5)_uw(l=U6I^5Z(6g? zG-{yl$B2?(Z*I;_Ulr?~>c$k1@wr0rdd94}o=#IL4a1^s@A{)aML!aDWxN#zF20i@ zN-BqWA$F*d`X5kxo;$GfYg26aajwgY7wDw&){_$A2v0~S-@El|v#jh&Q^R0%rutW> za4nN1G5l0IFQqrzaF&PtqK^>bT?$`i8@?EKFKWMrF#s*PLhuh%Ey7Xwb^<~J@@lfa zPRJ%E_XI=0IQqh)8=%mk9?ajP29=`+Pig>NluME-;9N1vVf!+EOz&gj6IcX;4G6yc z-D(IORee|b=SLeA|4-p%pl?(udNf1iv@>940~;^46Zvn*!C5_p9lA46>U6+=|L`Yl zq%3;q`O}SMH~s&`-djb*6@2T134!45BtUQp7OZg#65JZM5G=U66Wrb18)@7ff?IHh z;4Z`%QT1#EkL(b})amKpQ51YObGn>|T4+Pye0fA->k72*KOXzLvq;Xb6K_OQ+ zt`qYmdCf|~w9K-m>LYRnU4l_jC8rxTLuvEz8Ts`+`YAe$xf5Hl$`8M#II#Al0Tc94 zt)Whb#l;On<&A2PqT=!S0RzM;&w^F>f7p!<0)*N}rw8dp)&^P2Ei-7}q#Kl(?D~cMW46#Au-oAR>IRg0aUfrV|nK40AyK zDW|MUGig_SxLT?c25NG$F7YgHGv&QymcY2JkWA#K_h+SCt~i(BdJCaAZ{Jv9+oEPi zGj0A$g4}w#f}+E7sbefHw3&XgHBrLI-M=q#B%eubtNclkZ*+7cZjtv^^f|HOII{fP+7Rsg#M^kpAJhNNFea<} z{f-2uB2lb~T9Bc!ic^E#I_^s+>_It8Ivvcx_vTNQ%d;b6Y!Q=KJO@?Od5IvpD7ys+ zoq5jt?e_=gTxJw;J!a+p@a6_Ci^;tgM|>o}fnnP{?r!>fvbnDuR>0Or<8?R5P)B_l z0P7|sl%+o!+`~i3t08NoJ5cymZCuNzs_mOoeW%Xe43G!hrSS6%%RX=zA4CsKTyZEbnw|uvkM@-v{>93S2rW%dz zcdSDKON&n+%B3o_U0v0YJ9M|~xO=Q2y0&U+_N+e}woBAl45L7P&6n0J1L=?fq;M^9L{a->D_hLWn`wthKJ$xV!rHdjcHD=@c#z~9|G$pD+EFr z7?gUmM!(!uf%=3qcX(r4bq`wrNgt`i)A=`sKXIcG%VyaIB{;r`^m&@DIEF<2w4M?uxO5j^*gPW8JI1NWNdqI`&98t+0+AtQ1QbnLQ#GteG6l8;YAB6MG~zDmy4E z?x>6pTha`X06RhWI^`%F)Ybv8a-p5Y9eyDr_K%wDY>%4%>`~M}3P|-|c;dwL88)wB z?BdMtClk0iX;d^4AMRn(*9f_@9#ZJ!U$!c!q_2&OIW?W=4;_yf<`mFts!vSccATyK z?q5y2Exe}2Bf{6~fR0E4W|I1KgNHpjkoL(pjw(86B>Ub-8zbmSMAfO@ti7|g4Ck!8 z%A`@#VmbEm}`z>&UGM zZ9!G3TontfeKh|sC{MiWaPo;NC;;pSLs1T zvy%+Fu?Q$0ba$AhQl^5;YQRIrR;*@IM9nOz50h5ydm73Y)JzoFkeIgN_XKZFVHVd( z_cfE%|ug0EJF#1dLGFRyu!#C$qn>+zm$NYs_MaKEwiC< zLf(#KHOz3ZG>;ZI5xuKS_{|6QTQ)Y}s%I1{hy&AX2*J{*Y?C@OL3Ih2C=X*wCD{cmU#)QA`XZlH%)o( z%Z54FgEN@T&`iO#Z7ZZN;S~|FHsB1C#n3}JC4HTG{!v2(#Apf8Cc#WH@2aF#6Iw&e_U^|M## z-5EC%yJ3J21jf!FU{;n(LIur1-F>b2Agej+)Or2bV}3yLSRwBDXyl0E)l?~yXOvKh^lGX@2Yy;Zp&j%SDhBCN|kN97!_ z6<06Ehv0$2T-y1=aTgTL-rC=`df}oIU7UYB@GpzuacI0}0|BN(2v|FLqDRNlsC}P5 zW%Fv|wl*_d==R2Ue9AJpeLhRNJ^3?7tJY=(REjcV1p&3Bca{pyJw^+C<(nmx*l z4gM}pwEA4E3E_kMEB*O;lJ};vSPIx)@WS-0QcIn;9vmMTECYP4_`8MUA+6r+5ZUw( zi@s}R6f}{R#A+mzv1tr53&BDw>~$YkF8UfbFxz} zkvCAR=V{y3>-Jo@zI0`Yn)$xCe+HOqA($Wl;a4n|$w`10I)Jbz{x*{jE!zD3fIEkb zF&;~nr~g_sF3jrCs`Y54@jD!2!$6gEm9a_^%=_9rGh^P7Y|i?_Vjzo2SxAXK9Vnvc z&h@eN+7ig)r{n4?rTPaI+3LLt$s&t1U)&o&%H}`}&;8d90G0|7m=Cg+*@@$2Q0E@&M<#pNb56m!iplo|CjA0sT(^qVDQF4!Jw@5b=Sxa zU^+F9-+=p<7aJul}G$bp#zViZ^csduk1EHI=Veq&*AbiUXSjB zgI0|d%1`d6+TA%-#!fJO1~lsU$gKv(pJobJ6Ayb_lv;zMKAdj!C!Fe+fAQ6fo@%nG z>0^5Z??*bIMTyY3KER~733s+xBzXGTXO0xRjoV5Zt1}Oqz1=-GPaZqXW=R*W&u7*a zE0{91v=03350S2HG^GRCf8W-`(-JZ$4HTEfJ08c|a-G8TXcPD)QK!LhkN=M$y(g|u z+Z+O6(Y%y|+03CMLmXoy^2%NQ4(KGAHzR8<9 zNAR%+8+pl_pk4J;dc&OWculpnosvZ-71Sr0&o96_AsM*rjc!_(o^Bj2I+k%V4M;Wi zJ}nhc|7TpUyxZ4K1681rO{(l+4M+_S(|E<0G2WLDj&RW2Kh^!W?flmqNWz47;hujtBb!?pREF`YHqkdPB9L7w*}_o0-w%%x^2IgW zs{Q%`W(ilQ=YKsu1e$q;2N!J(3h+G>W8n6!o!7fH=0_Ak`e#&e^HSOw&==0Qu7O6r z2Ko;DL!``s`*Mxh%<0^yhy(jVCdzb2+z@RO`4tqi{OTsW@9rGG#l2vdt9Q@h4gg@f zAs=?R9*dPURt+NHJ0?~o;jD%?p*;}B3Y)mRdptqY$kFQaL&vF6 z2?xDwh%P3ais%-5|l@yL?)UcI%lp^aij`70|=Rj4t&VQqCTl|{N8-Fyyh56H z@qyc@WdCM7T%Bf;2xooW0%rh!eL=Y)EdI7|CIdr@HB1wVUr9V9|MZONv9tEOXHk~V zQv2kl(D(_^z4!jWe7!aOxk`< ztRZAo_C~ORt*j_LG!13u>qe1hTS2Y%$C*m3>}Ewv##!e5532~7!^-V?b5?)D7A;#5 zXsRaDF^U{vw|CCy;Cl`Ltf#-`NE?@uJ{JI5UqraoeUqWDB60O_OJ#MBu5dX-@F7;G zqR%L(H2q@d?=VrB*GW82xoHp>FuiK0a2!wTgiFz$aGYN0pKLuxh1F zisb>mPTq6E3ny(z(*&PgwXM(YVc}c;;sM)K@GiTTZ;6TxK&$AKey@q`@jIftlHE*g zrM$vR+-7_xwkAkp{nbpEXvoZ>SDoqfrXMlQ=yf%IO@`PnoMHqtnojW6bDf@|rj!Dp z^zKHKwXaCloo+Gkf7@n^l<8YEM*dR@woo)yMGi4ak_#K(F;+mW5Ot{XN)fgU{k1q? zST!|ZpkEhkk|4=o$h^_s%5Y^QyS78>$X2|pGBp1R>_1V!T6}F#S}FI6ajDL7v`?qd zU4VHVsQ5m;)>ppF@6mFL$OOvz4y_8#3Hf*)7ZPyf7~tdOtO{7^gIMLd86^5NR4@`1 z-;E&zatu0CIZg$lqG)Q0u&bBBia!Usb<&0TC%wcCUK@}HjK~CAff%5l0@Ofw`X0)m zwiPC*S0e+RbW`LE7Qdg<*7nDTh3rU+c_DG#W;g|^GQDIy>hCx?)NW%l~6!qtUK zwPtrsxuS(pVdYjvf6~>k3(?4r&jd-m%}7oX|5*}*)_|#7ypjEl6bnd{ z^p_ZoZXjg5L-P@4jr95B*BM5f4@NjeL57Y#9;H7Wl^$$SX9pC^UpJrINJ9ab7F&J) zoO!L+@e@Fmc)HPq(~{GMG?3<`1VXSYTHR?;NK|v3X%w);vA=3cs#cUE&FgaDo7=z` z_0Hn2+clw;an=uZ^*%0D8_OTW_<;?EPIc62?*|x;Dn}|04&LBJx(BLcOw#(FqnIgJ z4SaYy7b@`&$iU=VAW2x5a%H%`sNS09BYBUnfTo3fEwSM8A80GTW;TSFv#KToP5(r! z-68P2S#=MF9Qn06T)}p5Uw&B~U+J4fP-@Q~8BD?jhR(YpHy{-R)g<1U#0+^qyH_CCSfTBw>~!D} zy^5EjuqBNR+{z8pM$d>=Y#km}c0y7tj|s@$alP;fDY{$?HsNJrJg*+=V1HgKC(Hnj&OL{`oVaq0wDw zP72I<_2!JQNTteG&%D5^5!!lB4clxb@j}%&&=uQx>@=*v`Py%_#sG;f)Vn5cm&aFf^BzSr6CqDaGq0hrT9vh9yeH8(hBO>I%bLvN?~~U$vUvSqPT$J0&< zd)QxFCjbM|12P4>t^xnTPm?`X(r#F%WkcjaX9g0Lp#Um;f348?&-DwBu`35NC>5LfH0?f`UkY2Im1?+F@vb2I6fsXxm+1uBc9-j|c4FC!I z-#vVKy(uQxfOZIMMgP0(9Z+`g1|(pe@!#D8NVgO~MqXCLvl0JO_CEP_-M8{s1^B`L z?#(3|F!@i*JY^sLrwoM!D0{bj+X^VefA?nZHErBOlcPH5f6D$>fPfhPSAc*X{;vl9 zFNFVNXa3g)0!I1&=OXg>a@-c=P1&EZO?b9id$T&>5I0%~BBu-aC>d{@PiShDZ|m;v z{4%q0vNASe=Cm~$j3Xw+E<0}Qe%b~)BiVCbgN=m|&SpiX&BiXq&w@flQbvODV+fgb zAHze;FrC}etYE`^Fj-*4I}r$c87RKzrf#p=uKeFcZz&XhBP18&+3vRNzgBrm@%^>_ z04of2iN*=>-01&Z!~!E0?(@Ho{9jrAuM+*g(K9wNRso?!txG7dhdRFd<@{CcMPYBw zK2=Fk7mI#aO5|5%@l>8v#jvz=ZWbEK3M&6Q=dVSPbE_bndxw#?Psd8y2=Ni&h<)M# zB&JepozZ>kD=fudwtoB;;<7Jd{@5`?dg6fMe|^$rGq+5E zxzuzSuvjwrM?np5XJ$`PC$Yq|fd{_(F6~Kk52fI~y~FUonveV1De7p~X!NWmUtW$H zRRJ0G;J+yUdv*PMHjxr(lT)DQUro7;y7~D#DmiHcIf=QL!rVv&f_W-^cS&DprNZfq zlx3kqXz?n}7yh|LQn`l9fH?BbBIu3SR^Hl>S-TFKc}Is3a!Rk}ba@m>0{2oEk?}YB z1G%P8bok4l|FTWd*l)3C($7X#(Yq<~0d1j#AhEjjXChwnn zPtJBAW8X9^52aKsXo_>!GK~<*Foahs^Xd7j%EUJdcubC~xVl8Q;x1O!iypLkR3K+e z|J5B9Pr!7_k=EZ_4!k_%jjm^rn6?)=CP&7Rc1&PDuZ>#;t|Ym4|6Qs6rb6tQIT@Ry zpkxQ)tAPseH8l(Hed-QPO)V7?s8n0=ZUcF^74Uv+p2m*UJsiEl_T~)2P-kRIk(Dm( zwl6j7xhOTWt4B$j)1)N2Qp?uI3v)~r5`Iyb%JPGsF1uUi*fZP<OL72PPkGTrD>R7$2rRkcSQ`qUwV5h))s97ia6r&Ds&h z+E3Gp$Jzz#BO-h-X-QyZz`H;7##M4t^NXQc>bRWDzlw*x7`r};Caj%xlR(5)6b_C{a#0vOf;`}13{s0WDO7W>fFeaj;{<;5iz^9Va!H1U>WS#Zj zGpjbws%tJq|J>~nUOPRVVio#20<_m3a|J!9simSEgNGd$qE}Z_8S6K0+D$9?R<#SI z?vc9WS)gIjD|_0LqPcK(9joGh>Sr zcNi*@Lb@?-E-H7!6^FjnsWWz8uO2V8)f)M_5sAvF_R|aAH*}&@rZp? zSwuzy0Ywc+vR7GdjX3atN6|YmZdCw?vE~)W3k%jEbz?!rnWp>b?$9fLWxJ0&6a}$I zyc&#cPtHA~V_knDF1h8%KI1MxGPRXSN%VB^>**s7?st|`AR9eLs+zMIcDX!u1)y|= zMEax+p-#~{uKR^@c0hNXUUirEfw5}#4r8W=yy!GIn>cobCiBF?cl^3q%Jj<|Q!$Es zc^M-5=P38Gvc#ms-~qITNaIP>5n@fZ!0re9mhQA7o*rV%bvYx59UU4lhhceHD^h?= zjA?+8)YPnDnd=;h)`@F9qs|oQA66gOWCb7exhCiveRVCaU_e!&fRqe~&M?KgZK+zv z?;qqN@P^uTSUAC7+Wdb%4%cUhG6VXj{$4bSJ3UJPzlkf0fO_Ohm2w=1eqchhpl8Xa7(Yy;j zLQ00W6{v9QN*39~fS1c{BN~`mf&|FVl0?9eY5Bmt{13Bp% zt9C#+Qp7t&Ii^H%a{%|rI)KR?gFZ{?XkpJxE8K;NMFQiF zL4N~U1G+2o>fi34qlSOgV;YOz2j=ChSf2@PywRTu($BO3bl;gind1;5z+^i@E2l)9%4A79~ z*1OJ85RCYzDWDPKW_M~mZtR&sgQP3H*v4$pCL}3osh}zA2<3G$qXiZFr&XYH|mVgb{&@9wQ_xZ*7paIf4NGk-nWMh(ee%}fB9PW)vhr<2&*v?~%s z8Hb`dS>B`Fvkj$Fw;H?WBK0xtNA%Zsh>*rZp7M}cP#WBCSIWXuCLBj@9GAVI$oE+S zyF7#y;cdo^a_=4VfAv@p!?V|cUwV(DzG|w4w}Oa*s*baep9Gsdy)s@+ngiEpvvo@G zSTmEf%6c$PB8vYIQb9m@$uz3>$cTVleB?>jpB3IGnYvoTZ(BcD7|JS8!iY*K&M`r- ztgIc)?CfB)xhgf3E2kTUJ=GyiDd(4!KeN09JeRr>{kYqgd7!*pNkf0DS6T1a=j@K&{m^ZYG&%qs6(4$@iz|9T16hxVeA0H8!db^Jc zHLfm}n@fbXl12PV-j2B03~vY3KVhh;E+hL(#+?!9*sZYfaJ)CxzlpNS)GHDN^o~lQ z=GeSf4on7D;08`i2#}%-!zqx5EpZm|mjsR0n!%+&xzqykvk%Uz5pl;Rib(%#_4udd zlP8+LmoSBt(oR%K&m>~;|C|yk#=9$pLXnINsET8->zq&m>!Dpg{Z5ZQgpSRTN~h#~ zj@vaKFsTa`FQ;BgE!O>d@6N(tRf05}YEDr=DUjcehi8UmMU>)idVx9vF&AYzQffO1 zR%keEs~_tsj^sahw_`~CQ}-sq10=BHz$cHd7?DteboIegT?qYdw{{Pr8lS?KezyJ; z0`DspU4v##wTsI~^AmzY+|A*%k6^$exgi;!a>SRCa5Lw$% zSISHTf6Omz$sD%0Qa;{1*t3i0ilWClI}@avkIO`SI{~#i2#@?_b}IVFgfLF>eA0tSI$?#?W`+jqCCp+lao0LWC8ZVUyal!!EEbzt9xJTB*~gxT9}1C(aXP5?iVY1!gV`DoOu!IN+Eet(9TDOt>F@)W3UgXAL zazydhDxryEb8KG~!0>eDc}Zi+9lAFDb*7Om>ALwCS}E>QUQ&cECRf+_2A`y75B93` zTf_j0|GRW?`~fxjm+$_gVzPhcL?=7(xAl8khl-y* z9}MMv(S|yCL7v>umEgGx?kwQg+PgL{zU19d_%SI>7JTt4)k4a%;iC8`XWCl1w@>u- z{1g|>=s+I2E_QH%+H5?#N2Hrm)yeUjJpLM;Ss)0J?-=VebTw*jHu*H0zVG;SPV6_= z&(pRK{9}jrT;=t83QL5@8uYc8+a6W-S^qjj#>48k%rO+pXGD0dcpZ0$z8_tz?y=TC zg(KVe#aI7qqMICQ^J_Oj`SZe7702Ej>Bax_>R=1@-E5%%m^7yk6!E-TIr|fOUui3B zSt7t?S59%%^oQOtR|fmnRs~K$uE)2b4TbsEMryv=R*L^jp+PB#G*^mP;dE7S3HNz-HPOZhCC1p!xEe0WRcT}=B%ejG0$gSMIx$G*`~-MWb54KP^=^HLcrleMV^NP|tbHmydON zn?+1HPni13*z9EjYH9uT@Ec6fTua*+Cw*IS!;F58h<|C_@aa5ahFw3Xaa9qxkRt_e zI-u*tFF_US+h&3X=Pm(NxR@%Qd@-YDkG%2Xi!Q>){4*uJp?G=PpUa!&Sc>#9J(zK3 ze>zJ(t!XP`?%1O%zf}C3=9dxmEU;0}H}`ucZ4(MxiXUhDo$+ikqNn!qb#}b6(((fW zX!@nRI${sw{ZaU+_(iZsC2inoYVp{FE&$ENe8%4a5k~6?vZ(9(3H!kZA80hYnbB6K zWd-;Z%OMOCoT{cSRFagTb|NK7@=;aQ!T>Gf$Spe zi@DZ*E&IZ0tpP9d#k38o%)~I|E(JvECaP|n`={(Z<`sxa~f7enjS0Fw=_txn{>LnG8sg;ul6ts1D7cf_` zAEfOxlFpjY%XWm{8yz01ZD*O zEr({LtG7iIAa{ybJK5h}1bM!}zvbp!`$-x^EP*%hn$cr&)1lsks;kbWJ+WB^?z1npAOficNVb6c_G_JWY5khN-S`$X>n4Jdg37~6dc=?YD?j*l z^Q=(mc79AML6Q`tt4306S%4hd#P{axtKfYx`TodU(o)L!y#Bn_SV_&G@&Y1{7#CX4 z47&Rhz?s6UbBI)AePhP(&G9()zRWxku5$PYa8(?guC;!Y%YseN6?>cB@A{djsRUJ2 zlqURwbF#qb-eT+wc@HD?PHX3-y@I3FHJ42(bJEJUuVg;2^h0;g@P}?{Os1pZWE+E) zu8=XUftwVUWFK3+9LAyop*(SKhr&a9KlR`XT)$!{4xL5=K%+FZQuDR9^4PJztC(0X zLQVHEqA{m%a*Rlw69Cmaiz_Ub*rjyV=ih>)zB~CZUZ2IVBq#|n=q(zgqtq*4z zNS`y6T`BU7;Dy0XAbzM-t&rJ7Z29DyP6qV6c=2)TAhi-rOFKpduk6T2#`&fH*w?SB z(coj{wx|6<)2*HS zymuWaeJT=#^MUDq_L-cDqXLRXX`$s;SIvWhjb%Ja>m;+I0;?PHM_Y*Po%wK8Ewpgw zN$`u^a&#r8NK*7sS4waohT`D*6HexmyJb$!(JzI7wsnFk#ipE2ge|N%5VNKXmvfF+ zAy&t)n)X)4hjcK1LlC_|g3>m{f(2w|w@RBE8X9_h(nimfsJ*|3o;Aejnnk_;SqYmE zc}hhA<)5w|ydvg%bi`lqmz|3adS= zl5t9of|emh4K5p0ReoBYZew6Lv0-VUHTpL&!;~`W5Tx9YUn#NPilag{VMFVAtf-VW zy%SU?v{dLm|31PKuTEpn8#=Rj;4MRTeW2O7U#T+ZmoRhrl$KzfT5McEgQSg)J#&OT z6UKUPJuCb4+1#wh88jz>txbj_9ZY|)sVF;Qoe}*j8C<{)M^z`1Q_ z;D930$;`Ntfm<=LwCB;2Exa6-+)Avm1Al0cr{>>Q=aNdZN2i5cffn6D9`XaxU0VMq zBfH_)jxMfG4%l)|LA9vQ4+hSnv&a=?cE^8S#zTt*%fATK!sPNBLSDwryV@^AoaZZ^ z1s%&l)kd=7f7S-D0H;#pAOdE#SCqqD6u01AZr!e(8o)R5#sr;Glpc0p7jUe{P|a`? zSDh0Q@kF#fwRB5V2k?7nNX3E!w2f+Go5i%8dAA0x{!A_oy!}aibZ9qYd1O*W*GUM0 zv@kpR_sqF>R~>rhm{*F5I@#6R0BlR^2pNBn*z+}_*7PoZ-AY@lE+&TPQ4Ndm)Qg3C zUKz>kZx1kf`UF%OkqO*IU8h-3hdw;APRLlxg5H!QWe0Th5sw4d>$&k|O3uZ8n!YGc-%Cyu8ZST{cpR{L2B!CMrR zw_K&iy~2!m8xJOrGpzdi=zu{iKW)D;l+eW` z#Qu0OVMo7%tJR3eK+ua#y?uHh!ul$=BK>j0e)IcPWPF_`g(6Gg512~r-xgDoCA7H% z$mFHThlX_+g`pWf@^v;(O&{MdBLqS4;x9+ZA@b6a1y472_++wL+4;$68Go){Xzctw zB%B_&Qkq6y2_O>*!EGIB03w(w!S6T|`VIB&W>YvZyU))oW4?Ab;Fz^AT*JKF0d(5p zzerkk`Phbwz`Jdv5EV>Fah!^hA|pb(z{mcW_@o|vdvSE_`hZP>(AG5u6=O&)0!B~* zhV~tXW4%L)54x_}Z8JC1WfS!yQ=}{P9Z3s%om8WIcpp z0vf>C+q$i}H|avG^QluC zJzt-C#r%tttIXhU(^)SzhK%9asu#OILVTn8+(Os!Uy)HXqm*U zP3khY!%T|=O1vv?|X{&uiEOBxi%JlzvF)TiJ_)ssB(fuO?INN}kok(-Li zDPAU?1MD*0bSaN3jMOs6ueu#a)CovxhVY8Myb&&HGbB7bIzU|E@44P8C>UA=D-)NL z(C4xJ>f&7RW5q*AA16o0C9TdhM~Q^;hMv2d-3Zh@1gD1u^QT#p1)hsQAk3lp-p=-4 zML$h_{N=Ts*>%;)9}S0;v%*w+BXNV$9a{BfYn7wfX8hCT?-PsU|IP(~ALc5e6SEM0 z{Z&))Gk?nSi#oxhrf9;M4yX~YI9>Yx$?Yvrlq{n+DY6O2!Nf@>%ZP0*_@3)fV}XNc zv18iD51|4D!PAs-i;6=wBgzG~3Fk^G8kpN{;!V88gL^K1*r>h(gI-r<7APH6RZOW4 z{iV_lxnEu;-c#dIFG-WHs^!aR&xn2_R#aA`FYe#-<54QFu#g`RC0?!RfRd2=oPD+n-zI*AhYOTF)*rrR5bn#YIg64!rI*(FH?6Rdsr z36*(IcY?27R4dqyN8N6+=Z4W2ea@pVwDjSUCsc!X^5*aoMPGL5Wm!IIU-Nd$!~OQJ zOw05R)=E;1M(g@G$Vc!&XE2yoLnd8_jWR|YJxLC z#3=;Fr=X3_&BW>YQYG!kV()<;y%Eom)Yq!AE?)+NYcyuSDxyd-43>5K&e#iutdN|s z@@d$Y?AAn5+;tbJb)!$;Bm=6tILXZrHUmEhtZp|fsxV_#VWK5DBr|*H(j)|ORcqm_ z?s>`jr-}Zajfjq7$0mcPpzG{EKj8mFQ{AI9_$?#3e(NhZE^@E{yPt!KVH#e^+No66 zL=26rB#tqHoYs7M?;69&m;4)T7$w$syV(vfT<&GgL5+mjA{l>!`x(uZQ8S^Wk1!}N&DNafXH$v?i7+D2*7g;}3-~gGEnQ8)5Yc zb{QpmRQ|>BE1@+5dV8soXDgjFuA7VrehS_2@VWN=KjL)lDtnN08U7Vz=$(4@%v30o7mnOLWoni8y^BiHWRap@xY5JXN#;cDmFlf57GnM(snAl7 zZ)UK~xzF>nuGd6_BEB(vvmF>MXv!Z#77)Iw+knjhX7qZr<3HM{_f&nJNor8+&`SQ?x?O zw+dM~f%}7l-){a^BP~#UuId`%DF~Ep*RzQdq1#S>NZ3e{@z@oev_7;xz*_6En{mw@ zS5TK^&4K;d>>O}5KXhMdAm-`7NXeK9F8}$(^{-|_pO6WcjmAIYifvGb44An?kpGnw zM2Fry76Xnl&U=dwDGHT@!$|Vm;A)DvC?_w-8N6CUu5HI6W|Dt3K z4fB^ry`#6=ndAJ*tB7SCv@8KIYfdy)Ax8qL{lUO-}R~0c_e_-bRkrl&DDb^|eF>l!3n8OsR970Tjq?CHzw;y@7 zvwv-~&dGh+v6uX1;uKjJ3@l=MXU_|q5EIL4VWya`3uhc02nL@O=x|MibTZVix3nG9 z=#_MCL#g*@S$%AVRf^$|(JUyPL&{Ft^27Pv!t1C=P6;M(m~uHg3PmYZu77j6aLf$o z*3&Q3V7=3bu#DPVGw^m5DtQoQY*wF-CJg?qMtOQI!1$Z7y%ZyXDj0 zyi(E!@jDtj2+Vt#{~G(=D6+;5uy1C5xi9_NfZHx>TYfPgFsSp{Tk|KdTSAWbDIS@G zt~mNFp+f=H%IWt;&WX6wl?RLJ17GL);|&2Y%3ASsG*|Df|7MA4l&%`{8+C9bvCp_S>T#UV@F?^RR_Mlu=n zVJSf#xBf|l7Dw6A#?G1Q-`}0H<;ktu=2a`ZB^EXnPp?;2#yO|rZ#2rGf8lp<<}-l2 zbQVoL0{S7r3|#}^et*G^NRnnX=n^$-mLlGrxn{T3=^$qf*~HWjx5IUcWr5tS#LS+N zJ{U2vQt}AAMKh|ND5E2#E_rG6%!zMVUOGxoT8RMxQBLytvzasX-eJK@4!C%Zd>e4m zX(IvZTcn9550d=QdI!nTX>#FByIjslHIPV5Ir!xy`eMGp!DIR%-|w%lD?H~B?{=lQ zUAR-7C%H0!(1NFQhiPW(67+F;^0%BP$!rC59ef@%ivgymbQ5SoCtu=rT%P6D0Zx~Z zuP}uxH^l6^^_hl7dvTFLRK zxLz_D55brjSXGoshz2E#rq6KhiW!Y|pRkdNL>Fe}bzpx^mlpjv%K&zw4G8=3fbS=$ zy72LdSC_3_A#PP*2Jd;UT`Q*i$0VMONBC_ku3f*$(e$8Povy9i;LR%S4<7?;G$tGo zrl)%w_gm4WX9g!ENPgc{NY=3U;!`ExIcaQe=r7bSa`w2W$3G?hs*%&b)ISua!_w57 z2f}j9CWz}dJvYFoc8B8lA2;`{S41@gMNK~+YW{Y>{j;TS(L&IZ{03tYC2jknS(o`R zopwH1?~*q44U$>ALF^_!(+2SVE6CDJ1;%Yo!cOeC&S;+ zg11C$^>EwgIvo7F!px2YO)dUI-YvoDaMy0p;&rQw$+s4KjKkZw$E*xSrdwhAblgPF zcDB?*e)W(LdS?(Z^VHnmWQYz#IUHR{3Y!`JN4WRSPL$AfQrvM&nz;SSyKs0M;VDGZ z9?n%x?$6sus$==TG6n2aAd6SwhF; zWaD@9mGq=P%_)o1Xd~gS;l{FG`NLhDj z?1s5lcC@jzlnQSi#*g`+D77I&Ge!RDs~7ntN%wU3k9H`~Q$Z=C7cez{ka`KaIWDC;*5E07Z?x_?_lu~S*RBX3N zEj_x=+oq(&Lt(wwqxMjQ(@r~@t-)BZ&P@6f0eE(=*Jm{X(py@RBiN9aHosq|n20U- z0f>!qW-H=!U4qfo7nSK93g0fRm%9xIDW3@IYq_d}%QkKkN@W6z?MOBXyciya=j39y z#mOScO;WT?QrpMzOfjoPpC0h%>CH+xBM{$k*3Dm@#BKzo`V;nO(-!|+^Dh44VfouQ zuERb-erxXviu2Dv&8^3j(W39OtqW89;miGAcctb8UM$kmiND3-hmy-iUK6t!JG-Tu-TRoY({u_DZAj)cc;+yZINzC9Zm5VIQt( zj6$pFyJUXZj{i95Cok#XpCnlkL?TT2^p?{*9R|=noKK^D6+oO&Qn3gOnR1ry5kd!- z3tM^{#V#FOI!S70OrF9fdeq9x{XC!LlNwFox+pi5I)4>4y6NGI9~Rnjsh?S+IMVD* z14m%LXtix@%1>T&ZOLy!1my-wPEMHzNI_WXCDdQRDa%}QY1O};{;kf^GVQfdy3tBv zvGwQ)&J(mVJ{WvV9oUE~&8Cd$zLJILb?{Kav(%X|;V6~1*A_7GF?P4>%E=q@4696H zsCc3G`WHWEMPX-tPASf%t{+C(9=JN$;}Oh1(|$sNPuMceDuuFL5iVqNMt>J+Vb+T~ zzPQIhVztfmZoM(?jQ4wU-}bJlf6bu|E@+|`N!c+gou#8_3kTiohE`2~+Wz<%OnJqT zf?W3#Kjg@pX+aoJDqc^;KdJu_5|$o@L|hcE%M%D*i~K`>t#1Pj+qX|{Dygl5|5tl& z8P#Ug{R;vu#VHnwOK^7z6mM`3!L3lBxE8k-cMI-L2oRiNh2mPExKpI1I289u-&ymY zx$EBV^X1LQtgNi(IkL~*`zOcxY+{KhKoM>UrWbi|7TUO1+Q>sR;g7t(TT3QGUed&B zq?04&lAJkIoQVPBiEO_N*9tJ59s)6K-` zm(4c{obHy!yFhkYX98cZ9B6jf#pPExeBXh;ripbWPG)}HU4$#M9cc@(6Dpy?Vkw46{2wG4w0_niSD9JFKq4b;YmPd z&KdOXhYhL`9PDXq$2(_e{Ymspa~zn_9Wa9qrhPo}R$1^t z2!Q2}QpJeop^PY6dzyq{3QeWyqjypiZ@?=qMYmrhxHhuXX)wuj%9ruRQI&QkV_QP<12X1d+rad5YEnfUE1Xr;KkQ+(c8LmN z$|65lBK;^h;98Tn&jrv=m}KQ0@;+@QD2U2j6h|DHhsw28OYQ_V8^qK{!lG-TPQrRJ zU6?0ZhP)CiMCq2M-E|gLO>$TDIAA@VVNd9!7D$fHeMV^Y zWy|s9#eN|f*x3H+)_3dnwz4{IgPd(T=0a?EJiT$EPfY@Lw}d77+p3UYb{oLOt|&AX z4@PpGHq)+0+nW-9=K1qa+%V%D=8k&&<1~LoqJ>P~XYZJLLS4gEd5)y|Uv$FVf zqMkIULMH5!eT}yLYGfsI3D!)q^vfFv8vSAv_dL~VCt^CPbcZsHneekO>Y8P+$nwI1 zS2y@8oa`Z2arNmjAtO8RWk;=agHJ$dqjJkpsP^Evb%+4@uE7PFN! zo?6znB#Lu*Y`N3jxIX|FZOYi%OX$8I!ad6@f;oJg3l8lU+xOj)QyJl6XTf=BJqsbsbW&?h6L|DddD`iwukHD-53h z@i8AgdQS68)bnZ3k#>*01mOBJeCX1=e~sy@gMyydpS`2$hcDvwR=);0DUa+<Sf>J9}P;>B(q?RZU^Mo-N}{x!KER0cfXN(rZz=38EJHg5Bu? zPUBNNA}U5?Up}_tGsA-hEY!b_M8Mpj-SRl)(B7rA=)-UID45TmSqZIIF&i+$R|x7N zdT5U7A(%6TyPOWyoh^0T;==WD=_HNW9@sIdm02G%6dN=#`DQ+`V`xo2Pq7{ay!l2; ztNAm?T+LxRYTzH?9V>1>WFg}T@jm|*Th@^7U(=uAhb-8?6SglN>2eCkUmr^DTpM1{q)kvX47l=WYGqG&>RIR5tFRlrkY ziR11Bh2cno_d@68sm|<1_DFEw<=>vHza5IN*G`Skyt5Px)&*->2x4}a`cJ;ms?NlqFvn3dJ3`?>%E1|T1sezhLYhli?2g_7YVs?;zvFC z^U>;LB>G=P_xHYF%gnaQI)&vOPp7@vQICF~HF1;NNl9F01XMepZ6h&#LXOq-y$G{p zL#J^4Rs3@1XdU~b$&^57)Ivk-s=lk7Q0I#BT-*5MRMgG0T^|+DOhj%|(ZW|40{v%% z2+;M|mH zpbM$XCVBW-D4<1wwwBl+=C(vnX*3#3QT#ffq^v0h1@d|9$$L#u%v{PV{! z9rFs3Su3zM7M75P>1pCZsL7VUdiu4=Ed~^YM5X_EVz5z~fGICV4z89}xc#jVK1J{@iB!|MG%>Jx2)!6^>04)LsaK@sfnus3eQ zfDN7$P!Orgm$FL-UwB276@8xA1qPu~nb+$Mji2je`VuI7gf_dR!=Kz!aV&|ao~w;5 z=3>QvICZ7VXMF#zGH9_Q`lKW$CNJs1J?pyql&}7;IC&Ap{DX|#D{qTK7t7CaJrsky zDOM@JcUwm2YDCP<@d*@OiZZv47}ak%&mQ42Quosqy&=cwDq@1~Y33O6jIQ03W8XhH z$(~2FJUxgDARDFQxZ?tWr2l#H$wK(UpQPU>IgvQKTpZTm&1eqSmwDwOu=*x{)4n6( z%n<^qi{F9~q}jL*0y2=guW+ghr$Jgw{eS?tyPrv~=q59Di(LWgj$v^=u~qi|XrHeL%Q=6nj87 zkfi;d_SjY}mV`gE^URqUDP7pxt{Xk)QG=*KwosKbNH?(ptxQZnjD1YeMFh=%^ahuE z+n1&~D-xv-vnB^QZWdq`uN6aar{akOlBqTfIHr{#^!x<1{S?~vO{-5WXg!1GS!K$a zi-G;o88eLwxbfV$i;DfJ5CfYgH+GN^Vhg)s#6EPew|*i(dN(i|u=t7BIKx~OkbX_o zn#YI!u0udrV_5XEN>!9Qx~jgmr#s5K;XNPqvG@5QkV}kiZC%HvoSH2;B-Sfu@tS5k zKWwMVe~xHPvxFf10I8wM%I##eQB(cT+ce3RY(8l&DMYeE1CmZ=hT@O$M8N^DKI{(w zdAlM90cQB_mq+?;yn3mA;WK**4f|3WB3zo_-i0jXZpu6xVh6Lg6!NdAQX?^9-AVhL z;X4!o2eQa)#3SmW-8j@Em8Oq<4YmUSXGVefR?!Ffa^M!rI-B*$C&4WRELI^a^k`|! zZ0K5eb5S1i11H2`SRLUV`UrNdXi@ck55HT?IT7X?qRvwHy@$0_)uTMd;moPIEqgP>%N^c~| zlw(;GS=D6;E0AT`7I6{}un5j`;d-LsrCwnM^oMX=`4{7o1jB++ z=XJg+`avxQoCuUmII?v%A2)3POrn~5r9y|?@THR^UJ?Z>C(jcA? zmgK;p9UxVgPKs!1h9#^MeNNu-ZBMDP**9CK|MsBI-|zW!0<`zu?@Ghf1BUj+_CE_M z#@t}8$mp|GgL1JWh$q5;aH>jGP>peFKWT@lhjGCW3J1X6qhSn9GvC#rI55=?*D)&| zhzaPl)6X0b{FT8Qs4^}yXoWw?oZ}wW8$-1X`9R9BzC1(VIrKtQty?#3-?8}>6+E!M zhO5J!*-N;6acz82HwdY&ntRW-Z~yOoUjj&SyMjuT#vvXRITb??Po;$Akru(#U6U;_ z^+ACsApQ`;Z7et^*~z`AdcvPX=K#gE>{eeGj>Ke51;-(*8F5QJ13r6iu4W@HfMJ$- zQ_E54O?W_eBboi0=Bua@_HO~{vK+&`w^c)Sm8Lt{KKLIHVxsOzX{n0ODnSU{7!l&_ zpwB}bF#=Pcn9+U9|AsNbi@T-K5!#Um^tQ*jkt67&2@ZU;_*S-2ORo}e)+}F=hBU@r zD1np~C*+?~Y$hnjg-o(UpF+c(uG# z07tWyYSG${H2;p^VaAUtaFUIRKm*W51zm-`Rr@t8r@RyH+PBGqr-bgD<1;>D`uF7G zsCM7qhy)E*-euejejDo3hyb7B}@!guGGZ)*V+0{6F* zhb~9)swhWk`@YwI1K!nxE?BU4&(A^}!>Vp2L`l7q@NmzF|F``scXHt@wLp+(XhNA5 z=O8hVA9vMZH?k3*;R*u_IIVly91g}eo z{I4J%78vfvTTPLin?xQG_!VfcK1&wRe}A`})iavCS5yT_%rOoN8&(Iyl4X5@N#NNa z^(WIh?=LE)q2i^k`aJ`gjVTz73qDttA4Ir}luklK)bwO*t5ZUK*6ZOXi9lj;4s^2R zS!1iRD0Yufr?fxlHB8O&LFv0sZq;mACAzOwTum5zzA7qncve?7YA#IXIDO_dHTrxX zo>LLOb`&RDnY8j*u5W?gpy)Q{{o%$uf0%7*tES(Jx6N^m+Jq04ltVwk^Iy+2OuF#~rq)yk*$a1&Js}E2GZ+J$*Iysmw$O>ZzSpPVf=*48Lb(OH+D{gyL=DCsvzx%~b z07jeaVvy}p(a)Xg`A}vtUf^n*A1T`GQ1bE<;N69hZ6(Gz$`_oz~2CRV6hr$Y9*86nom|l*n?pFAb zNR&(dc!=|TM`4V>(o^ZeC=Y9iNmji!PVN;+&Gj`>I&3=V!G{)xBxyG#+F1Y5XNEJw zPhI-*Tq07E6p++?1psXlB)t`TBLrceeUgrG9QsAw14Pq9?1Qx(bg^YGumD5OOqxKBI zZhFjUZ<9u%i`uFRRez&^+g{CwMx@Qkc~&W@C!f~UDFvFr_X0)LXy$_LJL+((pK6QQ zl!xN!rbuDV(^V+?DQnFn=;IjrZis#|r6<8Mc; zpf?hwUH+2&&0Kpsl)x3KH&diecB9QYg~jr(n)6=|b6*fP|InKS)oCjPNv_#DXs=6Z z$n%P7L=Bmx){8+)3+oy0rLD2e)*-Z_bcFI8dYO1gCE~-VG6o=~$xKO{qL;(yw?bhJ z=?FPT93e^+iaI;zLIk+&sRUPb%co`Q)6G}MxX+68I96gQp&R6K9-mOUs<#4)BFX*_ z%M!4ean=-aRd9gw^!u~V#-rS@Oh;zNLqFDZ?By1)x{63rG;X?$GWnNwDam3h<7XP zVsJo2cdQWV3h2MG26#B_d}_=tQoZ80CTfyBf(7lce}IEdv#^>LpdTp ztedPDH+q)c((Vs8E?Mga7ar+jSGA*aPGAR1U%27eD%&%ul`Fq9qUV)|nx%ICPkT<) z?pNuvjm|K@M!pwka(z~gaw@JY?NHjGw&P2hju~R^$duL5;4aKv>ZrF$Wl>_?QFM9%X4x>KBhmTFGNOauW}!;Od49_ zWTOnXJHLTMvcFGubfubOlvF^^*5H*k$U7q9s9g7R3B2B~#sU6tvh1dNwjB3x1m3_{!N=Q#}_1;(u55 z5}sKS&fO=mW!hXxHsOf$OYmDoh9r;k$$?JC-uvhiua^@zZ**ry%DB8D=~w5At7H^X z7kAZsFpuXiFTZkT@NiCBF47%;P_7gosj<0p>u?8eznJ^4+P5gexuzIcD!< zo3O3>{=N;2!?gNJW&bFD>ef}vbu9S#=(MMc6M&0;pp&E%`LrPGJH=93b*Pz99#K+N z$CLx1<9wRtPWf(a%ql#PudBbUpUOpo0en@k8q65tTg<5!1`l5m`?SYScqUr}Iu>8Kb=qlC z76;9osx&?pg+MR=h}775eYW3(Bfc*Sz)BKcn?}C5`?RM)@{GS=bxQx3KqkN9RKzs* zer@}o>YK_b^C=Y>8uk`s+LSY$J0Cv%^-^InKw!nq#1R7^5yFJ{;$MQefR}~Mu9-gv z+&eP*hqq+4s)v$r8zOk0SqXWU+0XuvWL=O%p9z!5q9<~}*lgPg41k+Ii(QUz_Q6{H(zYnXR8S;mrOn z$i$@;MzU#cX2z!BC+bi~rcZwsEz}cG+|%hu!PjJ?cNY}U!gU}n;66X5m$I_KvB%Og zk)$lF*S&~`o)XrFtQh$bi^`cxbFj{?m+tgz=P$Vs(VhbG!h!=5;`GCcB(lb9&z8bO zEB8^sm{GaIveZgxwZ>P`uYN%WEpYA@IjWy&rG%)j&My$|CPgXN3|-*rIWre#2<-4{ z)ypA{9Sa@2i^v6$(Q{AEAd*Kaut%p3rP)a6uW}N3Grfe+p z?4-Y63*Q?ob}#P7yQSqj>Wu02|LK^SB1SZMs{G=dCN-yvp+s}~MQU1(KEXq|`Vx1ux^UgVYadY)x z0qVoiUk?-4t!qLA73t2qz6;b?}BoIE%b@)xR`TwhPgx z4tX{THeTZX@em91KqB&1;nhc8AXU?J*?vzJ;!RZmCXwZ?ocWoW&+U8dEX!==^*T%+ zjl7!-E@Q%QOJWKQyu4Te0O(K(e+An)#{tK2wRDL{oy<}j1?Shx4;8cg_696gXXuOC zvamBYB@iHa38sj~kqwY|`}?I)`doyLT56Jo%Ac}2EI_RqlklNaMT{RvcVgmGz(4w= zP^#-)R=v{01XeQ=MQ%~>KYP4AfjOk;MV~OmamB9!!Pf}ScgTb>uXXK}6Z^YF!Kxr=mY`W07hjyp1#S`3O|H}D*uS{+AX^8I+#?1k!j@d9v>3%H zj2I)%cPksQCM>6}g!lHUgwqlc5r6HZ2NSjqbn2QeeVNH8&isf3oTpfV5*cRw_}N5) z8l+ZIG6y~a_(g<V8Nxmy}S ztpMYgenv_wQW=6C98Nk9377cdXi;(EusgEf*rS%gr_l#?`I8bM)SpG@(L9QsEhtTn z>>Ys>3QiZ{Z}}ObH!&;M{ufj}3#KlZC_FlM^2V)gQLIQfRws1ej@r_?JaQ0~tcE4w(&@;kg^F!~aI5=jR?;Tb;Zelo@2|c#=+$%~o^tmZx#e9= zF!em-Mt-n>*^sn1*>S}jUK4aGm+fwjJFJZm6N@S;rd+5c{=xy_aWa7%(v1#~voOaA z#x+e!=}J8OkVV!3vttA;RHB^ZN8{#`5PowKF2Xj1jO)WkwA-LoUTfDoHR_-yRoWI|ciU~PJ1uUj^+$v% z%B2apQ&9UF<&??G=T=Pehvh7DohoI%qR zs$}mvGP;_7`>^-%Us_Kb*Hntlcb*7Irp{W(Cqb3*P-IU`Fj}G+XvSF{cn?-_vCcwV z7cOKG}3OZPU|lD5aRF^fiY#Z1o>gnm#bwMDy6 z71~0k=2Y;4+|<;(U0gj{%#7BaTWU1?MCs@SCe=ijP!T3|d~6Rgc77tWyfrVZT`wbn zuP;xXwC>8?-*`_Dr0Qmx!zX>Ie#;07qQy8&0dWL?_Qlx+wB2S!B4)gZae|YMR$nGKK!WNM{ z)_}~M4++9l`p%O{<1uPHBc$LD9~!?=Cw_OMf5quaU8`YQaJQtw|Jlb-cy?iZP`LNh zYd}(4!_x}0SK%){@oXKY)s8cg#B(kuV{d_MY>EB&gzTwwK}LqQ+6sf-=JlwQjLEpU zjY<5#lW;>MD@O*r!UuV`nN5az=`81Lg$ZZ`0wS&nTQ(ObFH|EXIL%lLI7ecu#zc}Z6{k(s`RZkR?OR2h#~{c z7;Cvv36&#s1l=~cgo)65Z_pw{5x>2Z=0W!|igha&n9{o0Wx)+*Q7FYI0Y9wHm7RPV zAN3O*j2;g&^f1yt5MSml&*ILsv?-isa(3U~eGbjF?9Oi28uws5+?ShN1T1hjU|2-_ zt}GeQQ3S&Mon@+d^`q_dWUWZ0AQn+yt*Mia{&Uy(zE5AVjkzV? zRqcNFV>*@mo9);Nd!6}8mCJv4xoJL0iTr=|1|B1`{?Gt15WWsoI2KbA7oj@|MD%oE z55H@Vt?nfTR^}^3d7HiVZ5AsxR8-b193-}<=q;|KC|-!O2lc`1!K<6@UF;KnH1kF; zfM=oC55^P7)?sfHrfds+vZo9&?QfzxIejCLsUL`l#fXkcjp6x)Xho#=)q5VKO2aUC zAdRPaAYVxam_3K5Hex$OggX7j=I_&>LKXutio;{_ZAcv&R?FNE+Bj$~P)^~O{xsaZ zj9#92qfxrVKcsnM5swZPwJ-NpRbDA>A}Jh}mX3!^Wi(^j4Sl+K5{G4UU__WXuk}#P zhW7AGcp^ZMbpXBt4;1bRZ2xII7$e0nBGWL?t7n;hr^Y3$1%-}TY_8X5ELZSJV^$PX zWZUi$ayfbAdn?Bbc0tDG7>#DHjT^i4k}m5oB$#$eSNW;MiUw^7e;F$~-QSh$ytt5x z{%ghx9GbTr9X}#q6e~0;>a{5UE_7uNfK|002!D)m3&Ip3|vlyn9fv#AN8Ucix_h4fMEJ0T@< z_#E(O%>@oVrb*Er+S||lCOzdCZT5BR(Fj{lslEaol#j#9mZB&1O$hh4UDd^>t-#&$ zib{-sQE%pVYZ!YJ1L$zP1@hC2G3lW$N1~bKi!b=R&6dEli|mNmea7SC>kbiJ^L1SO zosv#sbCl^=%ATTjjgqDyO+qSV$RH2@$ zxg`R~%!i`na2UUJoz7Q{`Qe!C?4A(ix48JWV_cf)r}bTBo*~cT<@?UJon79=h}%|K zK#iEF=t^)rsyV08oH92hJyW+y{wNDKTT!s$j`)E zXfR(2Yv>E^pB9jUvvtY^mF-q^NtZwZ-3Tg=MW%LS=gazwa~VY7Ut_ZbwJ+_+U+osg zzn)b5+>-7xQSFv1|NS~Tpri6td(CBd?yJ;g6DUt;mE3O>5C&OIN*X@fl5`L(>FT42 zW0N?%l0>t^kiC%VJxMK9GLbFNSG_paB_0PD%mNDa%sYS-T#Unp8?7wBS< zlj?*u$aK#lncDLHO$(jKNu(zOXiiiAYPYSm6n%|4F2)#rO0uSqb7_y|TJm`+m$xeY z6sN9;a431|9rfX-`%3&KD9DU*diox>g~mm&FYoTt09vTT zZk~+t{BQZiLOV`gnr552W;JWpn8pmpxd4ZbKRBm8EWVG$V}w732pqD=<|b|A)rB2TM5lY zENd3gObHBkBAm)g|CCG%QaT0Eg$zB`aALnf$I9lZ&(Sd7jN8=RB%t?u11UNKR^6l^ z=?5cZp_LHcpgb-bTdkBsE&lkJ%)%Y2%$X`|gn^2EiCaTFjS9-WA{< zPk-y%!7=Ps32Zt#(RRfysM#grN5veddk$9M^{>EiKRJk(ik20n*t#rp89FdNA-dpU zHlTNHJ+Yta`{NYb9~PIN_Y0?<%36oVgCx?{XvIK*6a^916vI>+ihS6YcO9~fN!2Ej z`H`?`DTk)FFX|E)l~MQH5y>yLC6lo5R%^OOD*|1R zO-CweaSGY`^<=_V0ymzCc6J$g|5Gj`*fPi4ATx|CPddb;Wv3SVut0s+Tc%*uG96pD z+-HF!m%>n4zwgT4KSoXr2*;yQZk&$b zBvn~%H0^z|fFa`St!nN37MVD<4&6703faCu7HH(xrN3VpO8tP)SizlDZlP0J%gQz% z#wls~0W46Sg7Lz*#$V%vt~a-t47{W+Hg6PtO{|7|>E$wt{3GrBC7Zt~8Qd1ks72-H>%d^wMcb*DXk2lr`hY`)>A8M!cJ9G4JQ&iYl%4Nk?ucT9mhQ0oV zQTl49eiW=>Cbe2IDZTo)IlVPFrLBV00sXW-(5>&zFVpmF?JEV4aBwoHF@2%BhC}iI z!;Yu91k{khEhjW^N*{*lXNUvHiQ&!e9>~@&GhijCMSY0KM7=Cvq`8&`0sJOqQxEw+ zd&?iPFwV%YO8s@UW`|HG$3NS(GLJbU(GtN|QA4`3{;W9QWXzq;e?^MqHTP_0W)^26 z8zaBpkZI{+UC9cfSU@C!80B2P<5Sxq0kq8lvw_Ww-l)oERgS*> zioEDMp@^>8;%1Hm91c=k=;_lYx7z;Mgs9z-%?=)E(1AO=)}A~slZ6=h-4!j=B+ek) z)@kl)KKfOT?}k<^4>-^InI8kGI&xY{OAz;pM%jK=A@j`Ob*`qm$*@|jI5+EV-BY7z z^QiYP2D*k#~G2j)KkUJqTk8RCp%;8|~UM)Q-nN++1U^|%Bp$UWSp(-Wtd)adSwDjQd zBrDj+pyE4lodI$#D}bSVLG;-)!r!ssbuZ*K5EmG z=uMrZgDHgA3S<{rJ^s#oGc0s93C&FvdgL0`K+iB|r6&2Mt%O8bh=MXiV0}+547Qep zA?2#^itYDq@{5W|aBB0YL-2u?c9X$aIkgA@p^y2~FZEKV!xTLUC5&*1DxAWS_-dRH zcB7Tb(kiFqw?Bjty+$4=C@)=s8r^W1uju1gXi+SCWScsS14xmR;v3txR&%xJ zoo`#DEQkUWNaVuJR#QdQ6)I7=dqcWhasRzZ))*XoOWUJtZTh9flDeLZ zfhp^BI+GisC;o=MVlgzUH#}2rS(TKOFZ7$N+JO9B#=wwhDRyV+en=QnsF-;W*OM~} zYT^~KK4N`hoLHfCL~1s8oa~O-Iws6TfjNl}$TtECmoP{ z!CzhqP7y)Nes3#J_{|fY?&5Lz`h4%Rg5L#c+%#j!=R)Ffj?HMC6C6q-^F<1Vg<`@~ zwc>Q>9gMVU&O@8DYJCoPv0i~%s7Q);H77G{Y8ygB>r>U()i+CM+7@|=;O{ni60piO znV8`k!@}tf3a!}m0ppR3&?Fp6oo0Z-D~HSGeUVT(!wN1f@#ZZ8u`q}}J!7KK$?icr zrDBYay6Ng2j~DB*<_Dyf4uR!!`{kXzC3i4dmkIs2qFGdys{X ze@%QJzARJpnsG>2XSpfA-jy))NzV4?CY&?%;ZNbhAPf#RWp$~>uO20r9%-En&CBXREUfWVq zRi!DL^mK>6yWl8A2t~~R;I*p(T1Ktqzxv0#+sc$~!qEnai&GO#{zQAFp4ztIn1>8e zfR=}{8g-Y~7hX#hd^&e{7LyO?GY;*?*+Rppduv#g(>6;v9vMj(kIfnRoxB_^E^6-q zh%1%nsS$n;rR^h5#Gis{@i=;sdbd5_{0OA>* zlBmk=BNy2BCO+Az@!Sqor>NyC6QS#~>`eCF#osAPYVXaH6!#3{!aRV-ltQGW37qe0 zeRw`@v@ufev2o3*@B5032J236a%!!SQ^wM#FGeAg<%5GSeTGsDo9BI5D|?#$1SwGs zS$|q;m7HHgMC#Zj`(veusj|;~m*vDyT!?i49ZDCw-9j3H% zB}0sv5QNghVr8O?gPGU68L1FrCGaZqZ4MU9)NUAPHTc<|Yn~B=-TNYG!BA}s58FRM zfYc5Plb62X*I*8b72)PL-|v>64V9(cBQca?&BR+xAZ%FnEiPKxyHD+pD2FeXoA zA|AWQBjn4?p#3_KO?p(0nO;}PycNbVuKb~N>L8t(b?^HE+KUT{pc2o}lH|*j^L(6b z8!%6-W-%5x{(*+6$1Zd-F0a=EZsWx;z}lk(bxH?rLw(P{?R$lMeSNAeh2wmCM+?5+ z`_@ApdQ73!&b5{*IV7-HbVaH%hEpM;xHxmnA~>J$LK-@F-~~D`+;=<#Qk?PnXLq3b zHL;Vu_=IERBsM9=dBxtRUJV4=Fnx5N43}i>`y9I;L^AIKHQ6mNW3ugMo_fnrRD@n^ zrqm({KKep0G!44LBOc}ERUGEYHwx{;?p!}s|B2%<=3s~x6CN6DY6?&_dYCu#>#4q6 zQ`DlRNClb09ka%cqaNC1;eYMoun^V#hTmrQO|@$LGf{nafAk8p6GA)XD$$M*hl z=chm#?;hFQ7gOcW_K_gJF zH>XU9N}W&C>j6~Hhc@~IrgjR5?~YA_`p7~P3AbsjszDfq-+Q5lC#2LB5>d{P@P6Uj z+@sH|Dd0w(^5ee`_!n4RO3N%a7Q@?$62(#46RpZ1nsc)+2Pkhq6fE^#z2Q(?<$rc3jj4s0jWs@fdV!o zkv2K-6@dlEh(ELHw=TUgs}B2!Egf4r{HtBwn<#wY?x3G#(}95pSeALQ|e z;r-{a!1MLKq3xUqN|yUHhmauxxUY%=cFqaiK`O;guR>gB73{SV-Y4H8J_=v2Z^RNd^t9DXKk0Ohuf50jEdnu6_?5HS&g9bby~LBg%R)(T}RTNY&1nDt|i5!2=d z!v&FT2N{%=sdUmfWKH?#oanfle)9cfaaPjS&Be#$y&|~l0-DQje*ckRXvh`q1ql2D z69U8-c+&~q#eYtWw3mZAtTZnSN3%s9_vu#sxlo0(nr8(u2%yYxHeZw^$dTI~?LCvs zwwL3TyUcmVF#fkSnaug3XI}pI!2}=Li`qyXDcP5JHauiNzZyV;;6v|1!Rm?g5*{Jt z#^#TSh{1uAq#{1PDbumHtl}~Y#ixZ*@@w&u*^W;?=3q6mKh8Y_@x7~Gegi$p-v(XV zkq1;yI#nM(`hWd{H-Ikkomsouyfib6!;!}H+rY!ndTgrH7C}&tKF_${Kt3Vsksi|I zfAz`fE7HgPpAr4PyD9i%kPhwE?E?UT|M@Cb+V!7-`pU!c9Ob{C{NER<&^7$4pPKEB z`TpmtZT*Ach7&r#=rRb|8u { +describe("CF3 and Extensions emulator", () => { let test: TriggerEndToEndTest; before(async function (this) { this.timeout(TEST_SETUP_TIMEOUT); + setUpExtensionsCache(); expect(FIREBASE_PROJECT).to.exist.and.not.be.empty; - // TODO(joehan): Delete the --open-sesame call when extdev flag is removed. - const p = subprocess.spawnSync("firebase", ["--open-sesame", "extdev"], { cwd: __dirname }); - console.log("open-sesame output:", p.stdout.toString()); - - test = new TriggerEndToEndTest(FIREBASE_PROJECT, EXTENSION_ROOT, readConfig()); - await test.startExtEmulators([ - "--test-params", - "test-params.env", - "--test-config", - TEST_CONFIG_FILE, - ]); + const config = readConfig(); + const port = config.emulators!.storage.port; + process.env.STORAGE_EMULATOR_HOST = `http://localhost:${port}`; + + test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, config); + await test.startEmulators(); + + admin.initializeApp({ + projectId: FIREBASE_PROJECT, + credential: admin.credential.applicationDefault(), + storageBucket: `${FIREBASE_PROJECT}.appspot.com`, + }); }); after(async function (this) { this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + cleanUpExtensionsCache(); await test.stopEmulators(); }); - it("should execute an HTTP function", async function (this) { - this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + it("should call a CF3 HTTPS function to write to the default Storage bucket, then trigger the resize images extension", async function (this) { + this.timeout(EMULATOR_TEST_TIMEOUT); + + const response = await test.writeToDefaultStorage(); + expect(response.status).to.equal(200); + + /* + * We delay here so that the functions have time to write and trigger - + * this is happening in real time in a different process, so we have to wait like this. + */ + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); - const res = await test.invokeHttpFunction(TEST_FUNCTION_NAME, FIREBASE_PROJECT_ZONE); + const fileResized = await admin.storage().bucket().file(STORAGE_RESIZED_FILE_NAME).exists(); - expect(res.status).to.equal(200); - await expect(res.text()).to.eventually.equal("Hello World from greet-the-world"); + expect(fileResized[0]).to.be.true; }); }); From cbe824e01aa682b5b792100dec1b1b481477870c Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Thu, 14 Apr 2022 12:47:39 -0700 Subject: [PATCH 0243/1699] Adds blocking function discovery - Emulator (#4442) * adding blocking trigger discovery * fixing comments --- src/emulator/functionsEmulator.ts | 99 ++++++++++++++++++++++++- src/emulator/functionsEmulatorShared.ts | 26 ++++++- 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index be91a127126..286dcd67ba0 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -43,6 +43,8 @@ import { emulatedFunctionsByRegion, getSecretLocalPath, toBackendInfo, + prepareEndpoints, + BlockingTrigger, } from "./functionsEmulatorShared"; import { EmulatorRegistry } from "./registry"; import { EmulatorLogger, Verbosity } from "./emulatorLogger"; @@ -64,6 +66,8 @@ import { accessSecretVersion } from "../gcp/secretManager"; import * as runtimes from "../deploy/functions/runtimes"; import * as backend from "../deploy/functions/backend"; import * as functionsEnv from "../functions/env"; +import { AUTH_BLOCKING_EVENTS, BEFORE_CREATE_EVENT } from "../functions/events/v1"; +import { BlockingFunctionsConfig } from "../gcp/identityPlatform"; const EVENT_INVOKE = "functions:invoke"; @@ -187,6 +191,8 @@ export class FunctionsEmulator implements EmulatorInstance { private adminSdkConfig: AdminSdkConfig; + private blockingFunctionsConfig: BlockingFunctionsConfig; + constructor(private args: FunctionsEmulatorArgs) { // TODO: Would prefer not to have static state but here we are! EmulatorLogger.verbosity = this.args.quiet ? Verbosity.QUIET : Verbosity.DEBUG; @@ -204,6 +210,16 @@ export class FunctionsEmulator implements EmulatorInstance { : FunctionsExecutionMode.AUTO; this.workerPool = new RuntimeWorkerPool(mode); this.workQueue = new WorkQueue(mode); + this.blockingFunctionsConfig = { + triggers: { + beforeCreate: { + functionUri: "", + }, + beforeSignIn: { + functionUri: "", + }, + }, + }; } private async getCredentialsEnvironment(): Promise> { @@ -446,6 +462,7 @@ export class FunctionsEmulator implements EmulatorInstance { loadTriggerPromises.push(this.loadTriggers(backend, /* force= */ true)); } await Promise.all(loadTriggerPromises); + await this.performPostLoadOperations(); return; } @@ -516,6 +533,7 @@ export class FunctionsEmulator implements EmulatorInstance { } ); const endpoints = backend.allEndpoints(discoveredBackend); + prepareEndpoints(endpoints); triggerDefinitions = emulatedFunctionsFromEndpoints(endpoints); } // When force is true we set up all triggers, otherwise we only set up @@ -615,6 +633,16 @@ export class FunctionsEmulator implements EmulatorInstance { this.logger.log("DEBUG", `Unsupported trigger: ${JSON.stringify(definition)}`); break; } + } else if (definition.blockingTrigger) { + const { host, port } = this.getInfo(); + url = FunctionsEmulator.getHttpFunctionUrl( + host, + port, + this.args.projectId, + definition.name, + definition.region + ); + added = this.addBlockingTrigger(url, definition.blockingTrigger); } else { this.logger.log( "WARN", @@ -647,6 +675,41 @@ export class FunctionsEmulator implements EmulatorInstance { } } + async performPostLoadOperations(): Promise { + if ( + this.blockingFunctionsConfig.triggers?.beforeCreate?.functionUri === "" && + this.blockingFunctionsConfig.triggers?.beforeSignIn?.functionUri === "" + ) { + return; + } + + const authEmu = EmulatorRegistry.get(Emulators.AUTH); + if (!authEmu) { + return; + } + + const path = `/identitytoolkit.googleapis.com/v2/projects/${this.getProjectId()}/config?updateMask=blockingFunctions`; + + try { + await api.request("PATCH", path, { + origin: `http://${EmulatorRegistry.getInfoHostString(authEmu.getInfo())}`, + headers: { + Authorization: "Bearer owner", + }, + data: { + blockingFunctions: this.blockingFunctionsConfig, + }, + json: true, + }); + } catch (err) { + this.logger.log( + "WARN", + "Error updating blocking functions config to the auth emulator: " + err + ); + throw err; + } + } + addRealtimeDatabaseTrigger( projectId: string, key: string, @@ -795,6 +858,38 @@ export class FunctionsEmulator implements EmulatorInstance { return true; } + addBlockingTrigger(url: string, blockingTrigger: BlockingTrigger): boolean { + logger.debug(`addBlockingTrigger`, JSON.stringify({ blockingTrigger })); + + const eventType = blockingTrigger.eventType; + if (AUTH_BLOCKING_EVENTS.includes(eventType as any)) { + if (blockingTrigger.eventType === BEFORE_CREATE_EVENT) { + this.blockingFunctionsConfig.triggers = { + ...this.blockingFunctionsConfig.triggers, + beforeCreate: { + functionUri: url, + }, + }; + } else { + this.blockingFunctionsConfig.triggers = { + ...this.blockingFunctionsConfig.triggers, + beforeSignIn: { + functionUri: url, + }, + }; + } + this.blockingFunctionsConfig.forwardInboundCredentials = { + accessToken: blockingTrigger.options!.accessToken as boolean, + idToken: blockingTrigger.options!.idToken as boolean, + refreshToken: blockingTrigger.options!.refreshToken as boolean, + }; + } else { + return false; + } + + return true; + } + getProjectId(): string { return this.args.projectId; } @@ -1261,7 +1356,9 @@ export class FunctionsEmulator implements EmulatorInstance { for (const backend of this.args.emulatableBackends) { loadTriggerPromises.push(this.loadTriggers(backend)); } - return Promise.all(loadTriggerPromises); + await Promise.all(loadTriggerPromises); + await this.performPostLoadOperations(); + return; } private async handleBackgroundTrigger(projectId: string, triggerKey: string, proto: any) { diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 6c7009fa6dd..a386e60a9a9 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -9,11 +9,13 @@ import * as backend from "../deploy/functions/backend"; import { Constants } from "./constants"; import { BackendInfo, EmulatableBackend, InvokeRuntimeOpts } from "./functionsEmulator"; import { copyIfPresent } from "../gcp/proto"; -import { logger } from "../logger"; import { ENV_DIRECTORY } from "../extensions/manifest"; import { substituteParams } from "../extensions/extensionsHelper"; import { ExtensionSpec, ExtensionVersion } from "../extensions/extensionsApi"; import { replaceConsoleLinks } from "./extensions/postinstall"; +import { AUTH_BLOCKING_EVENTS } from "../functions/events/v1"; +import { serviceForEndpoint } from "../deploy/functions/services"; +import { inferBlockingDetails } from "../deploy/functions/prepare"; export type SignatureType = "http" | "event" | "cloudevent"; @@ -27,6 +29,7 @@ export interface ParsedTriggerDefinition { httpsTrigger?: any; eventTrigger?: EventTrigger; schedule?: EventSchedule; + blockingTrigger?: BlockingTrigger; labels?: { [key: string]: any }; } @@ -36,6 +39,11 @@ export interface EmulatedTriggerDefinition extends ParsedTriggerDefinition { secretEnvironmentVariables?: backend.SecretEnvVar[]; // Secret env vars needs to be specially loaded in the Emulator. } +export interface BlockingTrigger { + eventType: string; + options?: Record; +} + export interface EventSchedule { schedule: string; timeZone?: string; @@ -122,6 +130,14 @@ export class EmulatedTrigger { } } +export function prepareEndpoints(endpoints: backend.Endpoint[]) { + const bkend = backend.of(...endpoints); + for (const ep of endpoints) { + serviceForEndpoint(ep).validateTrigger(ep as any, bkend); + } + inferBlockingDetails(bkend); +} + /** * Creates a unique trigger definition from Endpoints. * @param Endpoints A list of all CloudFunctions in the deployment. @@ -185,6 +201,11 @@ export function emulatedFunctionsFromEndpoints( // TODO: This is an awkward transformation. Emulator does not understand scheduled triggers - maybe it should? def.eventTrigger = { eventType: "pubsub", resource: "" }; def.schedule = endpoint.scheduleTrigger as EventSchedule; + } else if (backend.isBlockingTriggered(endpoint)) { + def.blockingTrigger = { + eventType: endpoint.blockingTrigger.eventType, + options: endpoint.blockingTrigger.options || {}, + }; } else { // All other trigger types are not supported by the emulator // We leave both eventTrigger and httpTrigger attributes empty @@ -270,6 +291,9 @@ export function getFunctionService(def: ParsedTriggerDefinition): string { if (def.eventTrigger) { return def.eventTrigger.service ?? getServiceFromEventType(def.eventTrigger.eventType); } + if (def.blockingTrigger) { + return def.blockingTrigger.eventType; + } return "unknown"; } From 747ec2a3941edd97a278b47c6fa6b26c933244aa Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Thu, 14 Apr 2022 15:00:50 -0700 Subject: [PATCH 0244/1699] Fix comments from Auth Blocking Triggers PR (#4443) * addressing pr comments * cleaning up * fixing var case --- src/deploy/functions/backend.ts | 2 +- .../functions/runtimes/node/parseTriggers.ts | 2 +- src/deploy/functions/services/auth.ts | 25 +++++++------ src/deploy/functions/services/index.ts | 35 +++++++++---------- src/deploy/functions/validate.ts | 2 +- .../deploy/functions/services/auth.spec.ts | 10 ++---- src/test/deploy/functions/validate.spec.ts | 10 +++--- 7 files changed, 40 insertions(+), 46 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 374304678be..e1afae22a26 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -133,7 +133,7 @@ export interface TaskQueueTriggered { export interface BlockingTrigger { eventType: string; - options?: Record; + options?: Record; } export interface BlockingTriggered { diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index c887ee4f2d9..c8562da4834 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -70,7 +70,7 @@ export interface TriggerAnnotation { }; blockingTrigger?: { eventType: string; - options?: Record; + options?: Record; }; failurePolicy?: {}; schedule?: ScheduleAnnotation; diff --git a/src/deploy/functions/services/auth.ts b/src/deploy/functions/services/auth.ts index 9940e6224b1..6b2dec73cac 100644 --- a/src/deploy/functions/services/auth.ts +++ b/src/deploy/functions/services/auth.ts @@ -9,13 +9,11 @@ export class AuthBlockingService implements Service { name: Name; api: string; triggerQueue: Promise; - unregisterQueue: Promise; constructor() { this.name = "authblocking"; this.api = "identitytoolkit.googleapis.com"; this.triggerQueue = Promise.resolve(); - this.unregisterQueue = Promise.resolve(); this.ensureTriggerRegion = noop; } @@ -26,10 +24,10 @@ export class AuthBlockingService implements Service { * @param endpoint the Auth Blocking endpoint * @param wantBackend the backend we are deploying */ - validateTrigger( - endpoint: backend.Endpoint & backend.BlockingTriggered, - wantBackend: backend.Backend - ): void { + validateTrigger(endpoint: backend.Endpoint, wantBackend: backend.Backend): void { + if (!backend.isBlockingTriggered(endpoint)) { + return; // this should never happen + } const blockingEndpoints = backend .allEndpoints(wantBackend) .filter((ep) => backend.isBlockingTriggered(ep)) as (backend.Endpoint & @@ -94,9 +92,8 @@ export class AuthBlockingService implements Service { } newBlockingConfig.forwardInboundCredentials = { - idToken: endpoint.blockingTrigger.options?.idToken || false, - accessToken: endpoint.blockingTrigger.options?.accessToken || false, - refreshToken: endpoint.blockingTrigger.options?.refreshToken || false, + ...oldBlockingConfig.forwardInboundCredentials, + ...endpoint.blockingTrigger.options, }; if (!this.configChanged(newBlockingConfig, oldBlockingConfig)) { @@ -110,7 +107,10 @@ export class AuthBlockingService implements Service { * Registers the auth blocking trigger to identity platform. * @param ep the blocking endpoint */ - registerTrigger(ep: backend.Endpoint & backend.BlockingTriggered): Promise { + registerTrigger(ep: backend.Endpoint): Promise { + if (!backend.isBlockingTriggered(ep)) { + return Promise.resolve(); // this should never happen + } this.triggerQueue = this.triggerQueue.then(() => this.registerTriggerLocked(ep)); return this.triggerQueue; } @@ -143,7 +143,10 @@ export class AuthBlockingService implements Service { * Un-registers the auth blocking trigger from identity platform. If the endpoint uri is not on the resource, we do nothing. * @param ep the blocking endpoint */ - unregisterTrigger(ep: backend.Endpoint & backend.BlockingTriggered): Promise { + unregisterTrigger(ep: backend.Endpoint): Promise { + if (!backend.isBlockingTriggered(ep)) { + return Promise.resolve(); // this should never happen + } this.triggerQueue = this.triggerQueue.then(() => this.unregisterTriggerLocked(ep)); return this.triggerQueue; } diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index f832e976d91..19982b337ad 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -25,16 +25,13 @@ export interface Service { policy: iam.Policy ) => Promise>; ensureTriggerRegion: (ep: backend.Endpoint & backend.EventTriggered) => Promise; - validateTrigger: ( - ep: backend.Endpoint & backend.BlockingTriggered, - want: backend.Backend - ) => void; - registerTrigger: (ep: backend.Endpoint & backend.BlockingTriggered) => Promise; - unregisterTrigger: (ep: backend.Endpoint & backend.BlockingTriggered) => Promise; + validateTrigger: (ep: backend.Endpoint, want: backend.Backend) => void; + registerTrigger: (ep: backend.Endpoint) => Promise; + unregisterTrigger: (ep: backend.Endpoint) => Promise; } /** A noop service object, useful for v1 events */ -const NoOpService: Service = { +const noOpService: Service = { name: "noop", api: "", ensureTriggerRegion: noop, @@ -44,7 +41,7 @@ const NoOpService: Service = { }; /** A pubsub service object */ -const PubSubService: Service = { +const pubSubService: Service = { name: "pubsub", api: "pubsub.googleapis.com", requiredProjectBindings: noopProjectBindings, @@ -55,7 +52,7 @@ const PubSubService: Service = { }; /** A storage service object */ -const StorageService: Service = { +const storageService: Service = { name: "storage", api: "storage.googleapis.com", requiredProjectBindings: obtainStorageBindings, @@ -66,7 +63,7 @@ const StorageService: Service = { }; /** A firebase alerts service object */ -const FirebaseAlertsService: Service = { +const firebaseAlertsService: Service = { name: "firebasealerts", api: "firebasealerts.googleapis.com", requiredProjectBindings: noopProjectBindings, @@ -81,12 +78,12 @@ const authBlockingService = new AuthBlockingService(); /** Mapping from event type string to service object */ const EVENT_SERVICE_MAPPING: Record = { - "google.cloud.pubsub.topic.v1.messagePublished": PubSubService, - "google.cloud.storage.object.v1.finalized": StorageService, - "google.cloud.storage.object.v1.archived": StorageService, - "google.cloud.storage.object.v1.deleted": StorageService, - "google.cloud.storage.object.v1.metadataUpdated": StorageService, - "google.firebase.firebasealerts.alerts.v1.published": FirebaseAlertsService, + "google.cloud.pubsub.topic.v1.messagePublished": pubSubService, + "google.cloud.storage.object.v1.finalized": storageService, + "google.cloud.storage.object.v1.archived": storageService, + "google.cloud.storage.object.v1.deleted": storageService, + "google.cloud.storage.object.v1.metadataUpdated": storageService, + "google.firebase.firebasealerts.alerts.v1.published": firebaseAlertsService, "providers/cloud.auth/eventTypes/user.beforeCreate": authBlockingService, "providers/cloud.auth/eventTypes/user.beforeSignIn": authBlockingService, }; @@ -98,12 +95,12 @@ const EVENT_SERVICE_MAPPING: Record = { */ export function serviceForEndpoint(endpoint: backend.Endpoint): Service { if (backend.isEventTriggered(endpoint)) { - return EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType as events.Event] || NoOpService; + return EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType as events.Event] || noOpService; } if (backend.isBlockingTriggered(endpoint)) { - return EVENT_SERVICE_MAPPING[endpoint.blockingTrigger.eventType as events.Event] || NoOpService; + return EVENT_SERVICE_MAPPING[endpoint.blockingTrigger.eventType as events.Event] || noOpService; } - return NoOpService; + return noOpService; } diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index ebae6e77901..d7481a62bfe 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -15,7 +15,7 @@ export function endpointsAreValid(wantBackend: backend.Backend): void { const endpoints = backend.allEndpoints(wantBackend); functionIdsAreValid(endpoints); for (const ep of endpoints) { - serviceForEndpoint(ep).validateTrigger(ep as any, wantBackend); + serviceForEndpoint(ep).validateTrigger(ep, wantBackend); } // Our SDK doesn't let people articulate this, but it's theoretically possible in the manifest syntax. diff --git a/src/test/deploy/functions/services/auth.spec.ts b/src/test/deploy/functions/services/auth.spec.ts index e94d075e342..6f6ed65de00 100644 --- a/src/test/deploy/functions/services/auth.spec.ts +++ b/src/test/deploy/functions/services/auth.spec.ts @@ -22,10 +22,10 @@ describe("authBlocking", () => { beforeEach(() => { getConfig = sinon .stub(identityPlatform, "getBlockingFunctionsConfig") - .throws("Unexpected call to getBlockingFunctionsConfig"); + .rejects(new Error("Unexpected call to getBlockingFunctionsConfig")); setConfig = sinon .stub(identityPlatform, "setBlockingFunctionsConfig") - .throws("Unexpected call to setBlockingFunctionsConfig"); + .rejects(new Error("Unexpected call to setBlockingFunctionsConfig")); }); afterEach(() => { @@ -150,7 +150,6 @@ describe("authBlocking", () => { await authBlockingService.registerTrigger(ep); - expect(blockingConfig).to.deep.equal(newBlockingConfig); expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); }); @@ -201,7 +200,6 @@ describe("authBlocking", () => { await authBlockingService.registerTrigger(ep); - expect(blockingConfig).to.deep.equal(newBlockingConfig); expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); }); @@ -252,7 +250,6 @@ describe("authBlocking", () => { await authBlockingService.registerTrigger(ep); - expect(blockingConfig).to.deep.equal(newBlockingConfig); expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); }); @@ -400,7 +397,6 @@ describe("authBlocking", () => { await authBlockingService.unregisterTrigger(ep); - expect(blockingConfig).to.deep.equal(newBlockingConfig); expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); }); @@ -446,7 +442,6 @@ describe("authBlocking", () => { await authBlockingService.unregisterTrigger(ep); - expect(blockingConfig).to.deep.equal(newBlockingConfig); expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); }); @@ -488,7 +483,6 @@ describe("authBlocking", () => { await authBlockingService.unregisterTrigger(ep); - expect(blockingConfig).to.deep.equal(newBlockingConfig); expect(setConfig).to.have.been.calledWith("project", newBlockingConfig); }); }); diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index de968066ddb..01cb8572263 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -128,7 +128,7 @@ describe("validate", () => { httpsTrigger: {}, }; - it("Disallows concurrency for GCF gen 1", () => { + it("disallows concurrency for GCF gen 1", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, platform: "gcfv1", @@ -179,7 +179,7 @@ describe("validate", () => { } }); - it("Disallows concurrency with too little memory (implicit)", () => { + it("disallows concurrency with too little memory (implicit)", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, concurrency: 2, @@ -189,7 +189,7 @@ describe("validate", () => { ); }); - it("Disallows concurrency with too little memory (explicit)", () => { + it("disallows concurrency with too little memory (explicit)", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, concurrency: 2, @@ -200,7 +200,7 @@ describe("validate", () => { ); }); - it("Disallows multiple beforeCreate blocking", () => { + it("disallows multiple beforeCreate blocking", () => { const ep1: backend.Endpoint = { platform: "gcfv1", id: "id1", @@ -229,7 +229,7 @@ describe("validate", () => { ); }); - it("Disallows multiple beforeSignIn blocking", () => { + it("disallows multiple beforeSignIn blocking", () => { const ep1: backend.Endpoint = { platform: "gcfv1", id: "id1", From f5d73e64aa0d60b3dda9bc63e2f1c4eab9ddfae2 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Sat, 16 Apr 2022 18:27:52 -0700 Subject: [PATCH 0245/1699] Add support for larger instance sizes (#4152) --- src/deploy/functions/pricing.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/deploy/functions/pricing.ts b/src/deploy/functions/pricing.ts index 41837131e44..35dc33d9376 100644 --- a/src/deploy/functions/pricing.ts +++ b/src/deploy/functions/pricing.ts @@ -117,14 +117,17 @@ export const V2_FREE_TIER = { // In v1, CPU is automatically fixed to the memory size determines the CPU size. // Table at https://cloud.google.com/functions/pricing#compute_time +const VCPU_TO_GHZ = 2.4; const MB_TO_GHZ = { 128: 0.2, 256: 0.4, 512: 0.8, 1024: 1.4, - 2048: 2.4, - 4096: 4.8, - 8192: 4.8, + 2048: 1 * VCPU_TO_GHZ, + 4096: 2 * VCPU_TO_GHZ, + 8192: 2 * VCPU_TO_GHZ, + 16384: 4 * VCPU_TO_GHZ, + 32768: 8 * VCPU_TO_GHZ, }; /** Whether we have information in our price sheet to calculate the minInstance cost. */ From d3f860f5abf9116848d1cbafaba7d2aa5e554b56 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Sat, 16 Apr 2022 18:41:20 -0700 Subject: [PATCH 0246/1699] Introduce some metaprogramming types (#4432) * Introduce some metaprogramming types * Fix breaks * PR feedback --- src/functional.ts | 28 ++++++----- src/metaprogramming.ts | 83 +++++++++++++++++++++++++++++++ src/test/functional.spec.ts | 8 +++ src/test/metapgrogramming.spec.ts | 76 ++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 src/metaprogramming.ts create mode 100644 src/test/metapgrogramming.spec.ts diff --git a/src/functional.ts b/src/functional.ts index 97de8d3d117..8a4946b7273 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -1,3 +1,5 @@ +import { LeafElems } from "./metaprogramming"; + /** * Flattens an object so that the return value's keys are the path * to a value in the source object. E.g. flattenObject({the: {answer: 42}}) @@ -5,14 +7,14 @@ * @param obj An object to be flattened * @return An array where values come from obj and keys are the path in obj to that value. */ -export function* flattenObject(obj: Record): Generator<[string, unknown]> { - function* helper(path: string[], obj: Record): Generator<[string, unknown]> { +export function* flattenObject(obj: T): Generator<[string, unknown]> { + function* helper(path: string[], obj: V): Generator<[string, unknown]> { for (const [k, v] of Object.entries(obj)) { if (typeof v !== "object" || v === null) { yield [[...path, k].join("."), v]; } else { // Object.entries loses type info, so we must cast - yield* helper([...path, k], v as Record); + yield* helper([...path, k], v); } } } @@ -25,33 +27,35 @@ export function* flattenObject(obj: Record): Generator<[string, * [...flatten([[[1]], [2], 3])] = [1, 2, 3] */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function* flattenArray(arr: unknown[]): Generator { +export function* flattenArray(arr: T): Generator> { for (const val of arr) { if (Array.isArray(val)) { yield* flattenArray(val); } else { - yield val as T; + yield val as LeafElems; } } } /** Shorthand for flattenObject. */ -export function flatten(obj: Record): Generator<[string, unknown]>; +export function flatten(obj: T): Generator<[string, string]>; /** Shorthand for flattenArray. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function flatten(arr: unknown[]): Generator; +export function flatten(arr: T): Generator>; /** Flattens an object or array. */ -export function flatten( - objOrArr: Record | unknown[] -): Generator<[string, unknown]> | Generator { +export function flatten(objOrArr: T): unknown { if (Array.isArray(objOrArr)) { - return flattenArray(objOrArr); + return flattenArray(objOrArr); } else { return flattenObject(objOrArr); } } +type RecursiveElems = { + [Key in keyof T]: T[Key] extends unknown[] ? T[Key] | RecursiveElems : T[Key]; +}[number]; + /** * Used with reduce to flatten in place. * Due to the quirks of TypeScript, callers must pass [] as the @@ -59,7 +63,7 @@ export function flatten( */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function reduceFlat(accum: T[] | undefined, next: unknown): T[] { - return [...(accum || []), ...flatten([next])]; + return [...(accum || []), ...(flatten([next]) as Generator)]; } /** diff --git a/src/metaprogramming.ts b/src/metaprogramming.ts new file mode 100644 index 00000000000..95ebc81f9bd --- /dev/null +++ b/src/metaprogramming.ts @@ -0,0 +1,83 @@ +type Primitive = string | number | boolean | Function; + +/** + * RecursiveKeyOf is a type for keys of an objet usind dots for subfields. + * For a given object: {a: {b: {c: number}}, d } the RecursiveKeysOf are + * 'a' | 'a.b' | 'a.b.c' | 'd' + */ +export type RecursiveKeyOf = T extends Primitive + ? never + : + | (keyof T & string) + | { + [P in keyof T & string]: RecursiveSubKeys; + }[keyof T & string]; + +type RecursiveSubKeys = T[P] extends (infer Elem)[] + ? `${P}.${RecursiveKeyOf}` + : T[P] extends object + ? `${P}.${RecursiveKeyOf}` + : never; + +/** + * LeafKeysOf is like RecursiveKeysOf but omits the keys for any object. + * For a given object: {a: {b: {c: number}}, d } the LeafKeysOf are + * 'a.b.c' | 'd' + */ +export type LeafKeysOf = { + [Key in keyof T & (string | number)]: T[Key] extends unknown[] + ? `${Key}` + : T[Key] extends object + ? `${Key}.${RecursiveKeyOf}` + : `${Key}`; +}[keyof T & (string | number)]; + +/** + * SameType is used in testing to verify that two types are the same. + * Usage: + * const test: SameType = true. + * The assigment will fail if the types are different. + */ +export type SameType = T extends V ? (V extends T ? true : false) : false; + +type HeadOf = [T extends `${infer Head}.${infer Tail}` ? Head : T][number]; + +type TailsOf = [ + T extends `${Head}.${infer Tail}` ? Tail : never +][number]; + +/** + * DeepOmit allows you to omit fields from a nested structure using recursive keys. + */ +export type DeepOmit> = DeepOmitUnsafe; + +type DeepOmitUnsafe = { + [Key in Exclude]: Key extends Keys + ? T[Key] | undefined + : Key extends HeadOf + ? DeepOmitUnsafe> + : T[Key]; +}; + +export type DeepPick> = DeepPickUnsafe; + +type DeepPickUnsafe = { + [Key in Extract>]: Key extends Keys + ? T[Key] + : DeepPickUnsafe>; +}; + +/** In the array LeafElems<[[["a"], "b"], ["c"]]> is "a" | "b" | "c" */ +export type LeafElems = T extends Array + ? Elem extends unknown[] + ? LeafElems + : Elem + : T; + +/** + * In the object {a: number, b: { c: string } }, + * LeafValues is number | string + */ +export type LeafValues = { + [Key in keyof T]: T[Key] extends object ? LeafValues : T[Key]; +}[keyof T]; diff --git a/src/test/functional.spec.ts b/src/test/functional.spec.ts index 357835b365f..753b4ae5aa3 100644 --- a/src/test/functional.spec.ts +++ b/src/test/functional.spec.ts @@ -1,5 +1,6 @@ import { expect } from "chai"; import { flatten } from "lodash"; +import { SameType } from "../metaprogramming"; import * as f from "../functional"; @@ -13,6 +14,13 @@ describe("functional", () => { expect([...f.flatten({ a: "b" })]).to.deep.equal([["a", "b"]]); }); + it("Gets the right type for flattening arrays", () => { + const arr = [[["a"], "b"], ["c"]]; + const flattened = [...f.flattenArray(arr)]; + const test: SameType = true; + expect(test).to.be.true; + }); + it("can handle nested objects", () => { const init = { outer: { diff --git a/src/test/metapgrogramming.spec.ts b/src/test/metapgrogramming.spec.ts new file mode 100644 index 00000000000..0ec4cb7faec --- /dev/null +++ b/src/test/metapgrogramming.spec.ts @@ -0,0 +1,76 @@ +import { expect } from "chai"; +import { SameType, RecursiveKeyOf, LeafElems, DeepPick, DeepOmit } from "../metaprogramming"; + +describe("metaprogramming", () => { + it("can calcluate recursive keys", () => { + const test: SameType< + RecursiveKeyOf<{ + a: number; + b: { + c: boolean; + d: { + e: number; + }; + }; + }>, + "a" | "a.b" | "a.b.c" | "a.b.d" | "a.b.d.e" + > = true; + expect(test).to.be.true; + }); + + it("can detect recursive elems", () => { + const test: SameType, "a" | "b" | "c"> = true; + expect(test).to.be.true; + }); + + it("Can deep pick", () => { + interface original { + a: number; + b: { + c: boolean; + d: { + e: number; + }; + g: boolean; + }; + h: number; + } + + interface expected { + a: number; + b: { + c: boolean; + }; + } + + const test: SameType, expected> = true; + expect(test).to.be.true; + }); + + it("can deep omit", () => { + interface original { + a: number; + b: { + c: boolean; + d: { + e: number; + }; + g: boolean; + }; + h: number; + } + + interface expected { + b: { + d: { + e: number; + }; + g: boolean; + }; + h: number; + } + + const test: SameType, expected> = true; + expect(test).to.be.true; + }); +}); From b24f4055584538554f2271af1543675d3a00b83c Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 18 Apr 2022 09:51:35 -0700 Subject: [PATCH 0247/1699] Adding support for local extensions to firebase deploy --only extensions (#4434) --- src/deploy/extensions/deploy.ts | 7 +- src/deploy/extensions/deploymentSummary.ts | 9 ++- src/deploy/extensions/planner.ts | 19 ++--- src/deploy/extensions/secrets.ts | 11 ++- src/deploy/extensions/tasks.ts | 82 ++++++++++++++++------ src/extensions/extensionsHelper.ts | 5 +- src/extensions/provisioningHelper.ts | 6 +- src/extensions/warnings.ts | 8 +-- 8 files changed, 93 insertions(+), 54 deletions(-) diff --git a/src/deploy/extensions/deploy.ts b/src/deploy/extensions/deploy.ts index bdb339673ed..47e1edcbf53 100644 --- a/src/deploy/extensions/deploy.ts +++ b/src/deploy/extensions/deploy.ts @@ -33,18 +33,19 @@ export async function deploy(context: Context, options: Options, payload: Payloa }); // Validate all creates, updates and configures. + // Skip validating local extensions, since doing so requires us to create a new source. // No need to validate deletes. - for (const create of payload.instancesToCreate ?? []) { + for (const create of payload.instancesToCreate?.filter((i) => !!i.ref) ?? []) { const task = tasks.createExtensionInstanceTask(projectId, create, /* validateOnly=*/ true); void validationQueue.run(task); } - for (const update of payload.instancesToUpdate ?? []) { + for (const update of payload.instancesToUpdate?.filter((i) => !!i.ref) ?? []) { const task = tasks.updateExtensionInstanceTask(projectId, update, /* validateOnly=*/ true); void validationQueue.run(task); } - for (const configure of payload.instancesToConfigure ?? []) { + for (const configure of payload.instancesToConfigure?.filter((i) => !!i.ref) ?? []) { const task = tasks.configureExtensionInstanceTask( projectId, configure, diff --git a/src/deploy/extensions/deploymentSummary.ts b/src/deploy/extensions/deploymentSummary.ts index 16917417930..929182089f9 100644 --- a/src/deploy/extensions/deploymentSummary.ts +++ b/src/deploy/extensions/deploymentSummary.ts @@ -10,8 +10,10 @@ export const humanReadable = (dep: planner.InstanceSpec) => const humanReadableUpdate = (from: planner.InstanceSpec, to: planner.InstanceSpec) => { if ( - from.ref?.publisherId === to.ref?.publisherId && - from.ref?.extensionId === to.ref?.extensionId + from.ref && + to.ref && + from.ref.publisherId === to.ref.publisherId && + from.ref.extensionId === to.ref.extensionId ) { return `\t${clc.bold(from.instanceId)} (${refs.toExtensionVersionRef(from.ref!)} => ${ to.ref?.version @@ -20,7 +22,8 @@ const humanReadableUpdate = (from: planner.InstanceSpec, to: planner.InstanceSpe const fromRef = from.ref ? `${refs.toExtensionVersionRef(from.ref)}` : `Installed from local source`; - return `\t${clc.bold(from.instanceId)} (${fromRef} => ${refs.toExtensionVersionRef(to.ref!)})`; + const toRef = to.ref ? `${refs.toExtensionVersionRef(to.ref)}` : `Installed from local source`; + return `\t${clc.bold(from.instanceId)} (${fromRef} => ${toRef})`; } }; diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index ee0e9facbcc..19a78712a81 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -151,21 +151,12 @@ export async function want(args: { const autoPopulatedParams = await getFirebaseProjectParams(args.projectId, args.emulatorMode); const subbedParams = substituteParams(params, autoPopulatedParams); - let localPath: string; - // TODO(lihes): Remove once firebase deploy supports ext with local source. if (isLocalPath(e[1])) { - if (!args.emulatorMode) { - logger.warn( - `Unable to deploy instance ${instanceId} because it has a local source, please use "firebase ext:install" instead.` - ); - continue; - } else { - instanceSpecs.push({ - instanceId, - localPath: e[1], - params: subbedParams, - }); - } + instanceSpecs.push({ + instanceId, + localPath: e[1], + params: subbedParams, + }); } else { const ref = refs.parse(e[1]); ref.version = await resolveVersion(ref); diff --git a/src/deploy/extensions/secrets.ts b/src/deploy/extensions/secrets.ts index 30e37fe4e41..b7f46537806 100644 --- a/src/deploy/extensions/secrets.ts +++ b/src/deploy/extensions/secrets.ts @@ -4,7 +4,12 @@ import * as secretUtils from "../../extensions/secretsUtils"; import * as secretManager from "../../gcp/secretManager"; import { Payload } from "./args"; -import { getExtensionVersion, DeploymentInstanceSpec, InstanceSpec } from "./planner"; +import { + getExtensionVersion, + DeploymentInstanceSpec, + InstanceSpec, + getExtensionSpec, +} from "./planner"; import { promptCreateSecret } from "../../extensions/askUserForParam"; import { ExtensionSpec, Param, ParamType } from "../../extensions/extensionsApi"; import { FirebaseError } from "../../error"; @@ -41,8 +46,8 @@ export async function handleSecretParams( } export async function checkSpecForSecrets(i: InstanceSpec): Promise { - const extensionVersion = await getExtensionVersion(i); - return secretUtils.usesSecrets(extensionVersion.spec); + const extensionSpec = await getExtensionSpec(i); + return secretUtils.usesSecrets(extensionSpec); } const secretsInSpec = (spec: ExtensionSpec): Param[] => { diff --git a/src/deploy/extensions/tasks.ts b/src/deploy/extensions/tasks.ts index 317de7d844c..d92913e3ce9 100644 --- a/src/deploy/extensions/tasks.ts +++ b/src/deploy/extensions/tasks.ts @@ -1,6 +1,8 @@ import * as clc from "cli-color"; +import { FirebaseError } from "../../error"; import * as extensionsApi from "../../extensions/extensionsApi"; +import { createSourceFromLocation } from "../../extensions/extensionsHelper"; import * as refs from "../../extensions/refs"; import * as utils from "../../utils"; import { ErrorHandler } from "./errors"; @@ -42,13 +44,28 @@ export function createExtensionInstanceTask( validateOnly: boolean = false ): ExtensionDeploymentTask { const run = async () => { - await extensionsApi.createInstance({ - projectId, - instanceId: instanceSpec.instanceId, - params: instanceSpec.params, - extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref!), - validateOnly, - }); + if (instanceSpec.ref) { + await extensionsApi.createInstance({ + projectId, + instanceId: instanceSpec.instanceId, + params: instanceSpec.params, + extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref), + validateOnly, + }); + } else if (instanceSpec.localPath) { + const extensionSource = await createSourceFromLocation(projectId, instanceSpec.localPath); + await extensionsApi.createInstance({ + projectId, + instanceId: instanceSpec.instanceId, + params: instanceSpec.params, + extensionSource, + validateOnly, + }); + } else { + throw new FirebaseError( + `Tried to create extension instance ${instanceSpec.instanceId} without a ref or a local path. This should never happen.` + ); + } printSuccess(instanceSpec.instanceId, "create", validateOnly); return; }; @@ -65,13 +82,27 @@ export function updateExtensionInstanceTask( validateOnly: boolean = false ): ExtensionDeploymentTask { const run = async () => { - await extensionsApi.updateInstanceFromRegistry({ - projectId, - instanceId: instanceSpec.instanceId, - extRef: refs.toExtensionVersionRef(instanceSpec.ref!), - params: instanceSpec.params, - validateOnly, - }); + if (instanceSpec.ref) { + await extensionsApi.updateInstanceFromRegistry({ + projectId, + instanceId: instanceSpec.instanceId, + extRef: refs.toExtensionVersionRef(instanceSpec.ref!), + params: instanceSpec.params, + validateOnly, + }); + } else if (instanceSpec.localPath) { + const extensionSource = await createSourceFromLocation(projectId, instanceSpec.localPath); + await extensionsApi.updateInstance({ + projectId, + instanceId: instanceSpec.instanceId, + extensionSource, + validateOnly, + }); + } else { + throw new FirebaseError( + `Tried to update extension instance ${instanceSpec.instanceId} without a ref or a local path. This should never happen.` + ); + } printSuccess(instanceSpec.instanceId, "update", validateOnly); return; }; @@ -88,12 +119,23 @@ export function configureExtensionInstanceTask( validateOnly: boolean = false ): ExtensionDeploymentTask { const run = async () => { - await extensionsApi.configureInstance({ - projectId, - instanceId: instanceSpec.instanceId, - params: instanceSpec.params, - validateOnly, - }); + if (instanceSpec.ref) { + await extensionsApi.configureInstance({ + projectId, + instanceId: instanceSpec.instanceId, + params: instanceSpec.params, + validateOnly, + }); + } else if (instanceSpec.localPath) { + // We should _always_ be updating when using local extensions, since we don't know if there was a code change at the local path since last deploy. + throw new FirebaseError( + `Tried to configure extension instance ${instanceSpec.instanceId} from a local path. This should never happen.` + ); + } else { + throw new FirebaseError( + `Tried to configure extension instance ${instanceSpec.instanceId} without a ref or a local path. This should never happen.` + ); + } printSuccess(instanceSpec.instanceId, "configure", validateOnly); return; }; diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 0d40b03babd..4074b0db1e1 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -553,18 +553,15 @@ export async function createSourceFromLocation( let packageUri: string; let objectPath = ""; - let spinner = ora(" Archiving and uploading extension source code"); + const spinner = ora(" Archiving and uploading extension source code"); try { spinner.start(); objectPath = await archiveAndUploadSource(sourceUri, EXTENSIONS_BUCKET_NAME); spinner.succeed(" Uploaded extension source code"); - spinner = ora(" Creating an extension source based on uploaded source code"); - spinner.start(); packageUri = storageOrigin + objectPath + "?alt=media"; const res = await createSource(projectId, packageUri, extensionRoot); logger.debug("Created new Extension Source %s", res.name); - spinner.succeed(" Created extension source code"); // if we uploaded an object to user's bucket, delete it after "createSource" copies it into extension service's bucket. await deleteUploadedSource(objectPath); diff --git a/src/extensions/provisioningHelper.ts b/src/extensions/provisioningHelper.ts index 4c359e50de0..d21feee3a8b 100644 --- a/src/extensions/provisioningHelper.ts +++ b/src/extensions/provisioningHelper.ts @@ -6,7 +6,7 @@ import { firebaseStorageOrigin, firedataOrigin } from "../api"; import { Client } from "../apiv2"; import { flattenArray } from "../functional"; import { FirebaseError } from "../error"; -import { getExtensionVersion, InstanceSpec } from "../deploy/extensions/planner"; +import { getExtensionSpec, InstanceSpec } from "../deploy/extensions/planner"; /** Product for which provisioning can be (or is) deferred */ export enum DeferredProduct { @@ -38,8 +38,8 @@ export async function bulkCheckProductsProvisioned( ): Promise { const usedProducts = await Promise.all( instanceSpecs.map(async (i) => { - const extensionVersion = await getExtensionVersion(i); - return getUsedProducts(extensionVersion.spec); + const extensionSpec = await getExtensionSpec(i); + return getUsedProducts(extensionSpec); }) ); await checkProducts(projectId, [...flattenArray(usedProducts)]); diff --git a/src/extensions/warnings.ts b/src/extensions/warnings.ts index d4c3188c098..37040d8bd3f 100644 --- a/src/extensions/warnings.ts +++ b/src/extensions/warnings.ts @@ -80,16 +80,16 @@ const toListEntry = (i: InstanceSpec) => { */ export async function displayWarningsForDeploy(instancesToCreate: InstanceSpec[]) { const trustedPublishers = await getTrustedPublishers(); - for (const i of instancesToCreate) { + const publishedExtensionInstances = instancesToCreate.filter((i) => i.ref); + for (const i of publishedExtensionInstances) { await getExtension(i); - await getExtensionVersion(i); } const [eapExtensions, nonEapExtensions] = partition( - instancesToCreate, + publishedExtensionInstances, (i) => !trustedPublishers.includes(i.ref?.publisherId ?? "") ); - // Only mark non-eap extensions as expeirmental. + // Only mark non-eap extensions as experimental. const experimental = nonEapExtensions.filter( (i) => i.extension!.registryLaunchStage === RegistryLaunchStage.EXPERIMENTAL ); From 6c9273ccbe279d820b5857ab83b32fb10949c61c Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 18 Apr 2022 11:21:18 -0700 Subject: [PATCH 0248/1699] Stop advertising CUD that might get pulled (#4452) Co-authored-by: Daniel Lee --- src/deploy/functions/prompts.ts | 14 ++++---------- src/test/deploy/functions/prompts.spec.ts | 15 --------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/deploy/functions/prompts.ts b/src/deploy/functions/prompts.ts index d686d6aaca9..f8df907f580 100644 --- a/src/deploy/functions/prompts.ts +++ b/src/deploy/functions/prompts.ts @@ -122,9 +122,9 @@ export async function promptForFunctionDeletion( /** * Checks whether a deploy will increase the min instance idle time bill of * any function. Cases include: - * * Setting minInstances on a new or existing function - * * Increasing the minInstances of an existing function - * * Increasing the CPU or memory of a function with min instances + * Setting minInstances on a new or existing function + * Increasing the minInstances of an existing function + * Increasing the CPU or memory of a function with min instances * If there are any, prompts the user to confirm a minimum bill. */ export async function promptForMinInstances( @@ -192,11 +192,6 @@ export async function promptForMinInstances( const cost = pricing.monthlyMinInstanceCost(backend.allEndpoints(want)).toFixed(2); costLine = `With these options, your minimum bill will be $${cost} in a 30-day month`; } - let cudAnnotation = ""; - if (backend.someEndpoint(want, (fn) => fn.platform === "gcfv2" && !!fn.minInstances)) { - cudAnnotation = - "\nThis bill can be lowered with a one year commitment. See https://cloud.google.com/run/cud for more"; - } const warnMessage = "The following functions have reserved minimum instances. This will " + "reduce the frequency of cold starts but increases the minimum cost. " + @@ -204,8 +199,7 @@ export async function promptForMinInstances( "CPU allocation of instances while they are idle.\n\n" + functionLines + "\n\n" + - costLine + - cudAnnotation; + costLine; utils.logLabeledWarning("functions", warnMessage); diff --git a/src/test/deploy/functions/prompts.spec.ts b/src/test/deploy/functions/prompts.spec.ts index cea07f0bce6..d99f8be175a 100644 --- a/src/test/deploy/functions/prompts.spec.ts +++ b/src/test/deploy/functions/prompts.spec.ts @@ -415,19 +415,4 @@ describe("promptForMinInstances", () => { expect(promptStub).to.have.been.called; expect(logStub.firstCall.args[1]).to.match(/Cannot calculate the minimum monthly bill/); }); - - it("Should advise customers of possible discounts", async () => { - const endpoint: backend.Endpoint = { - ...SAMPLE_ENDPOINT, - platform: "gcfv2", - minInstances: 2, - }; - promptStub.resolves(true); - - await expect( - functionPrompts.promptForMinInstances(SAMPLE_OPTIONS, backend.of(endpoint), backend.empty()) - ).to.eventually.be.fulfilled; - expect(promptStub).to.have.been.called; - expect(logStub.firstCall.args[1]).to.match(new RegExp("https://cloud.google.com/run/cud")); - }); }); From ce040bc452a5021391b947a06cd2d3305f8e23b3 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 18 Apr 2022 11:34:44 -0700 Subject: [PATCH 0249/1699] Stop debug printing kilobytes of discovery docs (#4428) * Stop debug printing kilobytes of discovery docs * Changelog * Formatter Co-authored-by: Daniel Lee --- CHANGELOG.md | 1 + src/ensureApiEnabled.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b47882cf1f4..0e40cc16d8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,4 @@ - Fixes cross-platform incompatibility with Storage Emulator exports (#4411). - Fixes issue where function deployment errored on projects without secrets (#4425). - Adds a blocking trigger type (#4395). +- Removes verbose HTTP responses from debug logs (#4428) diff --git a/src/ensureApiEnabled.ts b/src/ensureApiEnabled.ts index 7fcaac1743c..4a6b4a7fa27 100644 --- a/src/ensureApiEnabled.ts +++ b/src/ensureApiEnabled.ts @@ -29,7 +29,9 @@ export async function check( prefix: string, silent = false ): Promise { - const res = await apiClient.get<{ state: string }>(`/projects/${projectId}/services/${apiName}`); + const res = await apiClient.get<{ state: string }>(`/projects/${projectId}/services/${apiName}`, { + skipLog: { resBody: true }, + }); const isEnabled = res.body.state === "ENABLED"; if (isEnabled && !silent) { utils.logLabeledSuccess(prefix, `required API ${bold(apiName)} is enabled`); @@ -48,7 +50,9 @@ export async function check( */ async function enable(projectId: string, apiName: string): Promise { try { - await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`); + await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, { + skipLog: { resBody: true }, + }); } catch (err: any) { if (isBillingError(err)) { throw new FirebaseError(`Your project ${bold( From 6d55a0e6b880bebe231a07c36e17ec0a72d12dbe Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 18 Apr 2022 15:19:57 -0700 Subject: [PATCH 0250/1699] Use one preview flag for all of extensionsemulator (#4455) * Use one flag for all of extensionsemulator * linting --- .../internals.test.ts | 2 +- src/emulator/downloadableEmulators.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/storage-emulator-integration/internals.test.ts b/scripts/storage-emulator-integration/internals.test.ts index e1507692415..835b516b458 100644 --- a/scripts/storage-emulator-integration/internals.test.ts +++ b/scripts/storage-emulator-integration/internals.test.ts @@ -126,7 +126,7 @@ describe("Emulator Internals", function (this) { const smallFilePath = createRandomFile("testFile", SMALL_FILE_SIZE); await testGcsBucket.upload(smallFilePath, { destination: "public/testFile" }); - const downloadUrl = await page.evaluate(async () => { + const downloadUrl = await page.evaluate(() => { return firebase.storage().ref("public/testFile").getDownloadURL(); }); const requestClient = http; diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index c7d48e5016a..52a1ad8d9a7 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -62,7 +62,24 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe namePrefix: "cloud-storage-rules-emulator", }, }, - ui: previews.emulatoruisnapshot + ui: previews.extensionsemulator + ? { + version: "EXTENSIONS", + downloadPath: path.join(CACHE_DIR, "ui-vEXTENSIONS.zip"), + unzipDir: path.join(CACHE_DIR, "ui-vEXTENSIONS"), + binaryPath: path.join(CACHE_DIR, "ui-vEXTENSIONS", "server.bundle.js"), + opts: { + cacheDir: CACHE_DIR, + remoteUrl: + "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vEXTENSIONS.zip", + expectedSize: -1, + expectedChecksum: "", + skipCache: true, + skipChecksumAndSize: true, + namePrefix: "ui", + }, + } + : previews.emulatoruisnapshot ? { version: "SNAPSHOT", downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"), From c559a9e4f170aa554021cd7e8a79b6a804d5474b Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Tue, 19 Apr 2022 11:47:55 -0700 Subject: [PATCH 0251/1699] Release Cloud Firestore Emulator v1.14.3. (#4457) --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e40cc16d8d..8b2a8c211eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,4 @@ - Fixes issue where function deployment errored on projects without secrets (#4425). - Adds a blocking trigger type (#4395). - Removes verbose HTTP responses from debug logs (#4428) +- Releases Cloud Firestore Emulator v1.14.3: fixes #4336. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 52a1ad8d9a7..83b2fd3621b 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -39,14 +39,14 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe }, }, firestore: { - downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.2.jar"), - version: "1.14.2", + downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.3.jar"), + version: "1.14.3", opts: { cacheDir: CACHE_DIR, remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.2.jar", - expectedSize: 60551603, - expectedChecksum: "0930bb09c080b52b1cbc70a91377ccd8", + "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.3.jar", + expectedSize: 60442855, + expectedChecksum: "63517534875818689639ee5dee57dd52", namePrefix: "cloud-firestore-emulator", }, }, From 0241cf6b9e1d9e255d0f087a6d107716d3beed70 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Tue, 19 Apr 2022 14:17:29 -0700 Subject: [PATCH 0252/1699] Skip IPv4/IPv6 tests if unsupported. (#4458) * Skip IPv4/IPv6 tests if unsupported. * `this` is working as intended. --- src/test/emulators/registry.spec.ts | 36 ++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/test/emulators/registry.spec.ts b/src/test/emulators/registry.spec.ts index f4c6cc52bf9..c86e2bbba21 100644 --- a/src/test/emulators/registry.spec.ts +++ b/src/test/emulators/registry.spec.ts @@ -4,6 +4,7 @@ import { expect } from "chai"; import { FakeEmulator } from "./fakeEmulator"; import { findAvailablePort } from "../../emulator/portUtils"; import * as express from "express"; +import * as os from "os"; describe("EmulatorRegistry", () => { afterEach(async () => { @@ -46,6 +47,24 @@ describe("EmulatorRegistry", () => { }); describe("#url", () => { + // Only run IPv4 / IPv6 tests if supported respectively. + let ipv4Supported = false; + let ipv6Supported = false; + before(() => { + for (const ifaces of Object.values(os.networkInterfaces())) { + for (const iface of ifaces) { + switch (iface.family) { + case "IPv4": + ipv4Supported = true; + break; + case "IPv6": + ipv6Supported = true; + break; + } + } + } + }); + const name = Emulators.FUNCTIONS; afterEach(() => { return EmulatorRegistry.stopAll(); @@ -58,21 +77,32 @@ describe("EmulatorRegistry", () => { expect(EmulatorRegistry.url(name).host).to.eql(`localhost:${port}`); }); - it("should quote IPv6 addresses", async () => { + it("should quote IPv6 addresses", async function (this) { + if (!ipv6Supported) { + return this.skip(); + } const port = await findAvailablePort("::1", 5000); await EmulatorRegistry.start(new FakeEmulator(name, "::1", port)); expect(EmulatorRegistry.url(name).host).to.eql(`[::1]:${port}`); }); - it("should use 127.0.0.1 instead of 0.0.0.0", async () => { + it("should use 127.0.0.1 instead of 0.0.0.0", async function (this) { + if (!ipv4Supported) { + return this.skip(); + } + const port = await findAvailablePort("0.0.0.0", 5000); await EmulatorRegistry.start(new FakeEmulator(name, "0.0.0.0", port)); expect(EmulatorRegistry.url(name).host).to.eql(`127.0.0.1:${port}`); }); - it("should use ::1 instead of ::", async () => { + it("should use ::1 instead of ::", async function (this) { + if (!ipv6Supported) { + return this.skip(); + } + const port = await findAvailablePort("::", 5000); await EmulatorRegistry.start(new FakeEmulator(name, "::", port)); From c3448569c4d70c756522bdd680a5597aa2a6d5e0 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 19 Apr 2022 21:27:01 +0000 Subject: [PATCH 0253/1699] 10.7.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0f9ca032a9d..5470beb7e2c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.6.0", + "version": "10.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.6.0", + "version": "10.7.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 20b10005747..6961b391512 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.6.0", + "version": "10.7.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 336357c03ae04dd39c385e9d0a7ee4427c0e7438 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 19 Apr 2022 21:27:25 +0000 Subject: [PATCH 0254/1699] [firebase-release] Removed change log and reset repo after 10.7.0 release --- CHANGELOG.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2a8c211eb..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +0,0 @@ -- Fix URL with wrong host returned in storage resumable upload (#4374). -- Fixes Firestore emulator transaction expiration and reused bug. -- Fixes Firestore emulator deadlock bug. [#2452](https://github.com/firebase/firebase-tools/issues/2452) -- Ensure that the hosting emulator port is not claimed by OSX (#4415). -- Improves support for prerelease versions in `ext:dev:publish` (#4244). -- Fixes console error on large uploads to Storage Emulator (#4407). -- Fixes cross-platform incompatibility with Storage Emulator exports (#4411). -- Fixes issue where function deployment errored on projects without secrets (#4425). -- Adds a blocking trigger type (#4395). -- Removes verbose HTTP responses from debug logs (#4428) -- Releases Cloud Firestore Emulator v1.14.3: fixes #4336. From 35dd3a9bfe6f2a1ea3ee7adaa96a24deb5d40d6e Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 20 Apr 2022 10:14:28 -0700 Subject: [PATCH 0255/1699] Allow and deploy multiple codebases on deploy. (#4419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What's new Let's say we are given `firebase.json` that specifies multiple function configuration (which became possible w/ https://github.com/firebase/firebase-tools/pull/4269): ```json { "functions": [ { "source": "functions", "codebase": "foo" }, { "source": "moreFunctions", "codebase": "bar" } ] } ``` With this change, we now deploy functions discovered in both codebases: ```bash $ firebase deploy --only functions i deploying functions ... i functions: preparing codebase foo for deployment i functions: preparing codebase bar for deployment i functions: preparing functions directory for uploading... i functions: packaged functions(45.24 KB) for uploading i functions: preparing moreFunctions directory for uploading... i functions: packaged moreFunctions (48.39 KB) for uploading ✔ functions: functions folder uploaded successfully ✔ functions: moreFunctions folder uploaded successfully i functions: creating Node.js 14 function [foo]myFn(us-central1)... i functions: creating Node.js 14 function [foo]anotherfn(us-central1)... i functions: creating Node.js 14 function [bar]otherfn(us-west1)... ``` It's possible to just deploy functions in a specific codebase using the `--only` flag: ```bash $ firebase deploy --only functions:foo i deploying functions ... i functions: preparing codebase foo for deployment i functions: preparing functions directory for uploading... i functions: packaged functions(45.24 KB) for uploading ✔ functions: functions folder uploaded successfully i functions: creating Node.js 14 function [foo]myFn(us-central1)... i functions: creating Node.js 14 function [foo]anotherfn(us-central1)... ``` ## Implementation Note * Most of the deployment logic remains the same except that we are now book-keeping `sources` and `want/haveBackends` for each of the target codebase. * Changes in prepare stage of deployment looks big at first glance, but it's just wrapping backend discovery logic inside a for loop while moving around few pieces to ensure that we don't duplicate work like enabling necessary GCP APIs. * At the time of creating deployment plan to be executed by the fabricator, we simply merge the plan generated for each codebase. Fabricator works just fine. * We add logic to prevent setting codebase label on functions for fns in the "default" codebase. This should preserve the old behavior and only use the label for intentional codebase usages. --- src/commands/functions-delete.ts | 16 +- src/deploy/functions/args.ts | 16 +- src/deploy/functions/backend.ts | 27 +++ src/deploy/functions/checkIam.ts | 14 +- src/deploy/functions/deploy.ts | 107 ++++++--- src/deploy/functions/functionsDeployHelper.ts | 82 ++++++- src/deploy/functions/prepare.ts | 221 +++++++++--------- .../functions/prepareFunctionsUpload.ts | 1 - src/deploy/functions/release/fabricator.ts | 50 ++-- src/deploy/functions/release/index.ts | 25 +- src/deploy/functions/release/planner.ts | 41 ++-- src/deploy/functions/validate.ts | 30 +++ src/deploy/hosting/convertConfig.ts | 6 +- src/functions/projectConfig.ts | 24 +- src/gcp/cloudfunctions.ts | 13 +- src/gcp/cloudfunctionsv2.ts | 16 +- src/test/deploy/functions/backend.spec.ts | 29 +++ .../functions/functionsDeployHelper.spec.ts | 118 +++++++++- .../functions/release/fabricator.spec.ts | 9 +- .../deploy/functions/release/planner.spec.ts | 54 +++-- src/test/deploy/functions/validate.spec.ts | 63 +++++ src/test/deploy/hosting/convertConfig.spec.ts | 93 ++++---- src/test/functions/projectConfig.spec.ts | 18 +- src/test/gcp/cloudfunctions.spec.ts | 2 - src/test/gcp/cloudfunctionsv2.spec.ts | 2 - 25 files changed, 749 insertions(+), 328 deletions(-) diff --git a/src/commands/functions-delete.ts b/src/commands/functions-delete.ts index ebbd8886bbb..ff40177ee5a 100644 --- a/src/commands/functions-delete.ts +++ b/src/commands/functions-delete.ts @@ -48,12 +48,13 @@ export default new Command("functions:delete [filters...]") if (options.region) { existingBackend.endpoints = { [options.region]: existingBackend.endpoints[options.region] }; } - const plan = planner.createDeploymentPlan( - backend.empty(), - existingBackend, - context.filters, - /* deleteAll= */ true - ); + const plan = planner.createDeploymentPlan({ + wantBackend: backend.empty(), + haveBackend: existingBackend, + codebase: "", + filters: context.filters, + deleteAll: true, + }); const allEpToDelete = Object.values(plan) .map((changes) => changes.endpointsToDelete) .reduce(reduceFlat, []) @@ -93,8 +94,9 @@ export default new Command("functions:delete [filters...]") try { const fab = new fabricator.Fabricator({ functionExecutor, - executor: new executor.QueueExecutor({}), appEngineLocation, + executor: new executor.QueueExecutor({}), + sources: {}, }); const summary = await fab.applyPlan(plan); await reporter.logAndTrackDeployStats(summary); diff --git a/src/deploy/functions/args.ts b/src/deploy/functions/args.ts index be11f015c3c..69deca14f92 100644 --- a/src/deploy/functions/args.ts +++ b/src/deploy/functions/args.ts @@ -4,17 +4,12 @@ import * as projectConfig from "../../functions/projectConfig"; import * as deployHelper from "./functionsDeployHelper"; // These types should probably be in a root deploy.ts, but we can only boil the ocean one bit at a time. - interface CodebasePayload { wantBackend: backend.Backend; haveBackend: backend.Backend; } -// Payload holds the output of what we want to build + what we already have. -export interface Payload { - functions?: CodebasePayload; -} - +// Source holds details on location of packaged and uploaded source code. export interface Source { // Filled in the "prepare" phase. functionsSourceV1?: string; @@ -25,6 +20,11 @@ export interface Source { storage?: gcfV2.StorageSource; } +// Payload holds the output of what we want to build + what we already have. +export interface Payload { + functions?: Record; // codebase -> payload +} + // Context holds cached values of what we've looked up in handling this request. // For non-trivial values, use helper functions that cache automatically and/or hide implementation // details. @@ -33,12 +33,12 @@ export interface Context { filters?: deployHelper.EndpointFilter[]; // Filled in the "prepare" phase. - config?: projectConfig.ValidatedSingle; + config?: projectConfig.ValidatedConfig; artifactRegistryEnabled?: boolean; firebaseConfig?: FirebaseConfig; // Filled in the "prepare" and "deploy" phase. - source?: Source; + sources?: Record; // codebase -> source } export interface FirebaseConfig { diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index e1afae22a26..a9ee425a823 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -6,6 +6,7 @@ import * as runtimes from "./runtimes"; import { FirebaseError } from "../../error"; import { Context } from "./args"; import { previews } from "../../previews"; +import { flattenArray } from "../../functional"; /** Retry settings for a ScheduleSpec. */ export interface ScheduleRetryConfig { @@ -356,6 +357,32 @@ export function of(...endpoints: Endpoint[]): Backend { return bkend; } +/** + * A helper utility to merge backends. + */ +export function merge(...backends: Backend[]): Backend { + // Merge all endpoints + const merged = of(...flattenArray(backends.map((b) => allEndpoints(b)))); + + // Merge all APIs + const apiToReasons: Record> = {}; + for (const b of backends) { + for (const { api, reason } of b.requiredAPIs) { + const reasons = apiToReasons[api] || new Set(); + if (reason) { + reasons.add(reason); + } + apiToReasons[api] = reasons; + } + // Mere all environment variables. + merged.environmentVariables = { ...merged.environmentVariables, ...b.environmentVariables }; + } + for (const [api, reasons] of Object.entries(apiToReasons)) { + merged.requiredAPIs.push({ api, reason: Array.from(reasons).join(" ") }); + } + return merged; +} + /** * A helper utility to test whether a backend is empty. * Consumers should use this before assuming a backend is empty (e.g. nooping diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index c1b970fcb7e..9b8409e086a 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -3,12 +3,13 @@ import { bold } from "cli-color"; import { logger } from "../../logger"; import { getEndpointFilters, endpointMatchesAnyFilter } from "./functionsDeployHelper"; import { FirebaseError } from "../../error"; +import { Options } from "../../options"; +import { flattenArray } from "../../functional"; import * as iam from "../../gcp/iam"; import * as args from "./args"; import * as backend from "./backend"; import { track } from "../../track"; import * as utils from "../../utils"; -import { Options } from "../../options"; import { getIamPolicy, setIamPolicy } from "../../gcp/resourceManager"; import { Service, serviceForEndpoint } from "./services"; @@ -65,11 +66,12 @@ export async function checkHttpIam( options: Options, payload: args.Payload ): Promise { + if (!payload.functions) { + return; + } const filters = context.filters || getEndpointFilters(options); - const wantBackend = payload.functions!.wantBackend; - - const httpEndpoints = backend - .allEndpoints(wantBackend) + const wantBackends = Object.values(payload.functions).map(({ wantBackend }) => wantBackend); + const httpEndpoints = [...flattenArray(wantBackends.map((b) => backend.allEndpoints(b)))] .filter(backend.isHttpsTriggered) .filter((f) => endpointMatchesAnyFilter(f, filters)); @@ -128,7 +130,7 @@ function reduceEventsToServices(services: Array, endpoint: backend.Endp * @param existingPolicy the project level IAM policy * @param serviceAccount the IAM service account * @param role the role you want to grant - * @returns + * @return the correct IAM binding */ export function obtainBinding( existingPolicy: iam.Policy, diff --git a/src/deploy/functions/deploy.ts b/src/deploy/functions/deploy.ts index 127a84f7576..1a0de7b452a 100644 --- a/src/deploy/functions/deploy.ts +++ b/src/deploy/functions/deploy.ts @@ -3,8 +3,10 @@ import * as clc from "cli-color"; import * as fs from "fs"; import { checkHttpIam } from "./checkIam"; -import { logSuccess, logWarning, groupBy, endpoint } from "../../utils"; +import { logSuccess, logWarning } from "../../utils"; import { Options } from "../../options"; +import { FirebaseError } from "../../error"; +import { configForCodebase } from "../../functions/projectConfig"; import * as args from "./args"; import * as gcs from "../../gcp/storage"; import * as gcf from "../../gcp/cloudfunctions"; @@ -13,26 +15,79 @@ import * as backend from "./backend"; setGracefulCleanup(); -async function uploadSourceV1(context: args.Context, region: string): Promise { - const uploadUrl = await gcf.generateUploadUrl(context.projectId, region); - context.source!.sourceUrl = uploadUrl; +async function uploadSourceV1( + projectId: string, + source: args.Source, + wantBackend: backend.Backend +): Promise { + const v1Endpoints = backend.allEndpoints(wantBackend).filter((e) => e.platform === "gcfv1"); + if (v1Endpoints.length === 0) { + return; + } + const region = v1Endpoints[0].region; // Just pick a region to upload the source. + const uploadUrl = await gcf.generateUploadUrl(projectId, region); const uploadOpts = { - file: context.source!.functionsSourceV1!, - stream: fs.createReadStream(context.source!.functionsSourceV1!), + file: source.functionsSourceV1!, + stream: fs.createReadStream(source.functionsSourceV1!), }; await gcs.upload(uploadOpts, uploadUrl, { "x-goog-content-length-range": "0,104857600", }); + return uploadUrl; } -async function uploadSourceV2(context: args.Context, region: string): Promise { - const res = await gcfv2.generateUploadUrl(context.projectId, region); +async function uploadSourceV2( + projectId: string, + source: args.Source, + wantBackend: backend.Backend +): Promise { + const v2Endpoints = backend.allEndpoints(wantBackend).filter((e) => e.platform === "gcfv2"); + if (v2Endpoints.length === 0) { + return; + } + const region = v2Endpoints[0].region; // Just pick a region to upload the source. + const res = await gcfv2.generateUploadUrl(projectId, region); const uploadOpts = { - file: context.source!.functionsSourceV2!, - stream: fs.createReadStream(context.source!.functionsSourceV2!), + file: source.functionsSourceV2!, + stream: fs.createReadStream(source.functionsSourceV2!), }; await gcs.upload(uploadOpts, res.uploadUrl); - context.source!.storage = res.storageSource; + return res.storageSource; +} + +async function uploadCodebase( + context: args.Context, + codebase: string, + wantBackend: backend.Backend +): Promise { + const source = context.sources?.[codebase]; + if (!source || (!source.functionsSourceV1 && !source.functionsSourceV2)) { + return; + } + + const uploads: Promise[] = []; + try { + uploads.push(uploadSourceV1(context.projectId, source, wantBackend)); + uploads.push(uploadSourceV2(context.projectId, source, wantBackend)); + + const [sourceUrl, storage] = await Promise.all(uploads); + if (sourceUrl) { + source.sourceUrl = sourceUrl as string; + } + if (storage) { + source.storage = storage as gcfv2.StorageSource; + } + + const sourceDir = configForCodebase(context.config!, codebase).source; + if (uploads.length) { + logSuccess( + `${clc.green.bold("functions:")} ${clc.bold(sourceDir)} folder uploaded successfully` + ); + } + } catch (err: any) { + logWarning(clc.yellow("functions:") + " Upload Error: " + err.message); + throw err; + } } /** @@ -50,34 +105,14 @@ export async function deploy( return; } - if (!context.source?.functionsSourceV1 && !context.source?.functionsSourceV2) { + if (!payload.functions) { return; } await checkHttpIam(context, options, payload); - - try { - const want = payload.functions!.wantBackend; - const uploads: Promise[] = []; - - // Choose one of the function region for source upload. - const byPlatform = groupBy(backend.allEndpoints(want), (e) => e.platform); - if (byPlatform.gcfv1?.length > 0) { - uploads.push(uploadSourceV1(context, byPlatform.gcfv1[0].region)); - } - if (byPlatform.gcfv2?.length > 0) { - uploads.push(uploadSourceV2(context, byPlatform.gcfv2[0].region)); - } - await Promise.all(uploads); - - const source = context.config.source; - if (uploads.length) { - logSuccess( - `${clc.green.bold("functions:")} ${clc.bold(source)} folder uploaded successfully` - ); - } - } catch (err: any) { - logWarning(clc.yellow("functions:") + " Upload Error: " + err.message); - throw err; + const uploads: Promise[] = []; + for (const [codebase, { wantBackend }] of Object.entries(payload.functions)) { + uploads.push(uploadCodebase(context, codebase, wantBackend)); } + await Promise.all(uploads); } diff --git a/src/deploy/functions/functionsDeployHelper.ts b/src/deploy/functions/functionsDeployHelper.ts index 23d63178f68..b79a42b65dc 100644 --- a/src/deploy/functions/functionsDeployHelper.ts +++ b/src/deploy/functions/functionsDeployHelper.ts @@ -1,5 +1,5 @@ import * as backend from "./backend"; -import * as projectConfig from "../../functions/projectConfig"; +import { DEFAULT_CODEBASE, ValidatedConfig } from "../../functions/projectConfig"; export interface EndpointFilter { // If codebase is undefined, match all functions in all codebase that matches the idChunks. @@ -69,7 +69,7 @@ export function parseFunctionSelector(selector: string): EndpointFilter[] { // conflict between a codebase name as function id in the default codebase. return [ { codebase: fragments[0] }, - { codebase: projectConfig.DEFAULT_CODEBASE, idChunks: fragments[0].split(/[-.]/) }, + { codebase: DEFAULT_CODEBASE, idChunks: fragments[0].split(/[-.]/) }, ]; } return [ @@ -127,6 +127,80 @@ export function getEndpointFilters(options: { only?: string }): EndpointFilter[] /** * Generate label for a function. */ -export function getFunctionLabel(fn: backend.TargetIds): string { - return `${fn.id}(${fn.region})`; +export function getFunctionLabel(fn: backend.TargetIds & { codebase?: string }): string { + let id = `${fn.id}(${fn.region})`; + if (fn.codebase && fn.codebase !== DEFAULT_CODEBASE) { + id = `[${fn.codebase}]${id}`; + } + return id; +} + +/** + * Returns list of codebases specified in firebase.json filtered by --only filters if present. + */ +export function targetCodebases(config: ValidatedConfig, filters?: EndpointFilter[]): string[] { + const codebasesFromConfig = [...new Set(Object.values(config).map((c) => c.codebase))]; + if (!filters) { + return [...codebasesFromConfig]; + } + + const codebasesFromFilters = [ + ...new Set(filters.map((f) => f.codebase).filter((c) => c !== undefined)), + ]; + + if (codebasesFromFilters.length === 0) { + return [...codebasesFromConfig]; + } + + const intersections: string[] = []; + for (const codebase of codebasesFromConfig) { + if (codebasesFromFilters.includes(codebase)) { + intersections.push(codebase); + } + } + return intersections; +} + +/** + * Assign each endpoint deployed in the project to a codebase. + * + * An endpoint is part a codebase if: + * 1. Endpoint is associated w/ the current codebase (duh). + * 2. Endpoint name matches name of an endoint we want to deploy + * + * Condition (2) might feel wrong but is a practical conflict resolution strategy as it makes migrating a function + * from one codebase to another straightforward. + */ +export function groupEndpointsByCodebase( + wantBackends: Record, + haveEndpoints: backend.Endpoint[] +): Record { + const grouped: Record = {}; + // endpointsToAssign will hold endpoints not assigned to any codebase. + let endpointsToAssign: backend.Endpoint[] = haveEndpoints; + + // First, dole out endpoints using names. If resource name matches, endpoint belongs to that codebase regardless + // of the codebase annotation. + for (const codebase of Object.keys(wantBackends)) { + const names = backend.allEndpoints(wantBackends[codebase]).map((e) => backend.functionName(e)); + grouped[codebase] = backend.of( + ...endpointsToAssign.filter((e) => names.includes(backend.functionName(e))) + ); + // Remove all endpoints we've assigned in this iteration. + endpointsToAssign = endpointsToAssign.filter((e) => !names.includes(backend.functionName(e))); + } + + // Next, dole out endpoints using codebase annotation. + for (const codebase of Object.keys(wantBackends)) { + const matchedEndpoints = endpointsToAssign.filter((e) => e.codebase === codebase); + grouped[codebase] = backend.merge(grouped[codebase], backend.of(...matchedEndpoints)); + // Update current backend, removing all endpoints we've assigned in this iteration. + const matchedNames = matchedEndpoints.map((e) => backend.functionName(e)); + endpointsToAssign = endpointsToAssign.filter((e) => { + return !matchedNames.includes(backend.functionName(e)); + }); + } + // What about unassigned endpoints? We leave them, as it's possible that these endpoints belong to codebases + // defined in other project repositories. + return grouped; } diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 46525ff8bd2..1440729a7b1 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -9,7 +9,12 @@ import * as runtimes from "./runtimes"; import * as validate from "./validate"; import * as ensure from "./ensure"; import { Options } from "../../options"; -import { endpointMatchesAnyFilter, getEndpointFilters } from "./functionsDeployHelper"; +import { + endpointMatchesAnyFilter, + getEndpointFilters, + groupEndpointsByCodebase, + targetCodebases, +} from "./functionsDeployHelper"; import { logLabeledBullet } from "../../utils"; import { getFunctionsConfig, prepareFunctionsUpload } from "./prepareFunctionsUpload"; import { promptForFailurePolicies, promptForMinInstances } from "./prompts"; @@ -19,7 +24,7 @@ import { logger } from "../../logger"; import { ensureTriggerRegions } from "./triggerRegionHelper"; import { ensureServiceAgentRoles } from "./checkIam"; import { FirebaseError } from "../../error"; -import { normalizeAndValidate } from "../../functions/projectConfig"; +import { configForCodebase, normalizeAndValidate } from "../../functions/projectConfig"; import { previews } from "../../previews"; import { AUTH_BLOCKING_EVENTS } from "../../functions/events/v1"; @@ -29,13 +34,6 @@ function hasUserConfig(config: Record): boolean { return Object.keys(config).length > 1; } -function hasDotenv(opts: functionsEnv.UserEnvsOpts): boolean { - return functionsEnv.hasUserEnvs(opts); -} - -/** - * - */ export async function prepare( context: args.Context, options: Options, @@ -44,13 +42,11 @@ export async function prepare( const projectId = needProjectId(options); const projectNumber = await needProjectNumber(options); - context.config = normalizeAndValidate(options.config.src.functions)[0]; + context.config = normalizeAndValidate(options.config.src.functions); context.filters = getEndpointFilters(options); // Parse --only filters for functions. - if ( - context.filters && - !context.filters.map((f) => f.codebase).includes(context.config.codebase) - ) { + const codebases = targetCodebases(context.config, context.filters); + if (codebases.length === 0) { throw new FirebaseError("No function matches given --only filters. Aborting deployment."); } @@ -78,114 +74,120 @@ export async function prepare( } // ===Phase 1. Load codebase from source. - logLabeledBullet( - "functions", - `preparing codebase ${clc.bold(context.config.codebase)} for deployment` - ); - const sourceDirName = context.config.source; - if (!sourceDirName) { - throw new FirebaseError( - `No functions code detected at default location (./functions), and no functions source defined in firebase.json` - ); - } - const sourceDir = options.config.path(sourceDirName); - const delegateContext: runtimes.DelegateContext = { - projectId, - sourceDir, - projectDir: options.config.projectDir, - runtime: context.config.runtime || "", - }; - const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext); - logger.debug(`Validating ${runtimeDelegate.name} source`); - await runtimeDelegate.validate(); - logger.debug(`Building ${runtimeDelegate.name} source`); - await runtimeDelegate.build(); + context.sources = {}; + const codebaseUsesEnvs: string[] = []; + const wantBackends: Record = {}; + for (const codebase of codebases) { + logLabeledBullet("functions", `preparing codebase ${clc.bold(codebase)} for deployment`); - const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); - const userEnvOpt: functionsEnv.UserEnvsOpts = { - functionsSource: sourceDir, - projectId: projectId, - projectAlias: options.projectAlias, - }; - const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt); - const usedDotenv = hasDotenv(userEnvOpt); - const tag = hasUserConfig(runtimeConfig) - ? usedDotenv - ? "mixed" - : "runtime_config" - : usedDotenv - ? "dotenv" - : "none"; - void track("functions_codebase_deploy_env_method", tag); + const config = configForCodebase(context.config, codebase); + const sourceDirName = config.source; + if (!sourceDirName) { + throw new FirebaseError( + `No functions code detected at default location (./functions), and no functions source defined in firebase.json` + ); + } + const sourceDir = options.config.path(sourceDirName); + const delegateContext: runtimes.DelegateContext = { + projectId, + sourceDir, + projectDir: options.config.projectDir, + runtime: config.runtime || "", + }; + const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext); + logger.debug(`Validating ${runtimeDelegate.name} source`); + await runtimeDelegate.validate(); + logger.debug(`Building ${runtimeDelegate.name} source`); + await runtimeDelegate.build(); - logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`); - const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs); - wantBackend.environmentVariables = { ...userEnvs, ...firebaseEnvs }; - for (const endpoint of backend.allEndpoints(wantBackend)) { - endpoint.environmentVariables = wantBackend.environmentVariables; - endpoint.codebase = context.config.codebase; + const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); + const userEnvOpt: functionsEnv.UserEnvsOpts = { + functionsSource: sourceDir, + projectId: projectId, + projectAlias: options.projectAlias, + }; + const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt); + logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`); + const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs); + wantBackend.environmentVariables = { ...userEnvs, ...firebaseEnvs }; + for (const endpoint of backend.allEndpoints(wantBackend)) { + endpoint.environmentVariables = wantBackend.environmentVariables; + endpoint.codebase = codebase; + } + wantBackends[codebase] = wantBackend; + if (functionsEnv.hasUserEnvs(userEnvOpt)) { + codebaseUsesEnvs.push(codebase); + } } + // ===Phase 1.5. Before proceeding further, let's make sure that we don't have conflicting function names. + validate.endpointsAreUnique(wantBackends); + // ===Phase 2. Prepare source for upload. - const source: args.Source = {}; - if (backend.someEndpoint(wantBackend, () => true)) { - logLabeledBullet( - "functions", - `preparing ${clc.bold(sourceDirName)} directory for uploading...` - ); - } - if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { - if (!previews.functionsv2) { - throw new FirebaseError( - "This version of firebase-tools does not support Google Cloud " + - "Functions gen 2\n" + - "If Cloud Functions for Firebase gen 2 is still in alpha, sign up " + - "for the alpha program at " + - "https://services.google.com/fb/forms/firebasealphaprogram/\n" + - "If Cloud Functions for Firebase gen 2 is in beta, get the latest " + - "version of Firebse Tools with `npm i -g firebase-tools@latest`" + for (const [codebase, wantBackend] of Object.entries(wantBackends)) { + const config = configForCodebase(context.config, codebase); + const sourceDirName = config.source; + const sourceDir = options.config.path(sourceDirName); + const source: args.Source = {}; + if (backend.someEndpoint(wantBackend, () => true)) { + logLabeledBullet( + "functions", + `preparing ${clc.bold(sourceDirName)} directory for uploading...` ); } - source.functionsSourceV2 = await prepareFunctionsUpload(sourceDir, context.config); - } - if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) { - source.functionsSourceV1 = await prepareFunctionsUpload( - sourceDir, - context.config, - runtimeConfig - ); + if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { + if (!previews.functionsv2) { + throw new FirebaseError( + "This version of firebase-tools does not support Google Cloud " + + "Functions gen 2\n" + + "If Cloud Functions for Firebase gen 2 is still in alpha, sign up " + + "for the alpha program at " + + "https://services.google.com/fb/forms/firebasealphaprogram/\n" + + "If Cloud Functions for Firebase gen 2 is in beta, get the latest " + + "version of Firebse Tools with `npm i -g firebase-tools@latest`" + ); + } + source.functionsSourceV2 = await prepareFunctionsUpload(sourceDir, config); + } + if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) { + source.functionsSourceV1 = await prepareFunctionsUpload(sourceDir, config, runtimeConfig); + } + context.sources[codebase] = source; } - context.source = source; // ===Phase 3. Fill in details and validate endpoints. We run the check for ALL endpoints - we think it's useful for // validations to fail even for endpoints that aren't being deployed so any errors are caught early. - - // Load all existing endpoints for the project, then filter out functions from other codebases. - // - // An endpoint is part a codebase if: - // 1. Endpoint is associated w/ the current codebase (duh). - // 2. Endpoint name matches name of an endpoint we want to deploy - // - // Condition (2) might feel wrong but is a practical conflict resolution strategy. It allows user to "claim" an - // endpoint for current codebase without much hassel. - const wantEndpointNames = backend.allEndpoints(wantBackend).map((e) => backend.functionName(e)); - const haveBackend = backend.matchingBackend( - await backend.existingBackend(context), - (endpoint) => { - if (endpoint.codebase === context.config?.codebase) { - return true; - } - return wantEndpointNames.includes(backend.functionName(endpoint)); - } + payload.functions = {}; + const haveBackends = groupEndpointsByCodebase( + wantBackends, + backend.allEndpoints(await backend.existingBackend(context)) ); - inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv); - await ensureTriggerRegions(wantBackend); - validate.endpointsAreValid(wantBackend); - inferBlockingDetails(wantBackend); + for (const [codebase, wantBackend] of Object.entries(wantBackends)) { + const haveBackend = haveBackends[codebase] || { ...backend.empty() }; + payload.functions[codebase] = { wantBackend, haveBackend }; + } + for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) { + inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase)); + await ensureTriggerRegions(wantBackend); + validate.endpointsAreValid(wantBackend); + inferBlockingDetails(wantBackend); + } + + const tag = hasUserConfig(runtimeConfig) + ? codebaseUsesEnvs.length > 0 + ? "mixed" + : "runtime_config" + : codebaseUsesEnvs.length > 0 + ? "dotenv" + : "none"; + void track("functions_codebase_deploy_env_method", tag); - payload.functions = { wantBackend: wantBackend, haveBackend: haveBackend }; + const codebaseCnt = Object.keys(payload.functions).length; + void track("functions_codebase_deploy_count", codebaseCnt >= 5 ? "5+" : codebaseCnt.toString()); - // ===Phase 4. Enable APIs required by the deploying backend. + // ===Phase 4. Enable APIs required by the deploying backends. + const wantBackend = backend.merge(...Object.values(wantBackends)); + const haveBackend = backend.merge(...Object.values(haveBackends)); // Enable required APIs. This may come implicitly from triggers (e.g. scheduled triggers // require cloudscheudler and, in v1, require pub/sub), or can eventually come from @@ -245,8 +247,7 @@ export function inferDetailsFromExisting( } // By default, preserve existing environment variables. - // Only overwrite environment variables when the dotenv preview is enabled - // AND there are user specified environment variables. + // Only overwrite environment variables when there are user specified environment variables. if (!usedDotenv) { wantE.environmentVariables = { ...haveE.environmentVariables, diff --git a/src/deploy/functions/prepareFunctionsUpload.ts b/src/deploy/functions/prepareFunctionsUpload.ts index 85b762581f3..4e141cd2cfc 100644 --- a/src/deploy/functions/prepareFunctionsUpload.ts +++ b/src/deploy/functions/prepareFunctionsUpload.ts @@ -12,7 +12,6 @@ import * as backend from "./backend"; import * as functionsConfig from "../../functionsConfig"; import * as utils from "../../utils"; import * as fsAsync from "../../fsAsync"; -import * as args from "./args"; import * as projectConfig from "../../functions/projectConfig"; const CONFIG_DEST_FILE = ".runtimeconfig.json"; diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 8059e0c7529..310d08d70f4 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -8,6 +8,7 @@ import { assertExhaustive } from "../../../functional"; import { getHumanFriendlyRuntimeName } from "../runtimes"; import { functionsOrigin, functionsV2Origin } from "../../../api"; import { logger } from "../../../logger"; +import * as args from "../args"; import * as backend from "../backend"; import * as cloudtasks from "../../../gcp/cloudtasks"; import * as deploymentTool from "../../../deploymentTool"; @@ -45,12 +46,7 @@ export interface FabricatorArgs { executor: Executor; functionExecutor: Executor; appEngineLocation: string; - - // Required if creating or updating any GCFv1 functions - sourceUrl?: string; - - // Required if creating or updating any GCFv2 functions - storage?: gcfV2.StorageSource; + sources: Record; } const rethrowAs = @@ -64,15 +60,13 @@ const rethrowAs = export class Fabricator { executor: Executor; functionExecutor: Executor; - sourceUrl?: string; - storage?: gcfV2.StorageSource; + sources: Record; appEngineLocation: string; constructor(args: FabricatorArgs) { this.executor = args.executor; this.functionExecutor = args.functionExecutor; - this.sourceUrl = args.sourceUrl; - this.storage = args.storage; + this.sources = args.sources; this.appEngineLocation = args.appEngineLocation; } @@ -199,11 +193,12 @@ export class Fabricator { } async createV1Function(endpoint: backend.Endpoint, scraper: SourceTokenScraper): Promise { - if (!this.sourceUrl) { + const sourceUrl = this.sources[endpoint.codebase!]?.sourceUrl; + if (!sourceUrl) { logger.debug("Precondition failed. Cannot create a GCF function without sourceUrl"); throw new Error("Precondition failed"); } - const apiFunction = gcf.functionFromEndpoint(endpoint, this.sourceUrl); + const apiFunction = gcf.functionFromEndpoint(endpoint, sourceUrl); // As a general security practice and way to smooth out the upgrade path // for GCF gen 2, we are enforcing that all new GCFv1 deploys will require // HTTPS @@ -216,7 +211,7 @@ export class Fabricator { const op: { name: string } = await gcf.createFunction(apiFunction); return poller.pollOperation({ ...gcfV1PollerOptions, - pollerName: `create-${endpoint.region}-${endpoint.id}`, + pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller, }); @@ -265,11 +260,12 @@ export class Fabricator { } async createV2Function(endpoint: backend.Endpoint): Promise { - if (!this.storage) { + const storage = this.sources[endpoint.codebase!]?.storage; + if (!storage) { logger.debug("Precondition failed. Cannot create a GCFv2 function without storage"); throw new Error("Precondition failed"); } - const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage); + const apiFunction = gcfV2.functionFromEndpoint(endpoint, storage); // N.B. As of GCFv2 private preview GCF no longer creates Pub/Sub topics // for Pub/Sub event handlers. This may change, at which point this code @@ -299,7 +295,7 @@ export class Fabricator { const op: { name: string } = await gcfV2.createFunction(apiFunction); return await poller.pollOperation({ ...gcfV2PollerOptions, - pollerName: `create-${endpoint.region}-${endpoint.id}`, + pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, }); }) @@ -351,18 +347,19 @@ export class Fabricator { } async updateV1Function(endpoint: backend.Endpoint, scraper: SourceTokenScraper): Promise { - if (!this.sourceUrl) { + const sourceUrl = this.sources[endpoint.codebase!]?.sourceUrl; + if (!sourceUrl) { logger.debug("Precondition failed. Cannot update a GCF function without sourceUrl"); throw new Error("Precondition failed"); } - const apiFunction = gcf.functionFromEndpoint(endpoint, this.sourceUrl); + const apiFunction = gcf.functionFromEndpoint(endpoint, sourceUrl); apiFunction.sourceToken = await scraper.tokenPromise(); const resultFunction = await this.functionExecutor .run(async () => { const op: { name: string } = await gcf.updateFunction(apiFunction); return await poller.pollOperation({ ...gcfV1PollerOptions, - pollerName: `update-${endpoint.region}-${endpoint.id}`, + pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller, }); @@ -389,11 +386,12 @@ export class Fabricator { } async updateV2Function(endpoint: backend.Endpoint): Promise { - if (!this.storage) { + const storage = this.sources[endpoint.codebase!]?.storage; + if (!storage) { logger.debug("Precondition failed. Cannot update a GCFv2 function without storage"); throw new Error("Precondition failed"); } - const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage); + const apiFunction = gcfV2.functionFromEndpoint(endpoint, storage); // N.B. As of GCFv2 private preview the API chokes on any update call that // includes the pub/sub topic even if that topic is unchanged. @@ -408,7 +406,7 @@ export class Fabricator { const op: { name: string } = await gcfV2.updateFunction(apiFunction); return await poller.pollOperation({ ...gcfV2PollerOptions, - pollerName: `update-${endpoint.region}-${endpoint.id}`, + pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, }); }) @@ -445,7 +443,7 @@ export class Fabricator { const op: { name: string } = await gcf.deleteFunction(fnName); const pollerOptions = { ...gcfV1PollerOptions, - pollerName: `delete-${endpoint.region}-${endpoint.id}`, + pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, }; await poller.pollOperation(pollerOptions); @@ -460,7 +458,7 @@ export class Fabricator { const op: { name: string } = await gcfV2.deleteFunction(fnName); const pollerOptions = { ...gcfV2PollerOptions, - pollerName: `delete-${endpoint.region}-${endpoint.id}`, + pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, }; await poller.pollOperation(pollerOptions); @@ -600,9 +598,7 @@ export class Fabricator { logOpStart(op: string, endpoint: backend.Endpoint): void { const runtime = getHumanFriendlyRuntimeName(endpoint.runtime); const label = helper.getFunctionLabel(endpoint); - utils.logBullet( - `${clc.bold.cyan("functions:")} ${op} ${runtime} function ${clc.bold(label)}...` - ); + utils.logLabeledBullet("functions", `${op} ${runtime} function ${clc.bold(label)}...`); } logOpSuccess(op: string, endpoint: backend.Endpoint): void { diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index 1890350ce00..1518a499873 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -30,9 +30,22 @@ export async function release( if (!payload.functions) { return; } + if (!context.sources) { + return; + } - const { wantBackend, haveBackend } = payload.functions!; - const plan = planner.createDeploymentPlan(wantBackend, haveBackend, context.filters); + let plan: planner.DeploymentPlan = {}; + for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) { + plan = { + ...plan, + ...planner.createDeploymentPlan({ + codebase, + wantBackend, + haveBackend, + filters: context.filters, + }), + }; + } const fnsToDelete = Object.values(plan) .map((regionalChanges) => regionalChanges.endpointsToDelete) @@ -58,8 +71,7 @@ export async function release( const fab = new fabricator.Fabricator({ functionExecutor, executor: new executor.QueueExecutor({}), - sourceUrl: context.source!.sourceUrl!, - storage: context.source!.storage!, + sources: context.sources, appEngineLocation: getAppEngineLocation(context.firebaseConfig), }); @@ -72,9 +84,10 @@ export async function release( // uri field. createDeploymentPlan copies endpoints by reference. Both of these // subtleties are so we can take out a round trip API call to get the latest // trigger URLs by calling existingBackend again. - printTriggerUrls(payload.functions!.wantBackend); + const wantBackend = backend.merge(...Object.values(payload.functions).map((p) => p.wantBackend)); + printTriggerUrls(wantBackend); - const haveEndpoints = backend.allEndpoints(payload.functions!.wantBackend); + const haveEndpoints = backend.allEndpoints(wantBackend); const deletedEndpoints = Object.values(plan) .map((r) => r.endpointsToDelete) .reduce(reduceFlat, []); diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index 541be7578d7..abca336941b 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -5,7 +5,6 @@ import { } from "../functionsDeployHelper"; import { isFirebaseManaged } from "../../../deploymentTool"; import { FirebaseError } from "../../../error"; -import * as args from "../args"; import * as utils from "../../../utils"; import * as backend from "../backend"; import * as v2events from "../../../functions/events/v2"; @@ -23,6 +22,14 @@ export interface Changeset { export type DeploymentPlan = Record; +export interface PlanArgs { + wantBackend: backend.Backend; // the desired state + haveBackend: backend.Backend; // the current state + codebase: string; // target codebase of the deployment + filters?: EndpointFilter[]; // filters to apply to backend, passed from users by --only flag + deleteAll?: boolean; // deletes all functions if set +} + /** Calculate the changesets of given endpoints by grouping endpoints with keyFn. */ export function calculateChangesets( want: Record, @@ -92,37 +99,33 @@ export function calculateUpdate(want: backend.Endpoint, have: backend.Endpoint): /** * Create a plan for deploying all functions in one region. - * @param want the desired state - * @param have the current state - * @param filters filters to apply to backend, passed from users by --only flag. - * @param deleteAll Deletes all functions if set. */ -export function createDeploymentPlan( - want: backend.Backend, - have: backend.Backend, - filters?: EndpointFilter[], - deleteAll?: boolean -): DeploymentPlan { +export function createDeploymentPlan(args: PlanArgs): DeploymentPlan { + let { wantBackend, haveBackend, codebase, filters, deleteAll } = args; let deployment: DeploymentPlan = {}; - want = backend.matchingBackend(want, (endpoint) => { + wantBackend = backend.matchingBackend(wantBackend, (endpoint) => { return endpointMatchesAnyFilter(endpoint, filters); }); - have = backend.matchingBackend(have, (endpoint) => { - return endpointMatchesAnyFilter(endpoint, filters); + const wantedEndpoint = backend.hasEndpoint(wantBackend); + haveBackend = backend.matchingBackend(haveBackend, (endpoint) => { + return wantedEndpoint(endpoint) || endpointMatchesAnyFilter(endpoint, filters); }); - const regions = new Set([...Object.keys(want.endpoints), ...Object.keys(have.endpoints)]); + const regions = new Set([ + ...Object.keys(wantBackend.endpoints), + ...Object.keys(haveBackend.endpoints), + ]); for (const region of regions) { const changesets = calculateChangesets( - want.endpoints[region] || {}, - have.endpoints[region] || {}, - (e) => `${e.region}-${e.availableMemoryMb || "default"}`, + wantBackend.endpoints[region] || {}, + haveBackend.endpoints[region] || {}, + (e) => `${codebase}-${e.region}-${e.availableMemoryMb || "default"}`, deleteAll ); deployment = { ...deployment, ...changesets }; } - if (upgradedToGCFv2WithoutSettingConcurrency(want, have)) { + if (upgradedToGCFv2WithoutSettingConcurrency(wantBackend, haveBackend)) { utils.logLabeledBullet( "functions", "You are updating one or more functions to Google Cloud Functions v2, " + diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index d7481a62bfe..5040d679fe8 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -46,6 +46,36 @@ export function endpointsAreValid(wantBackend: backend.Backend): void { } } +/** Validate that all endpoints in the given set of backends are unique */ +export function endpointsAreUnique(backends: Record): void { + const endpointToCodebases: Record> = {}; // function name -> codebases + + for (const [codebase, b] of Object.entries(backends)) { + for (const endpoint of backend.allEndpoints(b)) { + const key = backend.functionName(endpoint); + const cs = endpointToCodebases[key] || new Set(); + cs.add(codebase); + endpointToCodebases[key] = cs; + } + } + + const conflicts: Record = {}; + for (const [fn, codebases] of Object.entries(endpointToCodebases)) { + if (codebases.size > 1) { + conflicts[fn] = Array.from(codebases); + } + } + + if (Object.keys(conflicts).length === 0) { + return; + } + + const msgs = Object.entries(conflicts).map(([fn, codebases]) => `${fn}: ${codebases.join(",")}`); + throw new FirebaseError( + "More than one codebase claims following functions:\n\t" + `${msgs.join("\n\t")}` + ); +} + /** * Check that functions directory exists. * @param sourceDir Absolute path to source directory. diff --git a/src/deploy/hosting/convertConfig.ts b/src/deploy/hosting/convertConfig.ts index 4fc0c6ef782..87513f90d3c 100644 --- a/src/deploy/hosting/convertConfig.ts +++ b/src/deploy/hosting/convertConfig.ts @@ -58,8 +58,10 @@ export async function convertConfig( } const endpointBeingDeployed = (serviceId: string, region: string = "us-central1") => { - const endpoint = payload.functions?.wantBackend?.endpoints[region]?.[serviceId]; - if (endpoint && isHttpsTriggered(endpoint) && endpoint.platform === "gcfv2") return endpoint; + for (const { wantBackend } of Object.values(payload.functions || {})) { + const endpoint = wantBackend?.endpoints[region]?.[serviceId]; + if (endpoint && isHttpsTriggered(endpoint) && endpoint.platform === "gcfv2") return endpoint; + } return undefined; }; diff --git a/src/functions/projectConfig.ts b/src/functions/projectConfig.ts index 1c85b128860..7814dae6712 100644 --- a/src/functions/projectConfig.ts +++ b/src/functions/projectConfig.ts @@ -3,7 +3,7 @@ import { FirebaseError } from "../error"; export type NormalizedConfig = [FunctionConfig, ...FunctionConfig[]]; export type ValidatedSingle = FunctionConfig & { source: string; codebase: string }; -export type ValidatedConfig = [ValidatedSingle]; +export type ValidatedConfig = [ValidatedSingle, ...ValidatedSingle[]]; export const DEFAULT_CODEBASE = "default"; @@ -59,13 +59,10 @@ function assertUnique(config: ValidatedConfig, property: keyof ValidatedSingle) * Validate functions config. */ export function validate(config: NormalizedConfig): ValidatedConfig { - if (config.length > 1) { - throw new FirebaseError("More than one functions.source detected in firebase.json."); - } - const validated = validateSingle(config[0]); - assertUnique([validated], "source"); - assertUnique([validated], "codebase"); - return [validated]; + const validated = config.map((cfg) => validateSingle(cfg)) as ValidatedConfig; + assertUnique(validated, "source"); + assertUnique(validated, "codebase"); + return validated; } /** @@ -76,3 +73,14 @@ export function validate(config: NormalizedConfig): ValidatedConfig { export function normalizeAndValidate(config?: FunctionsConfig): ValidatedConfig { return validate(normalize(config)); } + +/** + * Return functions config for given codebase. + */ +export function configForCodebase(config: ValidatedConfig, codebase: string): ValidatedSingle { + const codebaseCfg = config.find((c) => c.codebase === codebase); + if (!codebaseCfg) { + throw new FirebaseError(`No functions config found for codebase ${codebase}`); + } + return codebaseCfg; +} diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index a17eda8da8a..f26c316de7c 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -673,9 +673,14 @@ export function functionFromEndpoint( "egressSettings" ); } - gcfFunction.labels = { - ...gcfFunction.labels, - [CODEBASE_LABEL]: endpoint.codebase || projectConfig.DEFAULT_CODEBASE, - }; + const codebase = endpoint.codebase || projectConfig.DEFAULT_CODEBASE; + if (codebase !== projectConfig.DEFAULT_CODEBASE) { + gcfFunction.labels = { + ...gcfFunction.labels, + [CODEBASE_LABEL]: codebase, + }; + } else { + delete gcfFunction.labels?.[CODEBASE_LABEL]; + } return gcfFunction; } diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 984dd42b16a..5245844d778 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -495,16 +495,18 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage ], }; } - gcfFunction.labels = { - ...gcfFunction.labels, - [CODEBASE_LABEL]: endpoint.codebase || projectConfig.DEFAULT_CODEBASE, - }; + const codebase = endpoint.codebase || projectConfig.DEFAULT_CODEBASE; + if (codebase !== projectConfig.DEFAULT_CODEBASE) { + gcfFunction.labels = { + ...gcfFunction.labels, + [CODEBASE_LABEL]: codebase, + }; + } else { + delete gcfFunction.labels?.[CODEBASE_LABEL]; + } return gcfFunction; } -/** - * - */ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoint { const [, project, , region, , id] = gcfFunction.name.split("/"); let trigger: backend.Triggered; diff --git a/src/test/deploy/functions/backend.spec.ts b/src/test/deploy/functions/backend.spec.ts index dd990007e7c..0c55230f2f9 100644 --- a/src/test/deploy/functions/backend.spec.ts +++ b/src/test/deploy/functions/backend.spec.ts @@ -85,6 +85,35 @@ describe("Backend", () => { "projects/project/locations/region/functions/id" ); }); + + it("merge", () => { + const BASE_ENDPOINT = { ...ENDPOINT, httpsTrigger: {} }; + const e1 = { ...BASE_ENDPOINT, id: "1" }; + const e21 = { ...BASE_ENDPOINT, id: "2.1" }; + const e22 = { ...BASE_ENDPOINT, id: "2.2" }; + const e3 = { ...BASE_ENDPOINT, id: "3" }; + + const b1 = backend.of(e1); + b1.environmentVariables = { foo: "bar" }; + b1.requiredAPIs = [ + { reason: "a", api: "a.com" }, + { reason: "b", api: "b.com" }, + ]; + + const b2 = backend.of(e21, e22); + b2.environmentVariables = { bar: "foo" }; + + const b3 = backend.of(e3); + b3.requiredAPIs = [{ reason: "a", api: "a.com" }]; + + const got = backend.merge(b3, b2, b1); + expect(backend.allEndpoints(got)).to.have.deep.members([e1, e21, e22, e3]); + expect(got.environmentVariables).to.deep.equal({ foo: "bar", bar: "foo" }); + expect(got.requiredAPIs).to.have.deep.members([ + { reason: "a", api: "a.com" }, + { reason: "b", api: "b.com" }, + ]); + }); }); describe("existing backend", () => { diff --git a/src/test/deploy/functions/functionsDeployHelper.spec.ts b/src/test/deploy/functions/functionsDeployHelper.spec.ts index 0cebc6c1fd6..7b30e53a485 100644 --- a/src/test/deploy/functions/functionsDeployHelper.spec.ts +++ b/src/test/deploy/functions/functionsDeployHelper.spec.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import * as backend from "../../../deploy/functions/backend"; import * as helper from "../../../deploy/functions/functionsDeployHelper"; import { Options } from "../../../options"; -import { DEFAULT_CODEBASE, ValidatedSingle } from "../../../functions/projectConfig"; +import { DEFAULT_CODEBASE, ValidatedConfig } from "../../../functions/projectConfig"; import { EndpointFilter, parseFunctionSelector, @@ -319,4 +319,120 @@ describe("functionsDeployHelper", () => { expect(helper.getEndpointFilters({ only: "hosting:siteA,storage:bucketB" })).to.be.undefined; }); }); + + describe("targetCodebases", () => { + const config: ValidatedConfig = [ + { + source: "foo", + codebase: "default", + }, + { + source: "bar", + codebase: "foobar", + }, + ]; + + it("returns all codebases in firebase.json with empty filters", () => { + expect(helper.targetCodebases(config)).to.have.members(["default", "foobar"]); + }); + + it("returns only codebases included in the filters", () => { + const filters: EndpointFilter[] = [ + { + codebase: "default", + }, + ]; + expect(helper.targetCodebases(config, filters)).to.have.members(["default"]); + }); + + it("correctly deals with duplicate entries", () => { + const filters: EndpointFilter[] = [ + { + codebase: "default", + }, + { + codebase: "default", + }, + ]; + expect(helper.targetCodebases(config, filters)).to.have.members(["default"]); + }); + + it("returns all codebases given filter without codebase specified", () => { + const filters: EndpointFilter[] = [ + { + idChunks: ["foo", "bar"], + }, + ]; + expect(helper.targetCodebases(config, filters)).to.have.members(["default", "foobar"]); + }); + }); + + describe("groupEndpointsByCodebase", () => { + function endpointsOf(b: backend.Backend): string[] { + return backend.allEndpoints(b).map((e) => backend.functionName(e)); + } + + it("groups codebase using codebase property", () => { + const wantBackends: Record = { + default: backend.of( + { ...ENDPOINT, id: "default-0", codebase: "default" }, + { ...ENDPOINT, id: "default-1", codebase: "default" } + ), + cb: backend.of( + { ...ENDPOINT, id: "cb-0", codebase: "cb" }, + { ...ENDPOINT, id: "cb-1", codebase: "cb" } + ), + }; + const haveBackend = backend.of( + { ...ENDPOINT, id: "default-0", codebase: "default" }, + { ...ENDPOINT, id: "default-1", codebase: "default" }, + { ...ENDPOINT, id: "cb-0", codebase: "cb" }, + { ...ENDPOINT, id: "cb-1", codebase: "cb" }, + { ...ENDPOINT, id: "orphan", codebase: "orphan" } + ); + + const got = helper.groupEndpointsByCodebase(wantBackends, backend.allEndpoints(haveBackend)); + for (const codebase of Object.keys(got)) { + expect(endpointsOf(got[codebase])).to.have.members(endpointsOf(wantBackends[codebase])); + } + }); + + it("claims endpoint with matching name regardless of codebase property", () => { + const wantBackends: Record = { + default: backend.of( + { ...ENDPOINT, id: "default-0", codebase: "default" }, + { ...ENDPOINT, id: "default-1", codebase: "default" } + ), + cb: backend.of( + { ...ENDPOINT, id: "cb-0", codebase: "cb" }, + { ...ENDPOINT, id: "cb-1", codebase: "cb" } + ), + }; + let haveBackend = backend.of( + { ...ENDPOINT, id: "default-0", codebase: "cb" }, + { ...ENDPOINT, id: "default-1", codebase: "cb" }, + { ...ENDPOINT, id: "cb-0", codebase: "cb" }, + { ...ENDPOINT, id: "cb-1", codebase: "cb" }, + { ...ENDPOINT, id: "orphan", codebase: "orphan" } + ); + + let got = helper.groupEndpointsByCodebase(wantBackends, backend.allEndpoints(haveBackend)); + for (const codebase of Object.keys(got)) { + expect(endpointsOf(got[codebase])).to.have.members(endpointsOf(wantBackends[codebase])); + } + + // Do it again, this time labeling with default codebase to make sure that arbitrary ordering does not matter. + haveBackend = backend.of( + { ...ENDPOINT, id: "default-0", codebase: "default" }, + { ...ENDPOINT, id: "default-1", codebase: "default" }, + { ...ENDPOINT, id: "cb-0", codebase: "default" }, + { ...ENDPOINT, id: "cb-1", codebase: "default" }, + { ...ENDPOINT, id: "orphan", codebase: "orphan" } + ); + got = helper.groupEndpointsByCodebase(wantBackends, backend.allEndpoints(haveBackend)); + for (const codebase of Object.keys(got)) { + expect(endpointsOf(got[codebase])).to.have.members(endpointsOf(wantBackends[codebase])); + } + }); + }); }); diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 0c373698024..3ba8892b477 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -96,8 +96,12 @@ describe("Fabricator", () => { const ctorArgs: fabricator.FabricatorArgs = { executor: new executor.InlineExecutor(), functionExecutor: new executor.InlineExecutor(), - sourceUrl: "https://example.com", - storage: storage, + sources: { + default: { + sourceUrl: "https://example.com", + storage: storage, + }, + }, appEngineLocation: "us-central1", }; let fab: fabricator.Fabricator; @@ -119,6 +123,7 @@ describe("Fabricator", () => { region: "us-central1", entryPoint: "entrypoint", runtime: "nodejs16", + codebase: "default", ...JSON.parse(JSON.stringify(base)), ...trigger, } as backend.Endpoint; diff --git a/src/test/deploy/functions/release/planner.spec.ts b/src/test/deploy/functions/release/planner.spec.ts index 11d6c0cc595..d3e05c0858f 100644 --- a/src/test/deploy/functions/release/planner.spec.ts +++ b/src/test/deploy/functions/release/planner.spec.ts @@ -6,7 +6,6 @@ import * as planner from "../../../../deploy/functions/release/planner"; import * as deploymentTool from "../../../../deploymentTool"; import * as utils from "../../../../utils"; import * as v2events from "../../../../functions/events/v2"; -import * as projectConfig from "../../../../functions/projectConfig"; describe("planner", () => { let logLabeledBullet: sinon.SinonStub; @@ -175,6 +174,8 @@ describe("planner", () => { }); describe("createDeploymentPlan", () => { + const codebase = "default"; + it("groups deployment by region and memory", () => { const region1mem1Created: backend.Endpoint = func("id1", "region1"); const region1mem1Updated: backend.Endpoint = func("id2", "region1"); @@ -186,16 +187,16 @@ describe("planner", () => { region2mem2Deleted.availableMemoryMb = 512; region2mem2Deleted.labels = deploymentTool.labels(); - const have = backend.of(region1mem1Updated, region2mem2Updated, region2mem2Deleted); - const want = backend.of( + const haveBackend = backend.of(region1mem1Updated, region2mem2Updated, region2mem2Deleted); + const wantBackend = backend.of( region1mem1Created, region1mem1Updated, region2mem1Created, region2mem2Updated ); - expect(planner.createDeploymentPlan(want, have)).to.deep.equal({ - "region1-default": { + expect(planner.createDeploymentPlan({ wantBackend, haveBackend, codebase })).to.deep.equal({ + "default-region1-default": { endpointsToCreate: [region1mem1Created], endpointsToUpdate: [ { @@ -204,12 +205,12 @@ describe("planner", () => { ], endpointsToDelete: [], }, - "region2-default": { + "default-region2-default": { endpointsToCreate: [region2mem1Created], endpointsToUpdate: [], endpointsToDelete: [], }, - "region2-512": { + "default-region2-512": { endpointsToCreate: [], endpointsToUpdate: [ { @@ -233,15 +234,18 @@ describe("planner", () => { group1Deleted.labels = deploymentTool.labels(); group2Deleted.labels = deploymentTool.labels(); - const want = backend.of(group1Updated, group1Created, group2Updated, group2Created); - const have = backend.of(group1Updated, group1Deleted, group2Updated, group2Deleted); + const wantBackend = backend.of(group1Updated, group1Created, group2Updated, group2Created); + const haveBackend = backend.of(group1Updated, group1Deleted, group2Updated, group2Deleted); expect( - planner.createDeploymentPlan(want, have, [ - { codebase: projectConfig.DEFAULT_CODEBASE, idChunks: ["g1"] }, - ]) + planner.createDeploymentPlan({ + wantBackend, + haveBackend, + codebase, + filters: [{ codebase, idChunks: ["g1"] }], + }) ).to.deep.equal({ - "region-default": { + "default-region-default": { endpointsToCreate: [group1Created], endpointsToUpdate: [ { @@ -259,11 +263,11 @@ describe("planner", () => { const upgraded: backend.Endpoint = { ...original }; upgraded.platform = "gcfv2"; - const have = backend.of(original); - const want = backend.of(upgraded); + const haveBackend = backend.of(original); + const wantBackend = backend.of(upgraded); allowV2Upgrades(); - planner.createDeploymentPlan(want, have); + planner.createDeploymentPlan({ wantBackend, haveBackend, codebase }); expect(logLabeledBullet).to.have.been.calledOnceWith( "functions", sinon.match(/change this with the 'concurrency' option/) @@ -276,11 +280,19 @@ describe("planner", () => { // should be no warning const v2Function: backend.Endpoint = { ...func("id", "region"), platform: "gcfv2" }; - planner.createDeploymentPlan(backend.of(v2Function), backend.of(v2Function)); + planner.createDeploymentPlan({ + wantBackend: backend.of(v2Function), + haveBackend: backend.of(v2Function), + codebase, + }); expect(logLabeledBullet).to.not.have.been.called; const v1Function: backend.Endpoint = { ...func("id", "region"), platform: "gcfv1" }; - planner.createDeploymentPlan(backend.of(v1Function), backend.of(v1Function)); + planner.createDeploymentPlan({ + wantBackend: backend.of(v1Function), + haveBackend: backend.of(v1Function), + codebase, + }); expect(logLabeledBullet).to.not.have.been.called; // Upgraded but specified concurrency @@ -289,7 +301,11 @@ describe("planner", () => { platform: "gcfv2", concurrency: 80, }; - planner.createDeploymentPlan(backend.of(concurrencyUpgraded), backend.of(v1Function)); + planner.createDeploymentPlan({ + wantBackend: backend.of(concurrencyUpgraded), + haveBackend: backend.of(v1Function), + codebase, + }); expect(logLabeledBullet).to.not.have.been.called; }); }); diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index 01cb8572263..516e259023c 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -296,6 +296,69 @@ describe("validate", () => { }); }); + describe("endpointsAreUnqiue", () => { + const ENDPOINT_BASE: backend.Endpoint = { + platform: "gcfv2", + id: "id", + region: "us-east1", + project: "project", + entryPoint: "func", + runtime: "nodejs16", + httpsTrigger: {}, + }; + + it("passes given unqiue ids", () => { + const b1 = backend.of( + { ...ENDPOINT_BASE, id: "i1", region: "r1" }, + { ...ENDPOINT_BASE, id: "i2", region: "r1" } + ); + const b2 = backend.of( + { ...ENDPOINT_BASE, id: "i3", region: "r2" }, + { ...ENDPOINT_BASE, id: "i4", region: "r2" } + ); + expect(() => validate.endpointsAreUnique({ b1, b2 })).to.not.throw; + }); + + it("passes given unique id, region pairs", () => { + const b1 = backend.of( + { ...ENDPOINT_BASE, id: "i1", region: "r1" }, + { ...ENDPOINT_BASE, id: "i2", region: "r1" } + ); + const b2 = backend.of( + { ...ENDPOINT_BASE, id: "i1", region: "r2" }, + { ...ENDPOINT_BASE, id: "i2", region: "r2" } + ); + expect(() => validate.endpointsAreUnique({ b1, b2 })).to.not.throw; + }); + + it("throws given non-unique id region pairs", () => { + const b1 = backend.of({ ...ENDPOINT_BASE, id: "i1", region: "r1" }); + const b2 = backend.of({ ...ENDPOINT_BASE, id: "i1", region: "r1" }); + expect(() => validate.endpointsAreUnique({ b1, b2 })).to.throw( + /projects\/project\/locations\/r1\/functions\/i1: b1,b2/ + ); + }); + + it("throws given non-unique id region pairs across all codebases", () => { + const b1 = backend.of({ ...ENDPOINT_BASE, id: "i1", region: "r1" }); + const b2 = backend.of({ ...ENDPOINT_BASE, id: "i1", region: "r1" }); + const b3 = backend.of({ ...ENDPOINT_BASE, id: "i1", region: "r1" }); + expect(() => validate.endpointsAreUnique({ b1, b2, b3 })).to.throw( + /projects\/project\/locations\/r1\/functions\/i1: b1,b2,b3/ + ); + }); + + it("throws given multiple conflicts", () => { + const b1 = backend.of( + { ...ENDPOINT_BASE, id: "i1", region: "r1" }, + { ...ENDPOINT_BASE, id: "i2", region: "r2" } + ); + const b2 = backend.of({ ...ENDPOINT_BASE, id: "i1", region: "r1" }); + const b3 = backend.of({ ...ENDPOINT_BASE, id: "i2", region: "r2" }); + expect(() => validate.endpointsAreUnique({ b1, b2, b3 })).to.throw(/b1,b2.*b1,b3/s); + }); + }); + describe("secretsAreValid", () => { const project = "project"; diff --git a/src/test/deploy/hosting/convertConfig.spec.ts b/src/test/deploy/hosting/convertConfig.spec.ts index 8ad16275447..d54d1e3f70e 100644 --- a/src/test/deploy/hosting/convertConfig.spec.ts +++ b/src/test/deploy/hosting/convertConfig.spec.ts @@ -1,7 +1,8 @@ import { expect } from "chai"; import { HostingConfig } from "../../../firebaseConfig"; - import { convertConfig } from "../../../deploy/hosting/convertConfig"; +import * as args from "../../../deploy/functions/args"; +import * as backend from "../../../deploy/functions/backend"; const DEFAULT_CONTEXT = { loadedExistingBackend: true, @@ -17,7 +18,7 @@ describe("convertConfig", () => { name: string; input: HostingConfig | undefined; want: any; - payload?: any; + payload?: args.Payload; finalize?: boolean; context?: any; }> = [ @@ -57,17 +58,17 @@ describe("convertConfig", () => { input: { rewrites: [{ regex: "/foo$", function: "foofn", region: "us-central1" }] }, payload: { functions: { - wantBackend: { - endpoints: { - "us-central1": { - foofn: { - id: "foofn", - region: "us-central1", - platform: "gcfv2", - httpsTrigger: true, - }, - }, - }, + default: { + wantBackend: backend.of({ + id: "foofn", + project: "my-project", + entryPoint: "foofn", + runtime: "nodejs14", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), }, }, }, @@ -79,17 +80,17 @@ describe("convertConfig", () => { input: { rewrites: [{ regex: "/foo$", function: "foofn", region: "us-central1" }] }, payload: { functions: { - wantBackend: { - endpoints: { - "us-central1": { - foofn: { - id: "foofn", - region: "us-central1", - platform: "gcfv2", - httpsTrigger: true, - }, - }, - }, + default: { + wantBackend: backend.of({ + id: "foofn", + project: "my-project", + entryPoint: "foofn", + runtime: "nodejs14", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), }, }, }, @@ -144,17 +145,17 @@ describe("convertConfig", () => { want: { rewrites: [] }, payload: { functions: { - wantBackend: { - endpoints: { - "us-central1": { - hello: { - id: "hello", - region: "us-central1", - platform: "gcfv2", - httpsTrigger: true, - }, - }, - }, + default: { + wantBackend: backend.of({ + id: "hello", + project: "my-project", + entryPoint: "hello", + runtime: "nodejs14", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), }, }, }, @@ -166,17 +167,17 @@ describe("convertConfig", () => { want: { rewrites: [{ regex: "/foo$", run: { region: "us-central1", serviceId: "hello" } }] }, payload: { functions: { - wantBackend: { - endpoints: { - "us-central1": { - hello: { - id: "hello", - region: "us-central1", - platform: "gcfv2", - httpsTrigger: true, - }, - }, - }, + default: { + wantBackend: backend.of({ + id: "hello", + project: "my-project", + entryPoint: "hello", + runtime: "nodejs14", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), }, }, }, diff --git a/src/test/functions/projectConfig.spec.ts b/src/test/functions/projectConfig.spec.ts index d871a0eb034..bdd69583e11 100644 --- a/src/test/functions/projectConfig.spec.ts +++ b/src/test/functions/projectConfig.spec.ts @@ -29,13 +29,6 @@ describe("projectConfig", () => { expect(projectConfig.validate([TEST_CONFIG_0])).to.deep.equal([TEST_CONFIG_0]); }); - it("fails validation given more than one config", () => { - expect(() => projectConfig.validate([TEST_CONFIG_0, TEST_CONFIG_1])).to.throw( - FirebaseError, - /More than one functions.source detected/ - ); - }); - it("fails validation given config w/o source", () => { expect(() => projectConfig.validate([{ runtime: "nodejs10" }])).to.throw( FirebaseError, @@ -115,14 +108,17 @@ describe("projectConfig", () => { it("fails validation given config w/ duplicate source", () => { expect(() => projectConfig.normalizeAndValidate([TEST_CONFIG_0, TEST_CONFIG_0])).to.throw( FirebaseError, - /More than one functions.source detected/ + /functions.source must be unique/ ); }); - it("fails validation given more than one config", () => { + it("fails validation given config w/ duplicate codebase", () => { expect(() => - projectConfig.normalizeAndValidate([TEST_CONFIG_0, { ...TEST_CONFIG_0, source: "bar" }]) - ).to.throw(FirebaseError, /More than one functions.source detected/); + projectConfig.normalizeAndValidate([ + { ...TEST_CONFIG_0, codebase: "foo" }, + { ...TEST_CONFIG_0, codebase: "foo", source: "bar" }, + ]) + ).to.throw(FirebaseError, /functions.codebase must be unique/); }); }); }); diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index cf021a69a36..ca6ea473c81 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -22,14 +22,12 @@ describe("cloudfunctions", () => { entryPoint: "function", runtime: "nodejs16", codebase: projectConfig.DEFAULT_CODEBASE, - labels: { [cloudfunctions.CODEBASE_LABEL]: projectConfig.DEFAULT_CODEBASE }, }; const CLOUD_FUNCTION: Omit = { name: "projects/project/locations/region/functions/id", entryPoint: "function", runtime: "nodejs16", - labels: { [cloudfunctions.CODEBASE_LABEL]: projectConfig.DEFAULT_CODEBASE }, }; const HAVE_CLOUD_FUNCTION: cloudfunctions.CloudFunction = { diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 2cd3edd8667..817938d08f1 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -19,7 +19,6 @@ describe("cloudfunctionsv2", () => { entryPoint: "function", runtime: "nodejs16", codebase: projectConfig.DEFAULT_CODEBASE, - labels: { [cloudfunctionsv2.CODEBASE_LABEL]: projectConfig.DEFAULT_CODEBASE }, }; const CLOUD_FUNCTION_V2_SOURCE: cloudfunctionsv2.StorageSource = { @@ -40,7 +39,6 @@ describe("cloudfunctionsv2", () => { environmentVariables: {}, }, serviceConfig: {}, - labels: { [cloudfunctionsv2.CODEBASE_LABEL]: projectConfig.DEFAULT_CODEBASE }, }; const RUN_URI = "https://id-nonce-region-project.run.app"; From e70451c3a738ca3deb57544fc7a4640bb018b689 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 20 Apr 2022 21:32:05 -0700 Subject: [PATCH 0256/1699] Add emulator support for multi codebase functions setup. (#4447) Emulator will now load all functions discoverable in all codebases defined in user's repository. The change ended up being fairly easy to implement, as we simply needed to `for` loop through the array of functions config and setup N emulatable backend (vs 1) for the FunctionsEmulator to load. This required a minor change on how triggers are invoked - the Functions Emulator will now take care of finding the correct emulatable backend associated w/ the trigger on invocation instead of taking it as an argument. I've taken a separate PR to test this new feature of the emulator to make PR reviews slightly less burdensome: https://github.com/firebase/firebase-tools/pull/4448. --- .../emulator-tests/functionsEmulator.spec.ts | 9 +--- .../functionsEmulatorRuntime.spec.ts | 13 ++++-- src/emulator/controller.ts | 31 ++++++------- src/emulator/functionsEmulator.ts | 12 ++++-- src/emulator/functionsEmulatorShared.ts | 6 ++- src/emulator/functionsEmulatorShell.ts | 4 +- src/functionsShellCommandAction.ts | 2 +- src/serve/functions.ts | 43 ++++++++++--------- 8 files changed, 67 insertions(+), 53 deletions(-) diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index 943dcc46ca7..024f15d3f61 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -9,11 +9,7 @@ import * as logform from "logform"; import * as path from "path"; import { EmulatedTriggerDefinition } from "../../src/emulator/functionsEmulatorShared"; -import { - EmulatableBackend, - FunctionsEmulator, - InvokeRuntimeOpts, -} from "../../src/emulator/functionsEmulator"; +import { FunctionsEmulator, InvokeRuntimeOpts } from "../../src/emulator/functionsEmulator"; import { Emulators } from "../../src/emulator/types"; import { RuntimeWorker } from "../../src/emulator/functionsRuntimeWorker"; import { TIMEOUT_LONG, TIMEOUT_MED, MODULE_ROOT } from "./fixtures"; @@ -131,12 +127,11 @@ function useFunctions(triggers: () => {}): void { // eslint-disable-next-line @typescript-eslint/unbound-method functionsEmulator.invokeTrigger = ( - backend: EmulatableBackend, trigger: EmulatedTriggerDefinition, proto?: any, runtimeOpts?: InvokeRuntimeOpts ): Promise => { - return invokeTrigger(testBackend, trigger, proto, { + return invokeTrigger(trigger, proto, { nodeBinary: process.execPath, serializedTriggers, }); diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index 3493fc8e6e8..4fe13961d6c 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -8,7 +8,11 @@ import * as sinon from "sinon"; import { EmulatorLog, Emulators } from "../../src/emulator/types"; import { FunctionRuntimeBundles, TIMEOUT_LONG, TIMEOUT_MED, MODULE_ROOT } from "./fixtures"; -import { FunctionsRuntimeBundle, SignatureType } from "../../src/emulator/functionsEmulatorShared"; +import { + EmulatedTriggerDefinition, + FunctionsRuntimeBundle, + SignatureType, +} from "../../src/emulator/functionsEmulatorShared"; import { InvokeRuntimeOpts, FunctionsEmulator } from "../../src/emulator/functionsEmulator"; import { RuntimeWorker } from "../../src/emulator/functionsRuntimeWorker"; import { streamToString, cloneDeep } from "../../src/utils"; @@ -60,15 +64,18 @@ async function invokeFunction( opts.ignore_warnings = true; opts.serializedTriggers = serializedTriggers; - const dummyTriggerDef = { + const dummyTriggerDef: EmulatedTriggerDefinition = { name: "function_id", region: "region", id: "region-function_id", entryPoint: "function_id", platform: "gcfv1" as const, }; + if (signatureType !== "http") { + dummyTriggerDef.eventTrigger = { resource: "dummyResource", eventType: "dummyType" }; + } + functionsEmulator.setTriggersForTesting([dummyTriggerDef], testBackend); return functionsEmulator.invokeTrigger( - testBackend, { ...dummyTriggerDef, // Fill in with dummy trigger info based on given signature type. diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 0036563d235..d9a7253e9e9 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -439,24 +439,25 @@ export async function startAll( const emulatableBackends: EmulatableBackend[] = []; const projectDir = (options.extDevDir || options.config.projectDir) as string; if (shouldStart(options, Emulators.FUNCTIONS)) { - const functionsCfg = normalizeAndValidate(options.config.src.functions)[0]; + const functionsCfg = normalizeAndValidate(options.config.src.functions); // Note: ext:dev:emulators:* commands hit this path, not the Emulators.EXTENSIONS path utils.assertIsStringOrUndefined(options.extDevDir); - const functionsDir = path.join(projectDir, functionsCfg.source); - emulatableBackends.push({ - functionsDir, - env: { - ...options.extDevEnv, - }, - secretEnv: [], // CF3 secrets are bound to specific functions, so we'll get them during trigger discovery. - // TODO(b/213335255): predefinedTriggers and nodeMajorVersion are here to support ext:dev:emulators:* commands. - // Ideally, we should handle that case via ExtensionEmulator. - predefinedTriggers: options.extDevTriggers as ParsedTriggerDefinition[] | undefined, - nodeMajorVersion: parseRuntimeVersion( - (options.extDevNodeVersion as string) || functionsCfg.runtime - ), - }); + for (const cfg of functionsCfg) { + const functionsDir = path.join(projectDir, cfg.source); + emulatableBackends.push({ + functionsDir, + codebase: cfg.codebase, + env: { + ...options.extDevEnv, + }, + secretEnv: [], // CF3 secrets are bound to specific functions, so we'll get them during trigger discovery. + // TODO(b/213335255): predefinedTriggers and nodeMajorVersion are here to support ext:dev:emulators:* commands. + // Ideally, we should handle that case via ExtensionEmulator. + predefinedTriggers: options.extDevTriggers as ParsedTriggerDefinition[] | undefined, + nodeMajorVersion: parseRuntimeVersion((options.extDevNodeVersion as string) || cfg.runtime), + }); + } } if (shouldStart(options, Emulators.EXTENSIONS) && previews.extensionsemulator) { diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 286dcd67ba0..d8a1422080d 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -91,6 +91,7 @@ export interface EmulatableBackend { functionsDir: string; env: Record; secretEnv: backend.SecretEnvVar[]; + codebase?: string; predefinedTriggers?: ParsedTriggerDefinition[]; nodeMajorVersion?: number; nodeBinary?: string; @@ -378,11 +379,12 @@ export class FunctionsEmulator implements EmulatorInstance { } async invokeTrigger( - backend: EmulatableBackend, trigger: EmulatedTriggerDefinition, proto?: any, runtimeOpts?: InvokeRuntimeOpts ): Promise { + const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger)); + const backend = record.backend; const bundleTemplate = this.getBaseBundle(); const runtimeBundle: FunctionsRuntimeBundle = { ...bundleTemplate, @@ -534,6 +536,9 @@ export class FunctionsEmulator implements EmulatorInstance { ); const endpoints = backend.allEndpoints(discoveredBackend); prepareEndpoints(endpoints); + for (const e of endpoints) { + e.codebase = emulatableBackend.codebase; + } triggerDefinitions = emulatedFunctionsFromEndpoints(endpoints); } // When force is true we set up all triggers, otherwise we only set up @@ -960,6 +965,7 @@ export class FunctionsEmulator implements EmulatorInstance { } setTriggersForTesting(triggers: EmulatedTriggerDefinition[], backend: EmulatableBackend) { + this.triggers = {}; triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false })); } @@ -1369,7 +1375,7 @@ export class FunctionsEmulator implements EmulatorInstance { } const trigger = record.def; const service = getFunctionService(trigger); - const worker = await this.invokeTrigger(record.backend, trigger, proto); + const worker = await this.invokeTrigger(trigger, proto); return new Promise((resolve, reject) => { if (projectId !== this.args.projectId) { @@ -1507,7 +1513,7 @@ export class FunctionsEmulator implements EmulatorInstance { ); } } - const worker = await this.invokeTrigger(record.backend, trigger); + const worker = await this.invokeTrigger(trigger); worker.onLogs((el: EmulatorLog) => { if (el.level === "FATAL") { diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index a386e60a9a9..e96ff46d07e 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -31,6 +31,7 @@ export interface ParsedTriggerDefinition { schedule?: EventSchedule; blockingTrigger?: BlockingTrigger; labels?: { [key: string]: any }; + codebase?: string; } export interface EmulatedTriggerDefinition extends ParsedTriggerDefinition { @@ -160,6 +161,7 @@ export function emulatedFunctionsFromEndpoints( // We should later refactor the emulator to stop using a custom trigger definition. name: endpoint.id, id: `${endpoint.region}-${endpoint.id}`, + codebase: endpoint.codebase, }; copyIfPresent( def, @@ -441,7 +443,9 @@ export function toBackendInfo( extension: e.extension, // Only present on published extensions extensionVersion: extensionVersion, // Only present on published extensions extensionSpec: extensionSpec, // Only present on local extensions - functionTriggers: e.predefinedTriggers ?? cf3Triggers, // If we don't have predefinedTriggers, this is the CF3 backend. + functionTriggers: + // If we don't have predefinedTriggers, this is the CF3 backend. + e.predefinedTriggers ?? cf3Triggers.filter((t) => t.codebase === e.codebase), }) ); } diff --git a/src/emulator/functionsEmulatorShell.ts b/src/emulator/functionsEmulatorShell.ts index c21c81fe108..28435afbaf5 100644 --- a/src/emulator/functionsEmulatorShell.ts +++ b/src/emulator/functionsEmulatorShell.ts @@ -15,7 +15,7 @@ export class FunctionsEmulatorShell implements FunctionsShellController { emulatedFunctions: string[]; urls: { [name: string]: string } = {}; - constructor(private emu: FunctionsEmulator, private backend: EmulatableBackend) { + constructor(private emu: FunctionsEmulator) { this.triggers = emu.getTriggerDefinitions(); this.emulatedFunctions = this.triggers.map((t) => t.id); @@ -64,7 +64,7 @@ export class FunctionsEmulatorShell implements FunctionsShellController { data, }; - this.emu.invokeTrigger(this.backend, trigger, proto); + this.emu.invokeTrigger(trigger, proto); } private getTrigger(name: string): EmulatedTriggerDefinition { diff --git a/src/functionsShellCommandAction.ts b/src/functionsShellCommandAction.ts index d0c88167f56..7f7dd859857 100644 --- a/src/functionsShellCommandAction.ts +++ b/src/functionsShellCommandAction.ts @@ -74,7 +74,7 @@ export const actionFunction = async (options: Options) => { }) .then(() => { const instance = serveFunctions.get(); - const emulator = new shell.FunctionsEmulatorShell(instance, serveFunctions.getBackend()); + const emulator = new shell.FunctionsEmulatorShell(instance); if (emulator.emulatedFunctions && emulator.emulatedFunctions.length === 0) { logger.info("No functions emulated."); diff --git a/src/serve/functions.ts b/src/serve/functions.ts index e3562df5fbb..ba84234cfed 100644 --- a/src/serve/functions.ts +++ b/src/serve/functions.ts @@ -15,40 +15,46 @@ import * as utils from "../utils"; // TODO(samstern): It would be better to convert this to an EmulatorServer // but we don't have the "options" object until start() is called. export class FunctionsServer { - emulatorServer: EmulatorServer | undefined = undefined; - backend: EmulatableBackend | undefined = undefined; + emulatorServer?: EmulatorServer; + backends?: EmulatableBackend[]; private assertServer() { - if (!this.emulatorServer || !this.backend) { + if (!this.emulatorServer || !this.backends) { throw new Error("Must call start() before calling any other operation!"); } } async start(options: Options, partialArgs: Partial): Promise { const projectId = needProjectId(options); - const config = projectConfig.normalizeAndValidate(options.config.src.functions)[0]; + const config = projectConfig.normalizeAndValidate(options.config.src.functions); + + const backends: EmulatableBackend[] = []; + for (const cfg of config) { + const functionsDir = path.join(options.config.projectDir, cfg.source); + const nodeMajorVersion = parseRuntimeVersion(cfg.runtime); + backends.push({ + functionsDir, + codebase: cfg.codebase, + nodeMajorVersion, + env: {}, + secretEnv: [], + }); + } + this.backends = backends; - const functionsDir = path.join(options.config.projectDir, config.source); const account = getProjectDefaultAccount(options.config.projectDir); - const nodeMajorVersion = parseRuntimeVersion(config.runtime); - this.backend = { - functionsDir, - nodeMajorVersion, - env: {}, - secretEnv: [], - }; - // Normally, these two fields are included in args (and typed as such). - // However, some poorly-typed tests may not have them and we need to provide - // default values for those tests to work properly. const args: FunctionsEmulatorArgs = { projectId, projectDir: options.config.projectDir, - emulatableBackends: [this.backend], + emulatableBackends: this.backends, projectAlias: options.projectAlias, account, ...partialArgs, }; + // Normally, these two fields are included in args (and typed as such). + // However, some poorly-typed tests may not have them and we need to provide + // default values for those tests to work properly. if (options.host) { utils.assertIsStringOrUndefined(options.host); args.host = options.host; @@ -83,11 +89,6 @@ export class FunctionsServer { await this.emulatorServer!.stop(); } - getBackend(): EmulatableBackend { - this.assertServer(); - return this.backend!; - } - get(): FunctionsEmulator { this.assertServer(); return this.emulatorServer!.get() as FunctionsEmulator; From 347a2155c7f361a45cad89b78cef8be3e087a6cf Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 21 Apr 2022 11:50:44 -0700 Subject: [PATCH 0257/1699] Use new codebase feature to split triggers for emulator e2e test (#4448) Emulator e2e test will now comprise 3 codebase: 1) Codebase that defines v1 triggers 2) Codebase that defines v2 triggers 3) Codebase that defines triggers that activate event/http triggers This is strictly not necessary but a good way to exercise multi-codebase support in the emulator. While implementing multi-codebase support, I found a small race condition in the Pubsub Emulator when registering trigger. Reorganizing where we `await` to squash that bug. --- .../triggers-end-to-end-tests/firebase.json | 16 +- .../functions/index.js | 371 ------------------ scripts/triggers-end-to-end-tests/run.sh | 12 +- .../{functions => triggers}/.gitignore | 0 .../triggers/index.js | 123 ++++++ .../{functions => triggers}/package.json | 2 +- .../triggers-end-to-end-tests/v1/.gitignore | 3 + scripts/triggers-end-to-end-tests/v1/index.js | 167 ++++++++ .../triggers-end-to-end-tests/v1/package.json | 17 + .../triggers-end-to-end-tests/v2/.gitignore | 3 + scripts/triggers-end-to-end-tests/v2/index.js | 96 +++++ .../triggers-end-to-end-tests/v2/package.json | 16 + src/emulator/pubsubEmulator.ts | 44 ++- 13 files changed, 473 insertions(+), 397 deletions(-) delete mode 100644 scripts/triggers-end-to-end-tests/functions/index.js rename scripts/triggers-end-to-end-tests/{functions => triggers}/.gitignore (100%) create mode 100644 scripts/triggers-end-to-end-tests/triggers/index.js rename scripts/triggers-end-to-end-tests/{functions => triggers}/package.json (90%) create mode 100644 scripts/triggers-end-to-end-tests/v1/.gitignore create mode 100644 scripts/triggers-end-to-end-tests/v1/index.js create mode 100644 scripts/triggers-end-to-end-tests/v1/package.json create mode 100644 scripts/triggers-end-to-end-tests/v2/.gitignore create mode 100644 scripts/triggers-end-to-end-tests/v2/index.js create mode 100644 scripts/triggers-end-to-end-tests/v2/package.json diff --git a/scripts/triggers-end-to-end-tests/firebase.json b/scripts/triggers-end-to-end-tests/firebase.json index 4a18a29f2ba..e37ee130128 100644 --- a/scripts/triggers-end-to-end-tests/firebase.json +++ b/scripts/triggers-end-to-end-tests/firebase.json @@ -6,7 +6,21 @@ "storage": { "rules": "storage.rules" }, - "functions": {}, + "functions": [ + { + "codebase": "triggers", + "source": "triggers" + }, + { + "codebase": "v1", + "source": "v1" + }, + { + "codebase": "v2", + "source": "v2" + } + + ], "emulators": { "hub": { "port": 4000 diff --git a/scripts/triggers-end-to-end-tests/functions/index.js b/scripts/triggers-end-to-end-tests/functions/index.js deleted file mode 100644 index 558300d71fb..00000000000 --- a/scripts/triggers-end-to-end-tests/functions/index.js +++ /dev/null @@ -1,371 +0,0 @@ -const admin = require("firebase-admin"); -const functions = require("firebase-functions"); -let functionsV2; -try { - functionsV2 = require("firebase-functions/v2"); -} catch { - // TODO: firebase-functions/lib path is unsupported, but this is the only way to access the v2 namespace in Node 10. - // Remove this ugly hack once we cut support for Node 10. - functionsV2 = require("firebase-functions/lib/v2"); -} -const { PubSub } = require("@google-cloud/pubsub"); - -/* - * Log snippets that the driver program above checks for. Be sure to update - * ../test.js if you plan on changing these. - */ -/* Functions V1 */ -const RTDB_FUNCTION_LOG = "========== RTDB FUNCTION =========="; -const FIRESTORE_FUNCTION_LOG = "========== FIRESTORE FUNCTION =========="; -const PUBSUB_FUNCTION_LOG = "========== PUBSUB FUNCTION =========="; -const AUTH_FUNCTION_LOG = "========== AUTH FUNCTION =========="; -const STORAGE_FUNCTION_ARCHIVED_LOG = "========== STORAGE FUNCTION ARCHIVED =========="; -const STORAGE_FUNCTION_DELETED_LOG = "========== STORAGE FUNCTION DELETED =========="; -const STORAGE_FUNCTION_FINALIZED_LOG = "========== STORAGE FUNCTION FINALIZED =========="; -const STORAGE_FUNCTION_METADATA_LOG = "========== STORAGE FUNCTION METADATA =========="; -const STORAGE_BUCKET_FUNCTION_ARCHIVED_LOG = - "========== STORAGE BUCKET FUNCTION ARCHIVED =========="; -const STORAGE_BUCKET_FUNCTION_DELETED_LOG = "========== STORAGE BUCKET FUNCTION DELETED =========="; -const STORAGE_BUCKET_FUNCTION_FINALIZED_LOG = - "========== STORAGE BUCKET FUNCTION FINALIZED =========="; -const STORAGE_BUCKET_FUNCTION_METADATA_LOG = - "========== STORAGE BUCKET FUNCTION METADATA =========="; -/* Functions V2 */ -const PUBSUB_FUNCTION_V2_LOG = "========== PUBSUB V2 FUNCTION =========="; -const STORAGE_FUNCTION_V2_ARCHIVED_LOG = "========== STORAGE V2 FUNCTION ARCHIVED =========="; -const STORAGE_FUNCTION_V2_DELETED_LOG = "========== STORAGE V2 FUNCTION DELETED =========="; -const STORAGE_FUNCTION_V2_FINALIZED_LOG = "========== STORAGE V2 FUNCTION FINALIZED =========="; -const STORAGE_FUNCTION_V2_METADATA_LOG = "========== STORAGE V2 FUNCTION METADATA =========="; -const STORAGE_BUCKET_FUNCTION_V2_ARCHIVED_LOG = - "========== STORAGE BUCKET V2 FUNCTION ARCHIVED =========="; -const STORAGE_BUCKET_FUNCTION_V2_DELETED_LOG = - "========== STORAGE BUCKET V2 FUNCTION DELETED =========="; -const STORAGE_BUCKET_FUNCTION_V2_FINALIZED_LOG = - "========== STORAGE BUCKET V2 FUNCTION FINALIZED =========="; -const STORAGE_BUCKET_FUNCTION_V2_METADATA_LOG = - "========== STORAGE BUCKET V2 FUNCTION METADATA =========="; - -/* - * We install onWrite triggers for START_DOCUMENT_NAME in both the firestore and - * database emulators. From each respective onWrite trigger, we write a document - * to both the firestore and database emulators. This exercises the - * bidirectional communication between cloud functions and each emulator. - */ -const START_DOCUMENT_NAME = "test/start"; -const END_DOCUMENT_NAME = "test/done"; - -const PUBSUB_TOPIC = "test-topic"; -const PUBSUB_SCHEDULED_TOPIC = "firebase-schedule-pubsubScheduled"; - -const STORAGE_FILE_NAME = "test-file.txt"; - -const pubsub = new PubSub(); -admin.initializeApp(); - -exports.deleteFromFirestore = functions.https.onRequest(async (req, res) => { - await admin.firestore().doc(START_DOCUMENT_NAME).delete(); - res.json({ deleted: true }); -}); - -exports.deleteFromRtdb = functions.https.onRequest(async (req, res) => { - await admin.database().ref(START_DOCUMENT_NAME).remove(); - res.json({ deleted: true }); -}); - -exports.writeToFirestore = functions.https.onRequest(async (req, res) => { - const ref = admin.firestore().doc(START_DOCUMENT_NAME); - await ref.set({ start: new Date().toISOString() }); - ref.get().then((snap) => { - res.json({ data: snap.data() }); - }); -}); - -exports.writeToRtdb = functions.https.onRequest(async (req, res) => { - const ref = admin.database().ref(START_DOCUMENT_NAME); - await ref.set({ start: new Date().toISOString() }); - ref.once("value", (snap) => { - res.json({ data: snap }); - }); -}); - -exports.writeToPubsub = functions.https.onRequest(async (req, res) => { - const msg = await pubsub.topic(PUBSUB_TOPIC).publishJSON({ foo: "bar" }, { attr: "val" }); - console.log("PubSub Emulator Host", process.env.PUBSUB_EMULATOR_HOST); - console.log("Wrote PubSub Message", msg); - res.json({ published: "ok" }); -}); - -exports.writeToScheduledPubsub = functions.https.onRequest(async (req, res) => { - const msg = await pubsub - .topic(PUBSUB_SCHEDULED_TOPIC) - .publishJSON({ foo: "bar" }, { attr: "val" }); - console.log("PubSub Emulator Host", process.env.PUBSUB_EMULATOR_HOST); - console.log("Wrote Scheduled PubSub Message", msg); - res.json({ published: "ok" }); -}); - -exports.writeToAuth = functions.https.onRequest(async (req, res) => { - const time = new Date().getTime(); - await admin.auth().createUser({ - uid: `uid${time}`, - email: `user${time}@example.com`, - }); - - res.json({ created: "ok" }); -}); - -exports.writeToDefaultStorage = functions.https.onRequest(async (req, res) => { - await admin.storage().bucket().file(STORAGE_FILE_NAME).save("hello world!"); - console.log("Wrote to default Storage bucket"); - res.json({ created: "ok" }); -}); - -exports.writeToSpecificStorageBucket = functions.https.onRequest(async (req, res) => { - await admin.storage().bucket("test-bucket").file(STORAGE_FILE_NAME).save("hello world!"); - console.log("Wrote to a specific Storage bucket"); - res.json({ created: "ok" }); -}); - -exports.updateMetadataFromDefaultStorage = functions.https.onRequest(async (req, res) => { - await admin.storage().bucket().file(STORAGE_FILE_NAME).save("hello metadata update!"); - console.log("Wrote to Storage bucket"); - await admin.storage().bucket().file(STORAGE_FILE_NAME).setMetadata({ somekey: "someval" }); - console.log("Updated metadata of default Storage bucket"); - res.json({ done: "ok" }); -}); - -exports.updateMetadataFromSpecificStorageBucket = functions.https.onRequest(async (req, res) => { - await admin - .storage() - .bucket("test-bucket") - .file(STORAGE_FILE_NAME) - .save("hello metadata update!"); - console.log("Wrote to a specific Storage bucket"); - await admin - .storage() - .bucket("test-bucket") - .file(STORAGE_FILE_NAME) - .setMetadata({ somenewkey: "somenewval" }); - console.log("Updated metadata of a specific Storage bucket"); - res.json({ done: "ok" }); -}); - -exports.updateDeleteFromDefaultStorage = functions.https.onRequest(async (req, res) => { - await admin.storage().bucket().file(STORAGE_FILE_NAME).save("something new!"); - console.log("Wrote to Storage bucket"); - await admin.storage().bucket().file(STORAGE_FILE_NAME).delete(); - console.log("Deleted from Storage bucket"); - res.json({ done: "ok" }); -}); - -exports.updateDeleteFromSpecificStorageBucket = functions.https.onRequest(async (req, res) => { - await admin.storage().bucket("test-bucket").file(STORAGE_FILE_NAME).save("something new!"); - console.log("Wrote to a specific Storage bucket"); - await admin.storage().bucket("test-bucket").file(STORAGE_FILE_NAME).delete(); - console.log("Deleted from a specific Storage bucket"); - res.json({ done: "ok" }); -}); - -exports.firestoreReaction = functions.firestore - .document(START_DOCUMENT_NAME) - .onWrite(async (/* change, ctx */) => { - console.log(FIRESTORE_FUNCTION_LOG); - /* - * Write back a completion timestamp to the firestore emulator. The test - * driver program checks for this by querying the firestore emulator - * directly. - */ - const ref = admin.firestore().doc(END_DOCUMENT_NAME + "_from_firestore"); - await ref.set({ done: new Date().toISOString() }); - - /* - * Write a completion marker to the firestore emulator. This exercise - * cross-emulator communication. - */ - const dbref = admin.database().ref(END_DOCUMENT_NAME + "_from_firestore"); - await dbref.set({ done: new Date().toISOString() }); - - return true; - }); - -exports.rtdbReaction = functions.database - .ref(START_DOCUMENT_NAME) - .onWrite(async (/* change, ctx */) => { - console.log(RTDB_FUNCTION_LOG); - - const ref = admin.database().ref(END_DOCUMENT_NAME + "_from_database"); - await ref.set({ done: new Date().toISOString() }); - - const firestoreref = admin.firestore().doc(END_DOCUMENT_NAME + "_from_database"); - await firestoreref.set({ done: new Date().toISOString() }); - - return true; - }); - -exports.pubsubReaction = functions.pubsub.topic(PUBSUB_TOPIC).onPublish((msg /* , ctx */) => { - console.log(PUBSUB_FUNCTION_LOG); - console.log("Message", JSON.stringify(msg.json)); - console.log("Attributes", JSON.stringify(msg.attributes)); - return true; -}); - -exports.pubsubv2reaction = functionsV2.pubsub.onMessagePublished(PUBSUB_TOPIC, (cloudevent) => { - console.log(PUBSUB_FUNCTION_V2_LOG); - console.log("Message", JSON.stringify(cloudevent.data.message.json)); - console.log("Attributes", JSON.stringify(cloudevent.data.message.attributes)); - return true; -}); - -exports.pubsubScheduled = functions.pubsub.schedule("every mon 07:00").onRun((context) => { - console.log(PUBSUB_FUNCTION_LOG); - console.log("Resource", JSON.stringify(context.resource)); - return true; -}); - -exports.authReaction = functions.auth.user().onCreate((user, ctx) => { - console.log(AUTH_FUNCTION_LOG); - console.log("User", JSON.stringify(user)); - return true; -}); - -exports.storageArchiveReaction = functions.storage - .bucket() - .object() - .onArchive((object, context) => { - console.log(STORAGE_FUNCTION_ARCHIVED_LOG); - console.log("Object", JSON.stringify(object)); - return true; - }); - -exports.storageDeleteReaction = functions.storage - .bucket() - .object() - .onDelete((object, context) => { - console.log(STORAGE_FUNCTION_DELETED_LOG); - console.log("Object", JSON.stringify(object)); - return true; - }); - -exports.storageFinalizeReaction = functions.storage - .bucket() - .object() - .onFinalize((object, context) => { - console.log(STORAGE_FUNCTION_FINALIZED_LOG); - console.log("Object", JSON.stringify(object)); - return true; - }); - -exports.storageMetadataReaction = functions.storage - .bucket() - .object() - .onMetadataUpdate((object, context) => { - console.log(STORAGE_FUNCTION_METADATA_LOG); - console.log("Object", JSON.stringify(object)); - return true; - }); - -exports.onCall = functions.https.onCall((data) => { - console.log("data", JSON.stringify(data)); - return data; -}); - -exports.storagev2archivedreaction = functionsV2.storage.onObjectArchived((cloudevent) => { - console.log(STORAGE_FUNCTION_V2_ARCHIVED_LOG); - console.log("Object", JSON.stringify(cloudevent.data)); - return true; -}); - -exports.storagev2deletedreaction = functionsV2.storage.onObjectDeleted((cloudevent) => { - console.log(STORAGE_FUNCTION_V2_DELETED_LOG); - console.log("Object", JSON.stringify(cloudevent.data)); - return true; -}); - -exports.storagev2finalizedreaction = functionsV2.storage.onObjectFinalized((cloudevent) => { - console.log(STORAGE_FUNCTION_V2_FINALIZED_LOG); - console.log("Object", JSON.stringify(cloudevent.data)); - return true; -}); - -exports.storagev2metadatareaction = functionsV2.storage.onObjectMetadataUpdated((cloudevent) => { - console.log(STORAGE_FUNCTION_V2_METADATA_LOG); - console.log("Object", JSON.stringify(cloudevent.data)); - return true; -}); - -exports.storageBucketArchiveReaction = functions.storage - .bucket("test-bucket") - .object() - .onArchive((object, context) => { - console.log(STORAGE_BUCKET_FUNCTION_ARCHIVED_LOG); - console.log("Object", JSON.stringify(object)); - return true; - }); - -exports.storageBucketDeleteReaction = functions.storage - .bucket("test-bucket") - .object() - .onDelete((object, context) => { - console.log(STORAGE_BUCKET_FUNCTION_DELETED_LOG); - console.log("Object", JSON.stringify(object)); - return true; - }); - -exports.storageBucketFinalizeReaction = functions.storage - .bucket("test-bucket") - .object() - .onFinalize((object, context) => { - console.log(STORAGE_BUCKET_FUNCTION_FINALIZED_LOG); - console.log("Object", JSON.stringify(object)); - return true; - }); - -exports.storageBucketMetadataReaction = functions.storage - .bucket("test-bucket") - .object() - .onMetadataUpdate((object, context) => { - console.log(STORAGE_BUCKET_FUNCTION_METADATA_LOG); - console.log("Object", JSON.stringify(object)); - return true; - }); - -exports.storagebucketv2archivedreaction = functionsV2.storage.onObjectArchived( - "test-bucket", - (cloudevent) => { - console.log(STORAGE_BUCKET_FUNCTION_V2_ARCHIVED_LOG); - console.log("Object", JSON.stringify(cloudevent.data)); - return true; - } -); - -exports.storagebucketv2deletedreaction = functionsV2.storage.onObjectDeleted( - "test-bucket", - (cloudevent) => { - console.log(STORAGE_BUCKET_FUNCTION_V2_DELETED_LOG); - console.log("Object", JSON.stringify(cloudevent.data)); - return true; - } -); - -exports.storagebucketv2finalizedreaction = functionsV2.storage.onObjectFinalized( - "test-bucket", - (cloudevent) => { - console.log(STORAGE_BUCKET_FUNCTION_V2_FINALIZED_LOG); - console.log("Object", JSON.stringify(cloudevent.data)); - return true; - } -); - -exports.storagebucketv2metadatareaction = functionsV2.storage.onObjectMetadataUpdated( - "test-bucket", - (cloudevent) => { - console.log(STORAGE_BUCKET_FUNCTION_V2_METADATA_LOG); - console.log("Object", JSON.stringify(cloudevent.data)); - return true; - } -); - -exports.oncallv2 = functionsV2.https.onCall((req) => { - console.log("data", JSON.stringify(req.data)); - return req.data; -}); diff --git a/scripts/triggers-end-to-end-tests/run.sh b/scripts/triggers-end-to-end-tests/run.sh index a2dad9bf9ab..b4e361cd2ef 100755 --- a/scripts/triggers-end-to-end-tests/run.sh +++ b/scripts/triggers-end-to-end-tests/run.sh @@ -3,11 +3,11 @@ source scripts/set-default-credentials.sh ./scripts/npm-link.sh -( - cd scripts/triggers-end-to-end-tests/functions - npm install -) +for dir in triggers v1 v2; do + ( + cd scripts/triggers-end-to-end-tests/$dir + npm install + ) +done npx mocha --exit scripts/triggers-end-to-end-tests/tests.ts - -rm scripts/triggers-end-to-end-tests/functions/package.json diff --git a/scripts/triggers-end-to-end-tests/functions/.gitignore b/scripts/triggers-end-to-end-tests/triggers/.gitignore similarity index 100% rename from scripts/triggers-end-to-end-tests/functions/.gitignore rename to scripts/triggers-end-to-end-tests/triggers/.gitignore diff --git a/scripts/triggers-end-to-end-tests/triggers/index.js b/scripts/triggers-end-to-end-tests/triggers/index.js new file mode 100644 index 00000000000..2ea104687a2 --- /dev/null +++ b/scripts/triggers-end-to-end-tests/triggers/index.js @@ -0,0 +1,123 @@ +const admin = require("firebase-admin"); +const functions = require("firebase-functions"); +const { PubSub } = require("@google-cloud/pubsub"); + +/* + * We install onWrite triggers for START_DOCUMENT_NAME in both the firestore and + * database emulators. From each respective onWrite trigger, we write a document + * to both the firestore and database emulators. This exercises the + * bidirectional communication between cloud functions and each emulator. + */ +const START_DOCUMENT_NAME = "test/start"; + +const PUBSUB_TOPIC = "test-topic"; +const PUBSUB_SCHEDULED_TOPIC = "firebase-schedule-pubsubScheduled"; + +const STORAGE_FILE_NAME = "test-file.txt"; + +const pubsub = new PubSub(); +admin.initializeApp(); + +exports.deleteFromFirestore = functions.https.onRequest(async (req, res) => { + await admin.firestore().doc(START_DOCUMENT_NAME).delete(); + res.json({ deleted: true }); +}); + +exports.deleteFromRtdb = functions.https.onRequest(async (req, res) => { + await admin.database().ref(START_DOCUMENT_NAME).remove(); + res.json({ deleted: true }); +}); + +exports.writeToFirestore = functions.https.onRequest(async (req, res) => { + const ref = admin.firestore().doc(START_DOCUMENT_NAME); + await ref.set({ start: new Date().toISOString() }); + ref.get().then((snap) => { + res.json({ data: snap.data() }); + }); +}); + +exports.writeToRtdb = functions.https.onRequest(async (req, res) => { + const ref = admin.database().ref(START_DOCUMENT_NAME); + await ref.set({ start: new Date().toISOString() }); + ref.once("value", (snap) => { + res.json({ data: snap }); + }); +}); + +exports.writeToPubsub = functions.https.onRequest(async (req, res) => { + const msg = await pubsub.topic(PUBSUB_TOPIC).publishJSON({ foo: "bar" }, { attr: "val" }); + console.log("PubSub Emulator Host", process.env.PUBSUB_EMULATOR_HOST); + console.log("Wrote PubSub Message", msg); + res.json({ published: "ok" }); +}); + +exports.writeToScheduledPubsub = functions.https.onRequest(async (req, res) => { + const msg = await pubsub + .topic(PUBSUB_SCHEDULED_TOPIC) + .publishJSON({ foo: "bar" }, { attr: "val" }); + console.log("PubSub Emulator Host", process.env.PUBSUB_EMULATOR_HOST); + console.log("Wrote Scheduled PubSub Message", msg); + res.json({ published: "ok" }); +}); + +exports.writeToAuth = functions.https.onRequest(async (req, res) => { + const time = new Date().getTime(); + await admin.auth().createUser({ + uid: `uid${time}`, + email: `user${time}@example.com`, + }); + + res.json({ created: "ok" }); +}); + +exports.writeToDefaultStorage = functions.https.onRequest(async (req, res) => { + await admin.storage().bucket().file(STORAGE_FILE_NAME).save("hello world!"); + console.log("Wrote to default Storage bucket"); + res.json({ created: "ok" }); +}); + +exports.writeToSpecificStorageBucket = functions.https.onRequest(async (req, res) => { + await admin.storage().bucket("test-bucket").file(STORAGE_FILE_NAME).save("hello world!"); + console.log("Wrote to a specific Storage bucket"); + res.json({ created: "ok" }); +}); + +exports.updateMetadataFromDefaultStorage = functions.https.onRequest(async (req, res) => { + await admin.storage().bucket().file(STORAGE_FILE_NAME).save("hello metadata update!"); + console.log("Wrote to Storage bucket"); + await admin.storage().bucket().file(STORAGE_FILE_NAME).setMetadata({ somekey: "someval" }); + console.log("Updated metadata of default Storage bucket"); + res.json({ done: "ok" }); +}); + +exports.updateMetadataFromSpecificStorageBucket = functions.https.onRequest(async (req, res) => { + await admin + .storage() + .bucket("test-bucket") + .file(STORAGE_FILE_NAME) + .save("hello metadata update!"); + console.log("Wrote to a specific Storage bucket"); + await admin + .storage() + .bucket("test-bucket") + .file(STORAGE_FILE_NAME) + .setMetadata({ somenewkey: "somenewval" }); + console.log("Updated metadata of a specific Storage bucket"); + res.json({ done: "ok" }); +}); + +exports.updateDeleteFromDefaultStorage = functions.https.onRequest(async (req, res) => { + await admin.storage().bucket().file(STORAGE_FILE_NAME).save("something new!"); + console.log("Wrote to Storage bucket"); + await admin.storage().bucket().file(STORAGE_FILE_NAME).delete(); + console.log("Deleted from Storage bucket"); + res.json({ done: "ok" }); +}); + +exports.updateDeleteFromSpecificStorageBucket = functions.https.onRequest(async (req, res) => { + await admin.storage().bucket("test-bucket").file(STORAGE_FILE_NAME).save("something new!"); + console.log("Wrote to a specific Storage bucket"); + await admin.storage().bucket("test-bucket").file(STORAGE_FILE_NAME).delete(); + console.log("Deleted from a specific Storage bucket"); + res.json({ done: "ok" }); +}); diff --git a/scripts/triggers-end-to-end-tests/functions/package.json b/scripts/triggers-end-to-end-tests/triggers/package.json similarity index 90% rename from scripts/triggers-end-to-end-tests/functions/package.json rename to scripts/triggers-end-to-end-tests/triggers/package.json index 412c456b046..cd5a9fa3826 100644 --- a/scripts/triggers-end-to-end-tests/functions/package.json +++ b/scripts/triggers-end-to-end-tests/triggers/package.json @@ -8,7 +8,7 @@ "dependencies": { "@google-cloud/pubsub": "^1.1.5", "firebase-admin": "^9.3.0", - "firebase-functions": "^3.16", + "firebase-functions": "^3.20.0", "@firebase/database-compat": "0.1.2" }, "devDependencies": { diff --git a/scripts/triggers-end-to-end-tests/v1/.gitignore b/scripts/triggers-end-to-end-tests/v1/.gitignore new file mode 100644 index 00000000000..884afa60ceb --- /dev/null +++ b/scripts/triggers-end-to-end-tests/v1/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.eslintrc +package-lock.json diff --git a/scripts/triggers-end-to-end-tests/v1/index.js b/scripts/triggers-end-to-end-tests/v1/index.js new file mode 100644 index 00000000000..c35b81abe5c --- /dev/null +++ b/scripts/triggers-end-to-end-tests/v1/index.js @@ -0,0 +1,167 @@ +const admin = require("firebase-admin"); +const functions = require("firebase-functions"); + +/* + * Log snippets that the driver program above checks for. Be sure to update + * ../test.js if you plan on changing these. + */ +const RTDB_FUNCTION_LOG = "========== RTDB FUNCTION =========="; +const FIRESTORE_FUNCTION_LOG = "========== FIRESTORE FUNCTION =========="; +const PUBSUB_FUNCTION_LOG = "========== PUBSUB FUNCTION =========="; +const AUTH_FUNCTION_LOG = "========== AUTH FUNCTION =========="; +const STORAGE_FUNCTION_ARCHIVED_LOG = "========== STORAGE FUNCTION ARCHIVED =========="; +const STORAGE_FUNCTION_DELETED_LOG = "========== STORAGE FUNCTION DELETED =========="; +const STORAGE_FUNCTION_FINALIZED_LOG = "========== STORAGE FUNCTION FINALIZED =========="; +const STORAGE_FUNCTION_METADATA_LOG = "========== STORAGE FUNCTION METADATA =========="; +const STORAGE_BUCKET_FUNCTION_ARCHIVED_LOG = + "========== STORAGE BUCKET FUNCTION ARCHIVED =========="; +const STORAGE_BUCKET_FUNCTION_DELETED_LOG = "========== STORAGE BUCKET FUNCTION DELETED =========="; +const STORAGE_BUCKET_FUNCTION_FINALIZED_LOG = + "========== STORAGE BUCKET FUNCTION FINALIZED =========="; +const STORAGE_BUCKET_FUNCTION_METADATA_LOG = + "========== STORAGE BUCKET FUNCTION METADATA =========="; + +/* + * We install onWrite triggers for START_DOCUMENT_NAME in both the firestore and + * database emulators. From each respective onWrite trigger, we write a document + * to both the firestore and database emulators. This exercises the + * bidirectional communication between cloud functions and each emulator. + */ +const START_DOCUMENT_NAME = "test/start"; +const END_DOCUMENT_NAME = "test/done"; + +const PUBSUB_TOPIC = "test-topic"; + +admin.initializeApp(); + +exports.firestoreReaction = functions.firestore + .document(START_DOCUMENT_NAME) + .onWrite(async (/* change, ctx */) => { + console.log(FIRESTORE_FUNCTION_LOG); + /* + * Write back a completion timestamp to the firestore emulator. The test + * driver program checks for this by querying the firestore emulator + * directly. + */ + const ref = admin.firestore().doc(END_DOCUMENT_NAME + "_from_firestore"); + await ref.set({ done: new Date().toISOString() }); + + /* + * Write a completion marker to the firestore emulator. This exercise + * cross-emulator communication. + */ + const dbref = admin.database().ref(END_DOCUMENT_NAME + "_from_firestore"); + await dbref.set({ done: new Date().toISOString() }); + + return true; + }); + +exports.rtdbReaction = functions.database + .ref(START_DOCUMENT_NAME) + .onWrite(async (/* change, ctx */) => { + console.log(RTDB_FUNCTION_LOG); + + const ref = admin.database().ref(END_DOCUMENT_NAME + "_from_database"); + await ref.set({ done: new Date().toISOString() }); + + const firestoreref = admin.firestore().doc(END_DOCUMENT_NAME + "_from_database"); + await firestoreref.set({ done: new Date().toISOString() }); + + return true; + }); + +exports.pubsubReaction = functions.pubsub.topic(PUBSUB_TOPIC).onPublish((msg /* , ctx */) => { + console.log(PUBSUB_FUNCTION_LOG); + console.log("Message", JSON.stringify(msg.json)); + console.log("Attributes", JSON.stringify(msg.attributes)); + return true; +}); + +exports.pubsubScheduled = functions.pubsub.schedule("every mon 07:00").onRun((context) => { + console.log(PUBSUB_FUNCTION_LOG); + console.log("Resource", JSON.stringify(context.resource)); + return true; +}); + +exports.authReaction = functions.auth.user().onCreate((user, ctx) => { + console.log(AUTH_FUNCTION_LOG); + console.log("User", JSON.stringify(user)); + return true; +}); + +exports.storageArchiveReaction = functions.storage + .bucket() + .object() + .onArchive((object, context) => { + console.log(STORAGE_FUNCTION_ARCHIVED_LOG); + console.log("Object", JSON.stringify(object)); + return true; + }); + +exports.storageDeleteReaction = functions.storage + .bucket() + .object() + .onDelete((object, context) => { + console.log(STORAGE_FUNCTION_DELETED_LOG); + console.log("Object", JSON.stringify(object)); + return true; + }); + +exports.storageFinalizeReaction = functions.storage + .bucket() + .object() + .onFinalize((object, context) => { + console.log(STORAGE_FUNCTION_FINALIZED_LOG); + console.log("Object", JSON.stringify(object)); + return true; + }); + +exports.storageMetadataReaction = functions.storage + .bucket() + .object() + .onMetadataUpdate((object, context) => { + console.log(STORAGE_FUNCTION_METADATA_LOG); + console.log("Object", JSON.stringify(object)); + return true; + }); + +exports.onCall = functions.https.onCall((data) => { + console.log("data", JSON.stringify(data)); + return data; +}); + +exports.storageBucketArchiveReaction = functions.storage + .bucket("test-bucket") + .object() + .onArchive((object, context) => { + console.log(STORAGE_BUCKET_FUNCTION_ARCHIVED_LOG); + console.log("Object", JSON.stringify(object)); + return true; + }); + +exports.storageBucketDeleteReaction = functions.storage + .bucket("test-bucket") + .object() + .onDelete((object, context) => { + console.log(STORAGE_BUCKET_FUNCTION_DELETED_LOG); + console.log("Object", JSON.stringify(object)); + return true; + }); + +exports.storageBucketFinalizeReaction = functions.storage + .bucket("test-bucket") + .object() + .onFinalize((object, context) => { + console.log(STORAGE_BUCKET_FUNCTION_FINALIZED_LOG); + console.log("Object", JSON.stringify(object)); + return true; + }); + +exports.storageBucketMetadataReaction = functions.storage + .bucket("test-bucket") + .object() + .onMetadataUpdate((object, context) => { + console.log(STORAGE_BUCKET_FUNCTION_METADATA_LOG); + console.log("Object", JSON.stringify(object)); + return true; + }); diff --git a/scripts/triggers-end-to-end-tests/v1/package.json b/scripts/triggers-end-to-end-tests/v1/package.json new file mode 100644 index 00000000000..48516683e61 --- /dev/null +++ b/scripts/triggers-end-to-end-tests/v1/package.json @@ -0,0 +1,17 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": {}, + "engines": { + "node": "12" + }, + "dependencies": { + "firebase-admin": "^9.3.0", + "firebase-functions": "^3.20.0", + "@firebase/database-compat": "0.1.2" + }, + "devDependencies": { + "firebase-functions-test": "^0.2.0" + }, + "private": true +} diff --git a/scripts/triggers-end-to-end-tests/v2/.gitignore b/scripts/triggers-end-to-end-tests/v2/.gitignore new file mode 100644 index 00000000000..884afa60ceb --- /dev/null +++ b/scripts/triggers-end-to-end-tests/v2/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.eslintrc +package-lock.json diff --git a/scripts/triggers-end-to-end-tests/v2/index.js b/scripts/triggers-end-to-end-tests/v2/index.js new file mode 100644 index 00000000000..690d5b325d7 --- /dev/null +++ b/scripts/triggers-end-to-end-tests/v2/index.js @@ -0,0 +1,96 @@ +const admin = require("firebase-admin"); +const functionsV2 = require("firebase-functions/v2"); + +/* + * Log snippets that the driver program above checks for. Be sure to update + * ../test.js if you plan on changing these. + */ +const PUBSUB_FUNCTION_LOG = "========== PUBSUB V2 FUNCTION =========="; +const STORAGE_FUNCTION_ARCHIVED_LOG = "========== STORAGE V2 FUNCTION ARCHIVED =========="; +const STORAGE_FUNCTION_DELETED_LOG = "========== STORAGE V2 FUNCTION DELETED =========="; +const STORAGE_FUNCTION_FINALIZED_LOG = "========== STORAGE V2 FUNCTION FINALIZED =========="; +const STORAGE_FUNCTION_METADATA_LOG = "========== STORAGE V2 FUNCTION METADATA =========="; +const STORAGE_BUCKET_FUNCTION_ARCHIVED_LOG = + "========== STORAGE BUCKET V2 FUNCTION ARCHIVED =========="; +const STORAGE_BUCKET_FUNCTION_DELETED_LOG = + "========== STORAGE BUCKET V2 FUNCTION DELETED =========="; +const STORAGE_BUCKET_FUNCTION_FINALIZED_LOG = + "========== STORAGE BUCKET V2 FUNCTION FINALIZED =========="; +const STORAGE_BUCKET_FUNCTION_METADATA_LOG = + "========== STORAGE BUCKET V2 FUNCTION METADATA =========="; + +const PUBSUB_TOPIC = "test-topic"; + +admin.initializeApp(); + +exports.pubsubv2reaction = functionsV2.pubsub.onMessagePublished(PUBSUB_TOPIC, (cloudevent) => { + console.log(PUBSUB_FUNCTION_LOG); + console.log("Message", JSON.stringify(cloudevent.data.message.json)); + console.log("Attributes", JSON.stringify(cloudevent.data.message.attributes)); + return true; +}); + +exports.storagev2archivedreaction = functionsV2.storage.onObjectArchived((cloudevent) => { + console.log(STORAGE_FUNCTION_ARCHIVED_LOG); + console.log("Object", JSON.stringify(cloudevent.data)); + return true; +}); + +exports.storagev2deletedreaction = functionsV2.storage.onObjectDeleted((cloudevent) => { + console.log(STORAGE_FUNCTION_DELETED_LOG); + console.log("Object", JSON.stringify(cloudevent.data)); + return true; +}); + +exports.storagev2finalizedreaction = functionsV2.storage.onObjectFinalized((cloudevent) => { + console.log(STORAGE_FUNCTION_FINALIZED_LOG); + console.log("Object", JSON.stringify(cloudevent.data)); + return true; +}); + +exports.storagev2metadatareaction = functionsV2.storage.onObjectMetadataUpdated((cloudevent) => { + console.log(STORAGE_FUNCTION_METADATA_LOG); + console.log("Object", JSON.stringify(cloudevent.data)); + return true; +}); + +exports.storagebucketv2archivedreaction = functionsV2.storage.onObjectArchived( + "test-bucket", + (cloudevent) => { + console.log(STORAGE_BUCKET_FUNCTION_ARCHIVED_LOG); + console.log("Object", JSON.stringify(cloudevent.data)); + return true; + } +); + +exports.storagebucketv2deletedreaction = functionsV2.storage.onObjectDeleted( + "test-bucket", + (cloudevent) => { + console.log(STORAGE_BUCKET_FUNCTION_DELETED_LOG); + console.log("Object", JSON.stringify(cloudevent.data)); + return true; + } +); + +exports.storagebucketv2finalizedreaction = functionsV2.storage.onObjectFinalized( + "test-bucket", + (cloudevent) => { + console.log(STORAGE_BUCKET_FUNCTION_FINALIZED_LOG); + console.log("Object", JSON.stringify(cloudevent.data)); + return true; + } +); + +exports.storagebucketv2metadatareaction = functionsV2.storage.onObjectMetadataUpdated( + "test-bucket", + (cloudevent) => { + console.log(STORAGE_BUCKET_FUNCTION_METADATA_LOG); + console.log("Object", JSON.stringify(cloudevent.data)); + return true; + } +); + +exports.oncallv2 = functionsV2.https.onCall((req) => { + console.log("data", JSON.stringify(req.data)); + return req.data; +}); diff --git a/scripts/triggers-end-to-end-tests/v2/package.json b/scripts/triggers-end-to-end-tests/v2/package.json new file mode 100644 index 00000000000..07fcd7bff6b --- /dev/null +++ b/scripts/triggers-end-to-end-tests/v2/package.json @@ -0,0 +1,16 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": {}, + "engines": { + "node": "12" + }, + "dependencies": { + "firebase-admin": "^9.3.0", + "firebase-functions": "^3.20.0" + }, + "devDependencies": { + "firebase-functions-test": "^0.2.0" + }, + "private": true +} diff --git a/src/emulator/pubsubEmulator.ts b/src/emulator/pubsubEmulator.ts index b8747f5acdf..36ef2e5d3c6 100644 --- a/src/emulator/pubsubEmulator.ts +++ b/src/emulator/pubsubEmulator.ts @@ -76,27 +76,13 @@ export class PubsubEmulator implements EmulatorInstance { return Emulators.PUBSUB; } - async addTrigger(topicName: string, triggerKey: string, signatureType: SignatureType) { - this.logger.logLabeled( - "DEBUG", - "pubsub", - `addTrigger(${topicName}, ${triggerKey}, ${signatureType})` - ); - - const triggers = this.triggersForTopic.get(topicName) || []; - if ( - triggers.some((t) => t.triggerKey === triggerKey) && - this.subscriptionForTopic.has(topicName) - ) { - this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists"); - return; - } - + private async maybeCreateTopicAndSub(topicName: string): Promise { const topic = this.pubsub.topic(topicName); try { this.logger.logLabeled("DEBUG", "pubsub", `Creating topic: ${topicName}`); await topic.create(); } catch (e: any) { + // CODE 6: ALREADY EXISTS. Carry on. if (e && e.code === 6) { this.logger.logLabeled("DEBUG", "pubsub", `Topic ${topicName} exists`); } else { @@ -105,14 +91,15 @@ export class PubsubEmulator implements EmulatorInstance { } const subName = `emulator-sub-${topicName}`; - let sub; + let sub: Subscription; try { this.logger.logLabeled("DEBUG", "pubsub", `Creating sub for topic: ${topicName}`); [sub] = await topic.createSubscription(subName); } catch (e: any) { if (e && e.code === 6) { + // CODE 6: ALREADY EXISTS. Carry on. this.logger.logLabeled("DEBUG", "pubsub", `Sub for ${topicName} exists`); - sub = topic.subscription(`emulator-sub-${topicName}`); + sub = topic.subscription(subName); } else { throw new FirebaseError(`Could not create sub ${subName}`, { original: e }); } @@ -122,6 +109,27 @@ export class PubsubEmulator implements EmulatorInstance { this.onMessage(topicName, message); }); + return sub; + } + + async addTrigger(topicName: string, triggerKey: string, signatureType: SignatureType) { + this.logger.logLabeled( + "DEBUG", + "pubsub", + `addTrigger(${topicName}, ${triggerKey}, ${signatureType})` + ); + + const sub = await this.maybeCreateTopicAndSub(topicName); + + const triggers = this.triggersForTopic.get(topicName) || []; + if ( + triggers.some((t) => t.triggerKey === triggerKey) && + this.subscriptionForTopic.has(topicName) + ) { + this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists"); + return; + } + triggers.push({ triggerKey, signatureType }); this.triggersForTopic.set(topicName, triggers); this.subscriptionForTopic.set(topicName, sub); From f895a4007392ef71215555ac383805f36cbdd213 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 21 Apr 2022 13:03:21 -0700 Subject: [PATCH 0258/1699] Fix bug where enabling API erroneously sent some payload. (#4467) The options we were including to suppresses superfluously log entries whenever we enable GCP API were being sent as payload to the POST request. Fixes https://github.com/firebase/firebase-tools/issues/4465 --- CHANGELOG.md | 1 + src/ensureApiEnabled.ts | 10 +++++++--- src/test/ensureApiEnabled.spec.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..718c8326298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes bug where API enablement failed. (#4467) diff --git a/src/ensureApiEnabled.ts b/src/ensureApiEnabled.ts index 4a6b4a7fa27..05e3a4ba158 100644 --- a/src/ensureApiEnabled.ts +++ b/src/ensureApiEnabled.ts @@ -50,9 +50,13 @@ export async function check( */ async function enable(projectId: string, apiName: string): Promise { try { - await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, { - skipLog: { resBody: true }, - }); + await apiClient.post( + `/projects/${projectId}/services/${apiName}:enable`, + undefined, + { + skipLog: { resBody: true }, + } + ); } catch (err: any) { if (isBillingError(err)) { throw new FirebaseError(`Your project ${bold( diff --git a/src/test/ensureApiEnabled.spec.ts b/src/test/ensureApiEnabled.spec.ts index 2ab95c18e39..bdf5de7154f 100644 --- a/src/test/ensureApiEnabled.spec.ts +++ b/src/test/ensureApiEnabled.spec.ts @@ -74,7 +74,7 @@ describe("ensureApiEnabled", () => { .reply(200, { state: "DISABLED" }); nock("https://serviceusage.googleapis.com") - .post(`/v1/projects/${FAKE_PROJECT_ID}/services/${FAKE_API}:enable`) + .post(`/v1/projects/${FAKE_PROJECT_ID}/services/${FAKE_API}:enable`, (body) => !body) .once() .reply(200); From 0ff5f915adb7b2624cfff465ac4be9761080e748 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 21 Apr 2022 20:16:44 +0000 Subject: [PATCH 0259/1699] 10.7.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5470beb7e2c..08d53112945 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.7.0", + "version": "10.7.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.7.0", + "version": "10.7.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 6961b391512..73c63462835 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.7.0", + "version": "10.7.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 32a568b05135d614035414832810231ffaf77e17 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 21 Apr 2022 20:17:09 +0000 Subject: [PATCH 0260/1699] [firebase-release] Removed change log and reset repo after 10.7.1 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 718c8326298..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes bug where API enablement failed. (#4467) From 82a5bd77c88b0fd604151a102010fe3ff56b6872 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Mon, 25 Apr 2022 23:33:47 -0700 Subject: [PATCH 0261/1699] Complete the trigger annotation -> build -> backend chain (#4437) * Create an alternative code path which converts Functions trigger annotations into the new Build type. This code path is not currently used in the deployment process. * Extend the test suite for parseTriggers (in the Node runtime) to make sure that Build and Backend are convertible without loss of information. --- src/deploy/functions/build.ts | 67 ++- src/deploy/functions/runtimes/golang/index.ts | 13 + src/deploy/functions/runtimes/index.ts | 9 + src/deploy/functions/runtimes/node/index.ts | 12 + .../functions/runtimes/node/parseTriggers.ts | 164 ++++++++ .../runtimes/node/parseTriggers.spec.ts | 391 ++++++++++++++++++ 6 files changed, 638 insertions(+), 18 deletions(-) diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index f40eeb43587..dce88f174ff 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -11,6 +11,28 @@ export interface Build { params: Param[]; } +/* A utility function that returns an empty Build. */ +/** + * + */ +export function empty(): Build { + return { + requiredAPIs: [], + endpoints: {}, + params: [], + }; +} + +/* A utility function that creates a Build containing a map of IDs to Endpoints. */ +/** + * + */ +export function of(endpoints: Record): Build { + const build = empty(); + build.endpoints = endpoints; + return build; +} + interface RequiredApi { // The API that should be enabled. For Google APIs, this should be a googleapis.com subdomain // (e.g. vision.googleapis.com) @@ -64,7 +86,7 @@ function resolveBoolean(from: boolean | Expression | null): boolean { type ServiceAccount = string; // Trigger definition for arbitrary HTTPS endpoints -interface HttpsTrigger { +export interface HttpsTrigger { // Which service account should be able to trigger this function. No value means "make public // on create and don't do anything on update." For more, see go/cf3-http-access-control invoker?: ServiceAccount | null; @@ -77,13 +99,13 @@ interface CallableTrigger { } // Trigger definitions for endpoints that should be called as a delegate for other operations. // For example, before user login. -interface BlockingTrigger { +export interface BlockingTrigger { eventType: string; } // Trigger definitions for endpoints that listen to CloudEvents emitted by other systems (or legacy // Google events for GCF gen 1) -interface EventTrigger { +export interface EventTrigger { eventType: string; eventFilters: Record>; @@ -117,7 +139,7 @@ interface TaskQueueRetryConfig { maxDoublings?: Field; } -interface TaskQueueTrigger { +export interface TaskQueueTrigger { rateLimits?: TaskQueueRateLimits | null; retryConfig?: TaskQueueRetryConfig | null; @@ -133,13 +155,13 @@ interface ScheduleRetryConfig { maxDoublings?: Field; } -interface ScheduleTrigger { +export interface ScheduleTrigger { schedule: string | Expression; timeZone: string | Expression; retryConfig: ScheduleRetryConfig; } -type Triggered = +export type Triggered = | { httpsTrigger: HttpsTrigger } | { callableTrigger: CallableTrigger } | { blockingTrigger: BlockingTrigger } @@ -152,7 +174,7 @@ interface VpcSettings { egressSettings?: "PRIVATE_RANGES_ONLY" | "ALL_TRAFFIC"; } -type Endpoint = Triggered & { +export type Endpoint = Triggered & { // Defaults to "gcfv2". "Run" will be an additional option defined later platform?: "gcfv1" | "gcfv2"; @@ -170,6 +192,12 @@ type Endpoint = Triggered & { // process.env.FIREBASE_FUNCTIONS_DEFAULT_REGION region?: string[]; + // The Cloud project associated with this endpoint. + project: string; + + // The runtime being deployed to this endpoint. Currently targeting "nodejs16." + runtime: string; + // Firebase default of 80. Cloud default of 1 concurrency?: Field; @@ -251,32 +279,35 @@ export function resolveBackend(build: Build): backend.Backend { const bkEndpoint: backend.Endpoint = { id: endpointId, - project: "", + project: endpoint.project, region: region, entryPoint: endpoint.entryPoint, platform: endpoint.platform, - runtime: "", - labels: endpoint.labels, - environmentVariables: endpoint.environmentVariables, - secretEnvironmentVariables: undefined, - availableMemoryMb: endpoint.availableMemoryMb, + runtime: endpoint.runtime, timeoutSeconds: timeout, ...trigger, }; proto.renameIfPresent(bkEndpoint, endpoint, "maxInstances", "maxInstances", resolveInt); proto.renameIfPresent(bkEndpoint, endpoint, "minInstances", "minInstances", resolveInt); proto.renameIfPresent(bkEndpoint, endpoint, "concurrency", "concurrency", resolveInt); - proto.copyIfPresent(bkEndpoint, endpoint, "ingressSettings"); + proto.copyIfPresent( + bkEndpoint, + endpoint, + "ingressSettings", + "availableMemoryMb", + "environmentVariables", + "labels" + ); + // proto.copyIfPresent(bkEndpoint, endpoint, "secretEnvironmentVariables"); if (endpoint.vpc) { bkEndpoint.vpc = { - connector: resolveString(endpoint.vpc.connector), - egressSettings: endpoint.vpc.egressSettings, + // $REGION is a token in the Build VPC connector because Build endpoints can have multiple regions, so we unroll here + connector: resolveString(endpoint.vpc.connector).replace("$REGION", region), }; + proto.copyIfPresent(bkEndpoint.vpc, endpoint.vpc, "egressSettings"); } if (endpoint.serviceAccount) { bkEndpoint.serviceAccountEmail = endpoint.serviceAccount; - } else { - bkEndpoint.serviceAccountEmail = "default"; } bkEndpoints.push(bkEndpoint); diff --git a/src/deploy/functions/runtimes/golang/index.ts b/src/deploy/functions/runtimes/golang/index.ts index f7b96f23037..cfddb79ff57 100644 --- a/src/deploy/functions/runtimes/golang/index.ts +++ b/src/deploy/functions/runtimes/golang/index.ts @@ -8,6 +8,7 @@ import * as spawn from "cross-spawn"; import { FirebaseError } from "../../../../error"; import { logger } from "../../../../logger"; import * as backend from "../../backend"; +import * as build from "../../build"; import * as discovery from "../discovery"; import * as gomod from "./gomod"; import * as runtimes from ".."; @@ -23,6 +24,9 @@ export const FUNCTIONS_SDK = "github.com/FirebaseExtended/firebase-functions-go" export const FUNCTIONS_CODEGEN = FUNCTIONS_SDK + "/support/codegen"; export const FUNCTIONS_RUNTIME = FUNCTIONS_SDK + "/support/runtime"; +/** + * + */ export async function tryCreateDelegate( context: runtimes.DelegateContext ): Promise { @@ -141,6 +145,15 @@ export class Delegate { }); } + // eslint-disable-next-line + async discoverBuild( + configValues: backend.RuntimeConfigValues, + envs: backend.EnvironmentVariables + ): Promise { + // Unimplemented. Build discovery is not currently supported in Go. + return { requiredAPIs: [], endpoints: {}, params: [] }; + } + async discoverSpec( configValues: backend.RuntimeConfigValues, envs: backend.EnvironmentVariables diff --git a/src/deploy/functions/runtimes/index.ts b/src/deploy/functions/runtimes/index.ts index 245bd630df3..29e9092083d 100644 --- a/src/deploy/functions/runtimes/index.ts +++ b/src/deploy/functions/runtimes/index.ts @@ -1,4 +1,5 @@ import * as backend from "../backend"; +import * as build from "../build"; import * as golang from "./golang"; import * as node from "./node"; import * as validate from "../validate"; @@ -93,6 +94,11 @@ export interface RuntimeDelegate { // for this to reuse or keep alive an HTTP server. This will speed up the emulator // by only loading customer code once. This part of the interface will be easier // to figure out as we go. + discoverBuild( + configValues: backend.RuntimeConfigValues, + envs: backend.EnvironmentVariables + ): Promise; + discoverSpec( configValues: backend.RuntimeConfigValues, envs: backend.EnvironmentVariables @@ -111,6 +117,9 @@ export interface DelegateContext { type Factory = (context: DelegateContext) => Promise; const factories: Factory[] = [node.tryCreateDelegate, golang.tryCreateDelegate]; +/** + * + */ export async function getRuntimeDelegate(context: DelegateContext): Promise { const { projectDir, sourceDir, runtime } = context; validate.functionsDirectoryExists(sourceDir, projectDir); diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 983bef1fe7b..b706c4713ca 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -12,6 +12,7 @@ import { logger } from "../../../../logger"; import { previews } from "../../../../previews"; import { logLabeledWarning } from "../../../../utils"; import * as backend from "../../backend"; +import * as build from "../../build"; import * as discovery from "../discovery"; import * as runtimes from ".."; import * as validate from "./validate"; @@ -20,6 +21,9 @@ import * as parseTriggers from "./parseTriggers"; const MIN_FUNCTIONS_SDK_VERSION = "3.20.0"; +/** + * + */ export async function tryCreateDelegate( context: runtimes.DelegateContext ): Promise { @@ -155,4 +159,12 @@ export class Delegate { } return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env); } + + // eslint-disable-next-line require-await + async discoverBuild( + config: backend.RuntimeConfigValues, + env: backend.EnvironmentVariables + ): Promise { + return parseTriggers.discoverBuild(this.projectId, this.sourceDir, this.runtime, config, env); + } } diff --git a/src/deploy/functions/runtimes/node/parseTriggers.ts b/src/deploy/functions/runtimes/node/parseTriggers.ts index c8562da4834..aee2508d41b 100644 --- a/src/deploy/functions/runtimes/node/parseTriggers.ts +++ b/src/deploy/functions/runtimes/node/parseTriggers.ts @@ -5,6 +5,7 @@ import { fork } from "child_process"; import { FirebaseError } from "../../../../error"; import { logger } from "../../../../logger"; import * as backend from "../../backend"; +import * as build from "../../build"; import * as api from "../../../../api"; import * as proto from "../../../../gcp/proto"; import * as args from "../../args"; @@ -139,6 +140,31 @@ export function useStrategy(context: args.Context): Promise { return Promise.resolve(true); } +/** + * + */ +export async function discoverBuild( + projectId: string, + sourceDir: string, + runtime: runtimes.Runtime, + configValues: backend.RuntimeConfigValues, + envs: backend.EnvironmentVariables +): Promise { + const triggerAnnotations = await parseTriggers(projectId, sourceDir, configValues, envs); + const want: build.Build = { + requiredAPIs: [], + endpoints: {}, + params: [], + }; + for (const annotation of triggerAnnotations) { + addResourcesToBuild(projectId, runtime, annotation, want); + } + return want; +} + +/** + * + */ export async function discoverBackend( projectId: string, sourceDir: string, @@ -155,6 +181,9 @@ export async function discoverBackend( } /* @internal */ +/** + * + */ export function mergeRequiredAPIs(backend: backend.Backend) { const apiToReasons: Record> = {}; for (const { api, reason } of backend.requiredAPIs) { @@ -173,6 +202,141 @@ export function mergeRequiredAPIs(backend: backend.Backend) { backend.requiredAPIs = merged; } +/** + * + */ +export function addResourcesToBuild( + projectId: string, + runtime: runtimes.Runtime, + annotation: TriggerAnnotation, + want: build.Build +): void { + Object.freeze(annotation); + // for (const region of annotation.regions || [api.functionsDefaultRegion]) { + const regions = annotation.regions || [api.functionsDefaultRegion]; + let triggered: build.Triggered; + + const triggerCount = + +!!annotation.httpsTrigger + + +!!annotation.eventTrigger + + +!!annotation.taskQueueTrigger + + +!!annotation.blockingTrigger; + if (triggerCount !== 1) { + throw new FirebaseError( + "Unexpected annotation generated by the Firebase Functions SDK. This should never happen." + ); + } + + if (annotation.taskQueueTrigger) { + want.requiredAPIs.push({ + api: "cloudtasks.googleapis.com", + reason: "Needed for task queue functions.", + }); + triggered = { + taskQueueTrigger: {}, + }; + proto.copyIfPresent(triggered.taskQueueTrigger, annotation.taskQueueTrigger, "invoker"); + proto.copyIfPresent(triggered.taskQueueTrigger, annotation.taskQueueTrigger, "rateLimits"); + if (annotation.taskQueueTrigger.retryConfig) { + triggered.taskQueueTrigger.retryConfig = Object.assign( + annotation.taskQueueTrigger.retryConfig, + { + maxRetryDurationSeconds: proto.secondsFromDuration( + annotation.taskQueueTrigger.retryConfig.maxRetryDuration || "0" + ), + } + ); + } + } else if (annotation.httpsTrigger) { + if (annotation.labels?.["deployment-callable"]) { + delete annotation.labels["deployment-callable"]; + triggered = { callableTrigger: {} }; + } else { + const trigger: build.HttpsTrigger = {}; + if (annotation.failurePolicy) { + logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`); + } + if (annotation.httpsTrigger.invoker) { + trigger.invoker = annotation.httpsTrigger.invoker[0]; + } + triggered = { httpsTrigger: trigger }; + } + } else if (annotation.schedule) { + want.requiredAPIs.push({ + api: "cloudscheduler.googleapis.com", + reason: "Needed for scheduled functions.", + }); + triggered = { + scheduleTrigger: { + schedule: annotation.schedule.schedule, + timeZone: annotation.schedule.timeZone || "what's the default timezone?", + retryConfig: annotation.schedule.retryConfig || {}, + }, + }; + } else if (annotation.blockingTrigger) { + if (events.v1.AUTH_BLOCKING_EVENTS.includes(annotation.blockingTrigger.eventType as any)) { + want.requiredAPIs.push({ + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions.", + }); + } + triggered = { + blockingTrigger: { + eventType: annotation.blockingTrigger.eventType, + }, + }; + } else { + triggered = { + eventTrigger: { + eventType: annotation.eventTrigger!.eventType, + eventFilters: { resource: annotation.eventTrigger!.resource }, + retry: !!annotation.failurePolicy, + }, + }; + } + + const endpointId: string = annotation.name; + const endpoint: build.Endpoint = { + platform: annotation.platform || "gcfv1", + region: regions, + project: projectId, + entryPoint: annotation.entryPoint, + runtime: runtime, + serviceAccount: annotation.serviceAccountEmail || "default", + ...triggered, + }; + if (annotation.vpcConnector != null) { + let maybeId = annotation.vpcConnector; + if (maybeId && !maybeId.includes("/")) { + maybeId = `projects/${projectId}/locations/$REGION/connectors/${maybeId}`; + } + endpoint.vpc = { connector: maybeId }; + proto.renameIfPresent(endpoint.vpc, annotation, "egressSettings", "vpcConnectorEgressSettings"); + } + proto.copyIfPresent( + endpoint, + annotation, + "concurrency", + "labels", + "ingressSettings", + "maxInstances", + "minInstances", + "availableMemoryMb" + ); + proto.renameIfPresent( + endpoint, + annotation, + "timeoutSeconds", + "timeout", + proto.secondsFromDuration + ); + + want.endpoints[endpointId] = endpoint; +} + +/** + * + */ export function addResourcesToBackend( projectId: string, runtime: runtimes.Runtime, diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index 1a10affcbbb..2909b053988 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -2,10 +2,401 @@ import { expect } from "chai"; import { FirebaseError } from "../../../../../error"; import * as backend from "../../../../../deploy/functions/backend"; +import * as build from "../../../../../deploy/functions/build"; import * as parseTriggers from "../../../../../deploy/functions/runtimes/node/parseTriggers"; import * as api from "../../../../../api"; import { BEFORE_CREATE_EVENT } from "../../../../../functions/events/v1"; +function applyBuildDefaults( + endpoint: Omit +): Omit { + if (!endpoint.timeoutSeconds) { + endpoint.timeoutSeconds = 60; + } + if (!endpoint.serviceAccountEmail) { + endpoint.serviceAccountEmail = "default"; + } + return endpoint; +} + +describe("addResourcesToBuild", () => { + const oldDefaultRegion = api.functionsDefaultRegion; + before(() => { + (api as any).functionsDefaultRegion = "us-central1"; + }); + + after(() => { + (api as any).functionsDefaultRegion = oldDefaultRegion; + }); + + const BASIC_TRIGGER: parseTriggers.TriggerAnnotation = Object.freeze({ + name: "func", + entryPoint: "func", + }); + + const BASIC_ENDPOINT: Omit = Object.freeze({ + platform: "gcfv1", + region: [api.functionsDefaultRegion], + project: "project", + runtime: "nodejs16", + entryPoint: "func", + serviceAccount: "default", + }); + + const BASIC_FUNCTION_NAME: backend.TargetIds = Object.freeze({ + id: "func", + region: api.functionsDefaultRegion, + project: "project", + }); + + const BASIC_BACKEND_ENDPOINT: Omit = Object.freeze( + applyBuildDefaults({ + platform: "gcfv1", + ...BASIC_FUNCTION_NAME, + runtime: "nodejs16", + entryPoint: "func", + }) + ); + + it("should handle a minimal https trigger, yielding a build reversibly equivalent to the corresponding backend", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + httpsTrigger: {}, + }; + + const result = build.empty(); + parseTriggers.addResourcesToBuild("project", "nodejs16", trigger, result); + + const expected: build.Build = build.of({ func: { ...BASIC_ENDPOINT, httpsTrigger: {} } }); + expect(result).to.deep.equal(expected); + + const expectedBackend: backend.Backend = backend.of({ + ...BASIC_BACKEND_ENDPOINT, + httpsTrigger: {}, + }); + const convertedBackend: backend.Backend = build.resolveBackend(expected); + expect(convertedBackend).to.deep.equal(expectedBackend); + }); + + it("should handle a callable trigger, yielding a build reversibly equivalent to the corresponding backend", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + httpsTrigger: {}, + labels: { + "deployment-callable": "true", + }, + }; + + const result = build.empty(); + parseTriggers.addResourcesToBuild("project", "nodejs16", trigger, result); + + const expected: build.Build = build.of({ + func: { + ...BASIC_ENDPOINT, + callableTrigger: {}, + labels: {}, + }, + }); + expect(result).to.deep.equal(expected); + + const expectedBackend: backend.Backend = backend.of({ + ...BASIC_BACKEND_ENDPOINT, + callableTrigger: {}, + labels: {}, + }); + const convertedBackend: backend.Backend = build.resolveBackend(expected); + expect(convertedBackend).to.deep.equal(expectedBackend); + }); + + it("should handle a minimal task queue trigger, yielding a build reversibly equivalent to the corresponding backend", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + taskQueueTrigger: {}, + }; + + const result = build.empty(); + parseTriggers.addResourcesToBuild("project", "nodejs16", trigger, result); + + const expected: build.Build = build.of({ + func: { + ...BASIC_ENDPOINT, + taskQueueTrigger: {}, + }, + }); + expected.requiredAPIs = [ + { + api: "cloudtasks.googleapis.com", + reason: "Needed for task queue functions.", + }, + ]; + expect(result).to.deep.equal(expected); + + const expectedBackend: backend.Backend = { + ...backend.of({ ...BASIC_BACKEND_ENDPOINT, taskQueueTrigger: {} }), + requiredAPIs: [ + { + api: "cloudtasks.googleapis.com", + reason: "Needed for task queue functions.", + }, + ], + }; + const convertedBackend: backend.Backend = build.resolveBackend(expected); + expect(convertedBackend).to.deep.equal(expectedBackend); + }); + + it("should copy fields, yielding a build reversibly equivalent to the corresponding backend", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + httpsTrigger: { + invoker: ["public"], + }, + maxInstances: 42, + minInstances: 1, + serviceAccountEmail: "inlined@google.com", + vpcConnectorEgressSettings: "PRIVATE_RANGES_ONLY", + vpcConnector: "projects/project/locations/region/connectors/connector", + ingressSettings: "ALLOW_ALL", + labels: { + test: "testing", + }, + }; + + const result = build.empty(); + parseTriggers.addResourcesToBuild("project", "nodejs16", trigger, result); + + const config: Partial = { + maxInstances: 42, + minInstances: 1, + serviceAccount: "inlined@google.com", + vpc: { + connector: "projects/project/locations/region/connectors/connector", + egressSettings: "PRIVATE_RANGES_ONLY", + }, + ingressSettings: "ALLOW_ALL", + labels: { + test: "testing", + }, + }; + const expected: build.Build = build.of({ + func: { + ...BASIC_ENDPOINT, + ...config, + httpsTrigger: { + invoker: "public", + }, + }, + }); + expect(result).to.deep.equal(expected); + + const backendConfig: backend.ServiceConfiguration = { + maxInstances: 42, + minInstances: 1, + serviceAccountEmail: "inlined@google.com", + vpc: { + connector: "projects/project/locations/region/connectors/connector", + egressSettings: "PRIVATE_RANGES_ONLY", + }, + ingressSettings: "ALLOW_ALL", + labels: { + test: "testing", + }, + }; + const expectedBackend: backend.Backend = backend.of({ + ...BASIC_BACKEND_ENDPOINT, + httpsTrigger: { + invoker: ["public"], + }, + ...backendConfig, + }); + const convertedBackend: backend.Backend = build.resolveBackend(expected); + expect(convertedBackend).to.deep.equal(expectedBackend); + }); + + it("should rename/transform fields, yielding a build reversibly equivalent to the corresponding backend", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + eventTrigger: { + eventType: "google.pubsub.topic.publish", + resource: "projects/p/topics/t", + service: "pubsub.googleapis.com", + }, + timeout: "60s", + }; + + const result = build.empty(); + parseTriggers.addResourcesToBuild("project", "nodejs16", trigger, result); + + const eventTrigger: backend.EventTrigger = { + eventType: "google.pubsub.topic.publish", + eventFilters: { resource: "projects/p/topics/t" }, + retry: false, + }; + const expected: build.Build = build.of({ + func: { + ...BASIC_ENDPOINT, + eventTrigger, + timeoutSeconds: 60, + }, + }); + expect(result).to.deep.equal(expected); + + const expectedBackend: backend.Backend = backend.of({ + ...BASIC_BACKEND_ENDPOINT, + eventTrigger, + }); + const convertedBackend: backend.Backend = build.resolveBackend(expected); + expect(convertedBackend).to.deep.equal(expectedBackend); + }); + + it("should support multiple regions, yielding a build reversibly equivalent to the corresponding backend", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + httpsTrigger: {}, + regions: ["us-central1", "europe-west1"], + }; + + const result = build.empty(); + parseTriggers.addResourcesToBuild("project", "nodejs16", trigger, result); + const expected: build.Build = build.of({ + func: { + ...BASIC_ENDPOINT, + httpsTrigger: {}, + region: ["us-central1", "europe-west1"], + }, + }); + expect(result).to.deep.equal(expected); + + const expectedBackend: backend.Backend = backend.of( + { + ...BASIC_BACKEND_ENDPOINT, + httpsTrigger: {}, + region: "us-central1", + }, + { + ...BASIC_BACKEND_ENDPOINT, + httpsTrigger: {}, + region: "europe-west1", + } + ); + const convertedBackend: backend.Backend = build.resolveBackend(expected); + expect(convertedBackend).to.deep.equal(expectedBackend); + }); + + it("should support schedules, yielding a build reversibly equivalent to the corresponding backend", () => { + const schedule = { + schedule: "every 10 minutes", + timeZone: "America/Los_Angeles", + retryConfig: { + retryCount: 20, + maxRetryDuration: "200s", + minBackoffDuration: "1s", + maxBackoffDuration: "10s", + maxDoublings: 10, + }, + }; + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + eventTrigger: { + eventType: "google.pubsub.topic.publish", + resource: "projects/project/topics", + service: "pubsub.googleapis.com", + }, + regions: ["us-central1", "europe-west1"], + schedule, + labels: { + test: "testing", + }, + }; + + const result = build.empty(); + parseTriggers.addResourcesToBuild("project", "nodejs16", trigger, result); + + const europeFunctionName = { + ...BASIC_FUNCTION_NAME, + region: "europe-west1", + }; + + const expected: build.Build = build.of({ + func: { + ...BASIC_ENDPOINT, + scheduleTrigger: schedule, + labels: { + test: "testing", + }, + region: ["us-central1", "europe-west1"], + }, + }); + expected.requiredAPIs = [ + { + api: "cloudscheduler.googleapis.com", + reason: "Needed for scheduled functions.", + }, + ]; + expect(result).to.deep.equal(expected); + + const expectedBackend: backend.Backend = { + ...backend.of( + { + ...BASIC_BACKEND_ENDPOINT, + region: "us-central1", + labels: { + test: "testing", + }, + scheduleTrigger: schedule, + }, + { + ...BASIC_BACKEND_ENDPOINT, + region: "europe-west1", + labels: { + test: "testing", + }, + scheduleTrigger: schedule, + } + ), + requiredAPIs: [ + { + api: "cloudscheduler.googleapis.com", + reason: "Needed for scheduled functions.", + }, + ], + }; + const convertedBackend: backend.Backend = build.resolveBackend(expected); + expect(convertedBackend).to.deep.equal(expectedBackend); + }); + + it("should preserve empty vpc connector setting, yielding a build reversibly equivalent to the corresponding backend", () => { + const trigger: parseTriggers.TriggerAnnotation = { + ...BASIC_TRIGGER, + httpsTrigger: {}, + vpcConnector: "", + }; + + const result = build.empty(); + parseTriggers.addResourcesToBuild("project", "nodejs16", trigger, result); + + const expected: build.Build = build.of({ + func: { + ...BASIC_ENDPOINT, + httpsTrigger: {}, + vpc: { + connector: "", + }, + }, + }); + expect(result).to.deep.equal(expected); + + const expectedBackend: backend.Backend = backend.of({ + ...BASIC_BACKEND_ENDPOINT, + httpsTrigger: {}, + vpc: { + connector: "", + }, + }); + const convertedBackend: backend.Backend = build.resolveBackend(expected); + expect(convertedBackend).to.deep.equal(expectedBackend); + }); +}); + describe("addResourcesToBackend", () => { const oldDefaultRegion = api.functionsDefaultRegion; before(() => { From 15708d67c525481b09b9c7a5876b5b9af3c3f116 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 26 Apr 2022 09:26:39 -0700 Subject: [PATCH 0262/1699] 2022-04-26 audit fixes (#4479) * audit fixes * update dev deps --- npm-shrinkwrap.json | 116 +++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 08d53112945..475eb826073 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3152,9 +3152,9 @@ } }, "node_modules/archiver/node_modules/async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" }, "node_modules/archy": { "version": "1.0.0", @@ -3291,9 +3291,9 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dependencies": { "lodash": "^4.17.14" } @@ -6941,9 +6941,9 @@ } }, "node_modules/google-p12-pem/node_modules/node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "engines": { "node": ">= 6.13.0" } @@ -8943,9 +8943,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minimist-options": { "version": "4.1.0", @@ -10412,22 +10412,30 @@ } }, "node_modules/portfinder": { - "version": "1.0.23", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.23.tgz", - "integrity": "sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "dependencies": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" }, "engines": { "node": ">= 0.12.0" } }, - "node_modules/portfinder/node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/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==" }, "node_modules/prelude-ls": { "version": "1.1.2", @@ -13229,9 +13237,9 @@ } }, "node_modules/urijs": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.8.tgz", - "integrity": "sha512-iIXHrjomQ0ZCuDRy44wRbyTZVnfVNLVo3Ksz1yxNyE5wV1IDZW2S5Jszy45DTlw/UdsnRT7DyDhIz7Gy+vJumw==", + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", "dev": true }, "node_modules/url-join": { @@ -16263,9 +16271,9 @@ }, "dependencies": { "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" } } }, @@ -16414,9 +16422,9 @@ } }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "requires": { "lodash": "^4.17.14" } @@ -19284,9 +19292,9 @@ }, "dependencies": { "node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" } } }, @@ -20889,9 +20897,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minimist-options": { "version": "4.1.0", @@ -22018,19 +22026,27 @@ } }, "portfinder": { - "version": "1.0.23", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.23.tgz", - "integrity": "sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" }, "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -24217,9 +24233,9 @@ } }, "urijs": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.8.tgz", - "integrity": "sha512-iIXHrjomQ0ZCuDRy44wRbyTZVnfVNLVo3Ksz1yxNyE5wV1IDZW2S5Jszy45DTlw/UdsnRT7DyDhIz7Gy+vJumw==", + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", "dev": true }, "url-join": { From e44a909a39f4bd866186b61158fc7ad82679b0d8 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 26 Apr 2022 09:31:26 -0700 Subject: [PATCH 0263/1699] bumb node version to min 14; tooling at 16 (#4478) * drop node 12, add node 18 for testing * update builders to use node 14 * default tooling to 16; update actions * formatting is hard * update integration tests to be 16 * add back in a cache step for node_modules * update cache path * new cache format * add version for caches * remove extra caching * remove extra caching * bump package.json version * npm shrinkwrap update too --- .github/workflows/node-test.yml | 40 ++++++++++++++++-------------- npm-shrinkwrap.json | 2 +- package.json | 2 +- scripts/build/Dockerfile | 2 +- scripts/firepit-builder/Dockerfile | 2 +- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 466265e0a3f..5b4df5abe59 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -14,12 +14,12 @@ jobs: strategy: matrix: node-version: - - 12.x + - "16" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: npm @@ -34,12 +34,12 @@ jobs: strategy: matrix: node-version: - - 12.x - - 14.x - - 16.x + - "14" + - "16" + - "18" steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: npm @@ -66,7 +66,7 @@ jobs: fail-fast: false matrix: node-version: - - 12.x + - "16" script: - npm run test:hosting - npm run test:client-integration @@ -76,15 +76,15 @@ jobs: - npm run test:storage-deploy - npm run test:storage-emulator-integration steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: npm cache-dependency-path: npm-shrinkwrap.json - name: Cache firebase emulators - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ env.FIREBASE_EMULATORS_PATH }} key: ${{ runner.os }}-firebase-emulators-${{ hashFiles('emulator-cache/**') }} @@ -105,14 +105,16 @@ jobs: strategy: matrix: node-version: - - 12.x + - "16" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: npm + cache-dependency-path: npm-shrinkwrap.json - run: npm i -g npm@8.5 # --ignore-scripts prevents the `prepare` script from being run. - run: npm install --package-lock-only --ignore-scripts @@ -124,14 +126,16 @@ jobs: strategy: matrix: node-version: - - 12.x + - "16" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: npm + cache-dependency-path: npm-shrinkwrap.json - run: npm install - run: npm run generate:json-schema - run: "git diff --exit-code -- schema/*.json || (echo 'Error: JSON schema is changed! Please run npm run generate:json-schema and commit the results.' && false)" diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 08d53112945..6bba41fe39e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -149,7 +149,7 @@ "typescript-json-schema": "^0.50.1" }, "engines": { - "node": ">= 12" + "node": ">= 14.6.0" } }, "node_modules/@apidevtools/json-schema-ref-parser": { diff --git a/package.json b/package.json index 73c63462835..8e3bbd649c7 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ ], "preferGlobal": true, "engines": { - "node": ">= 12" + "node": ">= 14.6.0" }, "author": "Firebase (https://firebase.google.com/)", "license": "MIT", diff --git a/scripts/build/Dockerfile b/scripts/build/Dockerfile index dd24e6621cc..8f5e04ec3cb 100644 --- a/scripts/build/Dockerfile +++ b/scripts/build/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12 +FROM node:16 # Install dependencies RUN apt-get update && \ diff --git a/scripts/firepit-builder/Dockerfile b/scripts/firepit-builder/Dockerfile index a6537ded3d6..554f355a893 100644 --- a/scripts/firepit-builder/Dockerfile +++ b/scripts/firepit-builder/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12 +FROM node:16 # Install dependencies RUN apt-get update && \ From f2a4235eb9ae640f188f8cb19034ba36e2f4335a Mon Sep 17 00:00:00 2001 From: Jeff <3759507+jhuleatt@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:52:03 -0400 Subject: [PATCH 0264/1699] Fix minor typo (#4480) --- src/deploy/functions/validate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 5040d679fe8..6449d84316a 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -116,7 +116,7 @@ export function functionIdsAreValid(functions: { id: string; platform: string }[ }); if (invalidV2Ids.length !== 0) { const msg = - `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contin lower ` + + `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contain lower ` + `case letters, numbers, hyphens, and not exceed 62 characters in length`; throw new FirebaseError(msg); } From e64a143904c8c05c706ce2eeffddaee73f9aff54 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 26 Apr 2022 12:40:47 -0700 Subject: [PATCH 0265/1699] update marked and npm (#4481) * upgrade marked packages * upgrade npm to 8.7 --- .github/workflows/node-test.yml | 8 +- npm-shrinkwrap.json | 272 +++++++++++++++++++++-------- package.json | 6 +- scripts/build/Dockerfile | 2 +- scripts/firepit-builder/Dockerfile | 2 +- 5 files changed, 205 insertions(+), 85 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 5b4df5abe59..50196700636 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -25,7 +25,7 @@ jobs: cache: npm cache-dependency-path: npm-shrinkwrap.json - - run: npm i -g npm@8.5 + - run: npm i -g npm@8.7 - run: npm ci - run: npm run lint:changed-files @@ -45,7 +45,7 @@ jobs: cache: npm cache-dependency-path: npm-shrinkwrap.json - - run: npm i -g npm@8.5 + - run: npm i -g npm@8.7 - run: npm ci - run: npm test @@ -90,7 +90,7 @@ jobs: key: ${{ runner.os }}-firebase-emulators-${{ hashFiles('emulator-cache/**') }} continue-on-error: true - - run: npm i -g npm@8.5 + - run: npm i -g npm@8.7 - run: npm ci - run: echo ${{ secrets.service_account_json_base64 }} | base64 -d > ./scripts/service-account.json - run: ${{ matrix.script }} @@ -115,7 +115,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: npm cache-dependency-path: npm-shrinkwrap.json - - run: npm i -g npm@8.5 + - run: npm i -g npm@8.7 # --ignore-scripts prevents the `prepare` script from being run. - run: npm install --package-lock-only --ignore-scripts - run: "git diff --exit-code -- npm-shrinkwrap.json || (echo 'Error: npm-shrinkwrap.json is changed during npm install! Please make sure to use npm >= 8 and commit npm-shrinkwrap.json.' && false)" diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 6bba41fe39e..8fefbe56348 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -39,8 +39,8 @@ "jsonwebtoken": "^8.5.1", "leven": "^3.1.0", "lodash": "^4.17.21", - "marked": "^4.0.10", - "marked-terminal": "^3.3.0", + "marked": "^4.0.14", + "marked-terminal": "^5.1.1", "mime": "^2.5.2", "minimatch": "^3.0.4", "morgan": "^1.10.0", @@ -95,7 +95,7 @@ "@types/js-yaml": "^3.12.2", "@types/jsonwebtoken": "^8.3.8", "@types/lodash": "^4.14.149", - "@types/marked": "^4.0.1", + "@types/marked": "^4.0.3", "@types/marked-terminal": "^3.1.3", "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", @@ -486,6 +486,15 @@ "to-fast-properties": "^2.0.0" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -2187,9 +2196,9 @@ "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "node_modules/@types/marked": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz", - "integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz", + "integrity": "sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg==", "dev": true }, "node_modules/@types/marked-terminal": { @@ -3039,11 +3048,28 @@ } }, "node_modules/ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dependencies": { + "type-fest": "^1.0.2" + }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ansi-regex": { @@ -3058,6 +3084,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -3871,6 +3898,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -4008,6 +4036,20 @@ "node": ">= 0.2.0" } }, + "node_modules/cli-table3": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -7067,6 +7109,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, "engines": { "node": ">=4" } @@ -8454,11 +8497,6 @@ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=" }, - "node_modules/lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" - }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -8736,9 +8774,9 @@ } }, "node_modules/marked": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", - "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", + "integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==", "bin": { "marked": "bin/marked.js" }, @@ -8747,19 +8785,33 @@ } }, "node_modules/marked-terminal": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.3.0.tgz", - "integrity": "sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", + "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", "dependencies": { - "ansi-escapes": "^3.1.0", + "ansi-escapes": "^5.0.0", "cardinal": "^2.1.1", - "chalk": "^2.4.1", - "cli-table": "^0.3.1", - "node-emoji": "^1.4.1", - "supports-hyperlinks": "^1.0.1" + "chalk": "^5.0.0", + "cli-table3": "^0.6.1", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.2.0" + }, + "engines": { + "node": ">=14.13.1 || >=16.0.0" }, "peerDependencies": { - "marked": "^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0" + "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/media-typer": { @@ -9403,11 +9455,11 @@ "dev": true }, "node_modules/node-emoji": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", - "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", "dependencies": { - "lodash.toarray": "^4.4.0" + "lodash": "^4.17.21" } }, "node_modules/node-fetch": { @@ -10535,6 +10587,15 @@ "node": ">=10" } }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -12240,6 +12301,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -12248,23 +12310,34 @@ } }, "node_modules/supports-hyperlinks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz", - "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", "dependencies": { - "has-flag": "^2.0.0", - "supports-color": "^5.0.0" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=0.10.0" + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/swagger2openapi": { @@ -14053,6 +14126,12 @@ "to-fast-properties": "^2.0.0" } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true + }, "@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -15530,9 +15609,9 @@ "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/marked": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz", - "integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz", + "integrity": "sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg==", "dev": true }, "@types/marked-terminal": { @@ -16202,9 +16281,19 @@ "dev": true }, "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "requires": { + "type-fest": "^1.0.2" + }, + "dependencies": { + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" + } + } }, "ansi-regex": { "version": "2.1.1", @@ -16215,6 +16304,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -16859,6 +16949,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -16955,6 +17046,15 @@ "colors": "1.0.3" } }, + "cli-table3": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -19388,7 +19488,8 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "has-unicode": { "version": "2.0.1", @@ -20510,11 +20611,6 @@ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=" }, - "lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" - }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -20740,21 +20836,28 @@ "dev": true }, "marked": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", - "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==" + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", + "integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==" }, "marked-terminal": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.3.0.tgz", - "integrity": "sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", + "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", "requires": { - "ansi-escapes": "^3.1.0", + "ansi-escapes": "^5.0.0", "cardinal": "^2.1.1", - "chalk": "^2.4.1", - "cli-table": "^0.3.1", - "node-emoji": "^1.4.1", - "supports-hyperlinks": "^1.0.1" + "chalk": "^5.0.0", + "cli-table3": "^0.6.1", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.2.0" + }, + "dependencies": { + "chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==" + } } }, "media-typer": { @@ -21250,11 +21353,11 @@ } }, "node-emoji": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", - "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", "requires": { - "lodash.toarray": "^4.4.0" + "lodash": "^4.17.21" } }, "node-fetch": { @@ -22108,6 +22211,14 @@ "requires": { "err-code": "^2.0.2", "retry": "^0.12.0" + }, + "dependencies": { + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true + } } }, "propagate": { @@ -23470,23 +23581,32 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } }, "supports-hyperlinks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz", - "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", "requires": { - "has-flag": "^2.0.0", - "supports-color": "^5.0.0" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" }, "dependencies": { "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } } } }, diff --git a/package.json b/package.json index 8e3bbd649c7..12de5d108bf 100644 --- a/package.json +++ b/package.json @@ -116,8 +116,8 @@ "jsonwebtoken": "^8.5.1", "leven": "^3.1.0", "lodash": "^4.17.21", - "marked": "^4.0.10", - "marked-terminal": "^3.3.0", + "marked": "^4.0.14", + "marked-terminal": "^5.1.1", "mime": "^2.5.2", "minimatch": "^3.0.4", "morgan": "^1.10.0", @@ -169,7 +169,7 @@ "@types/js-yaml": "^3.12.2", "@types/jsonwebtoken": "^8.3.8", "@types/lodash": "^4.14.149", - "@types/marked": "^4.0.1", + "@types/marked": "^4.0.3", "@types/marked-terminal": "^3.1.3", "@types/mocha": "^9.0.0", "@types/multer": "^1.4.3", diff --git a/scripts/build/Dockerfile b/scripts/build/Dockerfile index 8f5e04ec3cb..b3f14d0be71 100644 --- a/scripts/build/Dockerfile +++ b/scripts/build/Dockerfile @@ -9,4 +9,4 @@ RUN curl -fsSL --output hub.tgz https://github.com/github/hub/releases/download/ RUN tar --strip-components=2 -C /usr/bin -xf hub.tgz hub-linux-amd64-2.14.2/bin/hub # Upgrade npm to 8. -RUN npm install --global npm@8 +RUN npm install --global npm@8.7 diff --git a/scripts/firepit-builder/Dockerfile b/scripts/firepit-builder/Dockerfile index 554f355a893..32e473be367 100644 --- a/scripts/firepit-builder/Dockerfile +++ b/scripts/firepit-builder/Dockerfile @@ -9,7 +9,7 @@ RUN curl -fsSL --output hub.tgz https://github.com/github/hub/releases/download/ RUN tar --strip-components=2 -C /usr/bin -xf hub.tgz hub-linux-amd64-2.11.2/bin/hub # Upgrade npm to 8. -RUN npm install --global npm@8 +RUN npm install --global npm@8.7 # Create app directory WORKDIR /usr/src/app From 46c834ae8241a5f3367c27a902d2831e900ae0a1 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 26 Apr 2022 14:11:38 -0700 Subject: [PATCH 0266/1699] update superstatic (#4483) * upgrade superstatic to v8 for audit fixes * add changelog --- CHANGELOG.md | 1 + npm-shrinkwrap.json | 265 ++++---------------------------------------- package.json | 2 +- 3 files changed, 21 insertions(+), 247 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..71f76f03bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Updates `superstatic` to `v8` to fix audit issues. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 475eb826073..3a9cbe4c410 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -56,7 +56,7 @@ "semver": "^5.7.1", "stream-chain": "^2.2.4", "stream-json": "^1.7.3", - "superstatic": "^7.1.0", + "superstatic": "^8.0.0", "tar": "^6.1.11", "tcp-port-used": "^1.0.1", "tmp": "0.0.33", @@ -6178,45 +6178,6 @@ "flat": "cli.js" } }, - "node_modules/flat-arguments": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flat-arguments/-/flat-arguments-1.0.2.tgz", - "integrity": "sha1-m6p4Ct8FAfKC1ybJxqA426ROp28=", - "dependencies": { - "array-flatten": "^1.0.0", - "as-array": "^1.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isobject": "^3.0.0" - } - }, - "node_modules/flat-arguments/node_modules/as-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/as-array/-/as-array-1.0.0.tgz", - "integrity": "sha1-KKbu6qVynx9OyiBH316d4avaDtE=", - "dependencies": { - "lodash.isarguments": "2.4.x", - "lodash.isobject": "^2.4.1", - "lodash.values": "^2.4.1" - } - }, - "node_modules/flat-arguments/node_modules/as-array/node_modules/lodash.isarguments": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-2.4.1.tgz", - "integrity": "sha1-STGpwIJTrfCRrnyhkiWKlzh27Mo=" - }, - "node_modules/flat-arguments/node_modules/as-array/node_modules/lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", - "dependencies": { - "lodash._objecttypes": "~2.4.1" - } - }, - "node_modules/flat-arguments/node_modules/lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" - }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -7123,11 +7084,6 @@ "he": "bin/he" } }, - "node_modules/home-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/home-dir/-/home-dir-1.0.0.tgz", - "integrity": "sha1-KRfrRL3JByztqUJXlUOEfjAX/k4=" - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -8323,24 +8279,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=" - }, "node_modules/lodash._objecttypes": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=" }, - "node_modules/lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", - "dependencies": { - "lodash._objecttypes": "~2.4.1" - } - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -8384,11 +8327,6 @@ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -8422,16 +8360,6 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "node_modules/lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dependencies": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8464,14 +8392,6 @@ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, - "node_modules/lodash.values": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", - "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", - "dependencies": { - "lodash.keys": "~2.4.1" - } - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -9288,22 +9208,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/nash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/nash/-/nash-3.0.0.tgz", - "integrity": "sha512-M5SahEycXUmko3zOvsBkF6p94CWLhnyy9hfpQ9Qzp+rQkQ8D1OaTlfTl1OBWktq9Fak3oDXKU+ev7tiMaMu+1w==", - "dependencies": { - "async": "^1.3.0", - "flat-arguments": "^1.0.0", - "lodash": "^4.17.5", - "minimist": "^1.1.0" - } - }, - "node_modules/nash/node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -11291,14 +11195,6 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "node_modules/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "engines": { - "node": "6.* || >= 7.*" - } - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -12037,32 +11933,29 @@ } }, "node_modules/superstatic": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-7.1.0.tgz", - "integrity": "sha512-yBU8iw07nM3Bu4jFc8lnKwLey0cj61OaGmFJZcYC2X+kEpXVmXzERJ3OTAHZAESe1OTeNIuWadt81U5IULGGAA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-8.0.0.tgz", + "integrity": "sha512-PqlA2xuEwOlRZsknl58A/rZEmgCUcfWIFec0bn10wYE5/tbMhEbMXGHCYDppiXLXcuhGHyOp1IimM2hLqkLLuw==", "dependencies": { "basic-auth-connect": "^1.0.0", "chalk": "^1.1.3", + "commander": "^9.2.0", "compare-semver": "^1.0.0", "compression": "^1.7.0", "connect": "^3.6.2", "destroy": "^1.0.4", "fast-url-parser": "^1.1.3", - "fs-extra": "^8.1.0", "glob-slasher": "^1.0.1", - "home-dir": "^1.0.0", "is-url": "^1.2.2", "join-path": "^1.1.1", "lodash": "^4.17.19", "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.8.0", "router": "^1.3.1", - "rsvp": "^4.8.5", "string-length": "^1.0.0", "update-notifier": "^4.1.1" }, @@ -12070,7 +11963,7 @@ "superstatic": "bin/server" }, "engines": { - "node": ">= 8.6.0" + "node": ">= 12.20" }, "optionalDependencies": { "re2": "^1.15.8" @@ -12115,17 +12008,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/superstatic/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, + "node_modules/superstatic/node_modules/commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", "engines": { - "node": ">=6 <7 || >=8" + "node": "^12.20.0 || >=14" } }, "node_modules/superstatic/node_modules/has-flag": { @@ -18668,49 +18556,6 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, - "flat-arguments": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flat-arguments/-/flat-arguments-1.0.2.tgz", - "integrity": "sha1-m6p4Ct8FAfKC1ybJxqA426ROp28=", - "requires": { - "array-flatten": "^1.0.0", - "as-array": "^1.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isobject": "^3.0.0" - }, - "dependencies": { - "as-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/as-array/-/as-array-1.0.0.tgz", - "integrity": "sha1-KKbu6qVynx9OyiBH316d4avaDtE=", - "requires": { - "lodash.isarguments": "2.4.x", - "lodash.isobject": "^2.4.1", - "lodash.values": "^2.4.1" - }, - "dependencies": { - "lodash.isarguments": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-2.4.1.tgz", - "integrity": "sha1-STGpwIJTrfCRrnyhkiWKlzh27Mo=" - }, - "lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", - "requires": { - "lodash._objecttypes": "~2.4.1" - } - } - } - }, - "lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" - } - } - }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -19440,11 +19285,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "home-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/home-dir/-/home-dir-1.0.0.tgz", - "integrity": "sha1-KRfrRL3JByztqUJXlUOEfjAX/k4=" - }, "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -20387,24 +20227,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=" - }, "lodash._objecttypes": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=" }, - "lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", - "requires": { - "lodash._objecttypes": "~2.4.1" - } - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -20448,11 +20275,6 @@ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, "lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -20486,16 +20308,6 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -20528,14 +20340,6 @@ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, - "lodash.values": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", - "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", - "requires": { - "lodash.keys": "~2.4.1" - } - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -21154,24 +20958,6 @@ "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, - "nash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/nash/-/nash-3.0.0.tgz", - "integrity": "sha512-M5SahEycXUmko3zOvsBkF6p94CWLhnyy9hfpQ9Qzp+rQkQ8D1OaTlfTl1OBWktq9Fak3oDXKU+ev7tiMaMu+1w==", - "requires": { - "async": "^1.3.0", - "flat-arguments": "^1.0.0", - "lodash": "^4.17.5", - "minimist": "^1.1.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - } - } - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -22707,11 +22493,6 @@ } } }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" - }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -23321,33 +23102,30 @@ } }, "superstatic": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-7.1.0.tgz", - "integrity": "sha512-yBU8iw07nM3Bu4jFc8lnKwLey0cj61OaGmFJZcYC2X+kEpXVmXzERJ3OTAHZAESe1OTeNIuWadt81U5IULGGAA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-8.0.0.tgz", + "integrity": "sha512-PqlA2xuEwOlRZsknl58A/rZEmgCUcfWIFec0bn10wYE5/tbMhEbMXGHCYDppiXLXcuhGHyOp1IimM2hLqkLLuw==", "requires": { "basic-auth-connect": "^1.0.0", "chalk": "^1.1.3", + "commander": "^9.2.0", "compare-semver": "^1.0.0", "compression": "^1.7.0", "connect": "^3.6.2", "destroy": "^1.0.4", "fast-url-parser": "^1.1.3", - "fs-extra": "^8.1.0", "glob-slasher": "^1.0.1", - "home-dir": "^1.0.0", "is-url": "^1.2.2", "join-path": "^1.1.1", "lodash": "^4.17.19", "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.8.0", "re2": "^1.15.8", "router": "^1.3.1", - "rsvp": "^4.8.5", "string-length": "^1.0.0", "update-notifier": "^4.1.1" }, @@ -23382,15 +23160,10 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } + "commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==" }, "has-flag": { "version": "4.0.0", diff --git a/package.json b/package.json index 73c63462835..f8440a48508 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "semver": "^5.7.1", "stream-chain": "^2.2.4", "stream-json": "^1.7.3", - "superstatic": "^7.1.0", + "superstatic": "^8.0.0", "tar": "^6.1.11", "tcp-port-used": "^1.0.1", "tmp": "0.0.33", From 673ee66a6665ec1cf9320903cc71e49ce085e02f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 26 Apr 2022 15:55:58 -0700 Subject: [PATCH 0267/1699] Expose TQ functions as normal HTTPS functions. (#4469) While TQ functions aren't fully supported by the Emulator due to lack of Cloud Tasks Emulator, it should be pretty useful to expose TQ functions as normal HTTPS function that users can hit to execute during development. --- src/emulator/functionsEmulatorShared.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index e96ff46d07e..4f977488b74 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -208,6 +208,9 @@ export function emulatedFunctionsFromEndpoints( eventType: endpoint.blockingTrigger.eventType, options: endpoint.blockingTrigger.options || {}, }; + } else if (backend.isTaskQueueTriggered(endpoint)) { + // Just expose TQ trigger as HTTPS. Useful for debugging. + def.httpsTrigger = {}; } else { // All other trigger types are not supported by the emulator // We leave both eventTrigger and httpTrigger attributes empty From ad6ec5aadac487008b1b8f484ce07d5c665a1eb0 Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Wed, 27 Apr 2022 10:03:39 -0400 Subject: [PATCH 0268/1699] Prepare ext:* commands for v11 with breaking changes (#4466) * showPostDeprecationNotice * Update ext-install.ts * Update all commands * format * add doc links --- src/commands/ext-configure.ts | 207 +++++------------ src/commands/ext-install.ts | 224 +------------------ src/commands/ext-uninstall.ts | 138 +----------- src/commands/ext-update.ts | 405 +++++++--------------------------- src/extensions/manifest.ts | 26 +-- 5 files changed, 150 insertions(+), 850 deletions(-) diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 8ea557307d0..128881564f8 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -1,8 +1,5 @@ -import * as _ from "lodash"; -import * as clc from "cli-color"; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const { marked } = require("marked"); -import * as ora from "ora"; import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; @@ -37,7 +34,6 @@ export default new Command("ext:configure ") .description("configure an existing extension instance") .withForce() .option("--params ", "path of params file with .env format.") - .option("--local", "save to firebase.json rather than directly install to a Firebase project") .before(requirePermissions, [ "firebaseextensions.instances.update", "firebaseextensions.instances.get", @@ -47,156 +43,73 @@ export default new Command("ext:configure ") .action(async (instanceId: string, options: Options) => { const projectId = getProjectId(options); - if (options.local) { - if (options.nonInteractive) { - throw new FirebaseError( - `Command not supported in non-interactive mode, edit ./extensions/${instanceId}.env directly instead` - ); - } - - const config = manifest.loadConfig(options); - - const refOrPath = manifest.getInstanceTarget(instanceId, config); - const isLocalSource = isLocalPath(refOrPath); - - let spec: extensionsApi.ExtensionSpec; - if (isLocalSource) { - const source = await createSourceFromLocation(needProjectId({ projectId }), refOrPath); - spec = source.spec; - } else { - const extensionVersion = await extensionsApi.getExtensionVersion(refOrPath); - spec = extensionVersion.spec; - } + if (options.nonInteractive) { + throw new FirebaseError( + `Command not supported in non-interactive mode, edit ./extensions/${instanceId}.env directly instead. ` + + `See https://firebase.google.com/docs/extensions/manifest for more details.` + ); + } - const oldParamValues = manifest.readInstanceParam({ - instanceId, - projectDir: config.projectDir, - }); + const config = manifest.loadConfig(options); - const [immutableParams, tbdParams] = partition( - spec.params, - (param) => param.immutable ?? false - ); - infoImmutableParams(immutableParams, oldParamValues); + const refOrPath = manifest.getInstanceTarget(instanceId, config); + const isLocalSource = isLocalPath(refOrPath); - // Ask for mutable param values from user. - paramHelper.setNewDefaults(tbdParams, oldParamValues); - const mutableParamsBindingOptions = await paramHelper.getParams({ - projectId, - paramSpecs: tbdParams, - nonInteractive: false, - paramsEnvPath: (options.params ?? "") as string, - instanceId, - reconfiguring: true, - }); - - // Merge with old immutable params. - const newParamOptions = { - ...buildBindingOptionsWithBaseValue(oldParamValues), - ...mutableParamsBindingOptions, - }; - - await manifest.writeToManifest( - [ - { - instanceId, - ref: !isLocalSource ? refs.parse(refOrPath) : undefined, - localPath: isLocalSource ? refOrPath : undefined, - params: newParamOptions, - extensionSpec: spec, - }, - ], - config, - { - nonInteractive: false, - force: true, // Skip asking for permission again - } - ); - manifest.showPreviewWarning(); - return; + let spec: extensionsApi.ExtensionSpec; + if (isLocalSource) { + const source = await createSourceFromLocation(needProjectId({ projectId }), refOrPath); + spec = source.spec; + } else { + const extensionVersion = await extensionsApi.getExtensionVersion(refOrPath); + spec = extensionVersion.spec; } - // TODO(b/220900194): Remove everything below and make --local the default behavior. - const spinner = ora( - `Configuring ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...` - ); - try { - let existingInstance: extensionsApi.ExtensionInstance; - try { - existingInstance = await extensionsApi.getInstance( - needProjectId({ projectId }), - instanceId - ); - } catch (err: any) { - if (err.status === 404) { - return utils.reject( - `No extension instance ${instanceId} found in project ${projectId}.`, - { - exit: 1, - } - ); - } - throw err; - } - const paramSpecWithNewDefaults = - paramHelper.getParamsWithCurrentValuesAsDefaults(existingInstance); - const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => param.immutable); - - const paramBindingOptions = await paramHelper.getParams({ - projectId, - paramSpecs: paramSpecWithNewDefaults, - nonInteractive: options.nonInteractive, - paramsEnvPath: options.params as string, - instanceId, - reconfiguring: true, - }); - const paramBindings = getBaseParamBindings(paramBindingOptions); - if (immutableParams.length) { - const plural = immutableParams.length > 1; - logger.info(`The following param${plural ? "s are" : " is"} immutable:`); - for (const { param } of immutableParams) { - const value = _.get(existingInstance, `config.params.${param}`); - logger.info(`param: ${param}, value: ${value}`); - paramBindings[param] = value; - } - logger.info( - (plural - ? "To set different values for these params" - : "To set a different value for this param") + - ", uninstall the extension, then install a new instance of this extension." - ); - } + const oldParamValues = manifest.readInstanceParam({ + instanceId, + projectDir: config.projectDir, + }); - spinner.start(); - const res = await extensionsApi.configureInstance({ - projectId: needProjectId({ projectId }), - instanceId, - params: paramBindings, - }); - spinner.stop(); - utils.logLabeledSuccess(logPrefix, `successfully configured ${clc.bold(instanceId)}.`); - utils.logLabeledBullet( - logPrefix, - marked( - `You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl( - needProjectId({ projectId }), - `/extensions/instances/${instanceId}?tab=config` - )}` - ) - ); - manifest.showDeprecationWarning(); - return res; - } catch (err: any) { - if (spinner.isSpinning) { - spinner.fail(); - } - if (!(err instanceof FirebaseError)) { - throw new FirebaseError(`Error occurred while configuring the instance: ${err.message}`, { - original: err, - }); + const [immutableParams, tbdParams] = partition( + spec.params, + (param) => param.immutable ?? false + ); + infoImmutableParams(immutableParams, oldParamValues); + + // Ask for mutable param values from user. + paramHelper.setNewDefaults(tbdParams, oldParamValues); + const mutableParamsBindingOptions = await paramHelper.getParams({ + projectId, + paramSpecs: tbdParams, + nonInteractive: false, + paramsEnvPath: (options.params ?? "") as string, + instanceId, + reconfiguring: true, + }); + + // Merge with old immutable params. + const newParamOptions = { + ...buildBindingOptionsWithBaseValue(oldParamValues), + ...mutableParamsBindingOptions, + }; + + await manifest.writeToManifest( + [ + { + instanceId, + ref: !isLocalSource ? refs.parse(refOrPath) : undefined, + localPath: isLocalSource ? refOrPath : undefined, + params: newParamOptions, + extensionSpec: spec, + }, + ], + config, + { + nonInteractive: false, + force: true, // Skip asking for permission again } - throw err; - } + ); + manifest.showPostDeprecationNotice(); + return; }); function infoImmutableParams( diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 7245c897196..7ad71381b4e 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -1,21 +1,14 @@ import * as clc from "cli-color"; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const { marked } = require("marked"); -import * as ora from "ora"; import TerminalRenderer = require("marked-terminal"); -import * as askUserForConsent from "../extensions/askUserForConsent"; import { displayExtInfo } from "../extensions/displayExtensionInfo"; -import { displayNode10CreateBillingNotice } from "../extensions/billingMigrationHelper"; -import { enableBilling } from "../extensions/checkProjectBilling"; -import { checkBillingEnabled } from "../gcp/cloudbilling"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; import { FirebaseError } from "../error"; import { getProjectId, needProjectId } from "../projectUtils"; import * as extensionsApi from "../extensions/extensionsApi"; -import * as secretsUtils from "../extensions/secretsUtils"; -import * as provisioningHelper from "../extensions/provisioningHelper"; import * as refs from "../extensions/refs"; import { displayWarningPrompts } from "../extensions/warnings"; import * as paramHelper from "../extensions/paramHelper"; @@ -23,26 +16,21 @@ import { confirm, createSourceFromLocation, ensureExtensionsApiEnabled, - instanceIdExists, logPrefix, promptForOfficialExtension, - promptForRepeatInstance, promptForValidInstanceId, diagnoseAndFixProject, isUrlPath, isLocalPath, canonicalizeRefInput, } from "../extensions/extensionsHelper"; -import { update } from "../extensions/updateHelper"; import { getRandomString } from "../extensions/utils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; import { track } from "../track"; -import { logger } from "../logger"; import { previews } from "../previews"; import { Options } from "../options"; import * as manifest from "../extensions/manifest"; -import { getBaseParamBindings, ParamBindingOptions } from "../extensions/paramHelper"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -62,7 +50,6 @@ export default new Command("ext:install [extensionName]") .withForce() // TODO(b/221037520): Deprecate the params flag then remove it in the next breaking version. .option("--params ", "name of params variables file with .env format.") - .option("--local", "save to firebase.json rather than directly install to a Firebase project") .before(requirePermissions, ["firebaseextensions.instances.create"]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") @@ -146,35 +133,10 @@ export default new Command("ext:install [extensionName]") ); } - if (options.local) { - try { - return installToManifest({ - paramsEnvPath, - projectId, - extensionName, - source, - extVersion: extensionVersion, - nonInteractive: options.nonInteractive, - force: options.force, - }); - } catch (err: any) { - if (!(err instanceof FirebaseError)) { - throw new FirebaseError( - `Error occurred saving the extension to manifest: ${err.message}`, - { - original: err, - } - ); - } - throw err; - } - } - - // TODO(b/220900194): Remove this and make --local the default behavior. try { - return installExtension({ + return installToManifest({ paramsEnvPath, - projectId: projectId, + projectId, extensionName, source, extVersion: extensionVersion, @@ -183,7 +145,7 @@ export default new Command("ext:install [extensionName]") }); } catch (err: any) { if (!(err instanceof FirebaseError)) { - throw new FirebaseError(`Error occurred installing the extension: ${err.message}`, { + throw new FirebaseError(`Error occurred saving the extension to manifest: ${err.message}`, { original: err, }); } @@ -262,183 +224,5 @@ async function installToManifest(options: InstallExtensionOptions): Promise { - const { extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } = options; - const projectId = needProjectId({ projectId: options.projectId }); - - const spec = source?.spec || extVersion?.spec; - if (!spec) { - throw new FirebaseError( - `Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.` - ); - } - const spinner = ora(); - try { - await provisioningHelper.checkProductsProvisioned(projectId, spec); - - const usesSecrets = secretsUtils.usesSecrets(spec); - if (spec.billingRequired || usesSecrets) { - const enabled = await checkBillingEnabled(projectId); - if (!enabled && nonInteractive) { - throw new FirebaseError( - `This extension requires the Blaze plan, but project ${projectId} is not on the Blaze plan. ` + - marked( - "Please visit https://console.cloud.google.com/billing/linkedaccount?project=${projectId} to upgrade your project." - ) - ); - } else if (!enabled) { - await displayNode10CreateBillingNotice(spec, false); - await enableBilling(projectId); - } else { - await displayNode10CreateBillingNotice(spec, !nonInteractive); - } - } - const apis = spec.apis || []; - if (usesSecrets) { - apis.push({ - apiName: "secretmanager.googleapis.com", - reason: `To access and manage secrets which are used by this extension. By using this product you agree to the terms and conditions of the following license: https://console.cloud.google.com/tos?id=cloud&project=${projectId}`, - }); - } - if (apis.length) { - askUserForConsent.displayApis(spec.displayName || spec.name, projectId, apis); - const consented = await confirm({ nonInteractive, force, default: true }); - if (!consented) { - throw new FirebaseError( - "Without explicit consent for the APIs listed, we cannot deploy this extension." - ); - } - } - if (usesSecrets) { - await secretsUtils.ensureSecretManagerApiEnabled(options); - } - - const roles = spec.roles ? spec.roles.map((role: extensionsApi.Role) => role.role) : []; - if (roles.length) { - await askUserForConsent.displayRoles(spec.displayName || spec.name, projectId, roles); - const consented = await confirm({ nonInteractive, force, default: true }); - if (!consented) { - throw new FirebaseError( - "Without explicit consent for the roles listed, we cannot deploy this extension." - ); - } - } - let instanceId = spec.name; - - let choice: "updateExisting" | "installNew" | "cancel"; - const anotherInstanceExists = await instanceIdExists(projectId, instanceId); - if (anotherInstanceExists) { - if (!nonInteractive) { - choice = await promptForRepeatInstance(projectId, spec.name); - } else if (nonInteractive && force) { - choice = "updateExisting"; - } else { - throw new FirebaseError( - `An extension with the ID '${clc.bold( - extensionName - )}' already exists in the project '${clc.bold(projectId)}'.` + - ` To update or reconfigure this instance instead, rerun this command with the --force flag.` - ); - } - } else { - choice = "installNew"; - } - let paramBindingOptions: { [key: string]: ParamBindingOptions }; - let paramBindings: Record; - switch (choice) { - case "installNew": - instanceId = await promptForValidInstanceId(`${instanceId}-${getRandomString(4)}`); - paramBindingOptions = await paramHelper.getParams({ - projectId, - paramSpecs: spec.params, - nonInteractive, - paramsEnvPath, - instanceId, - }); - paramBindings = getBaseParamBindings(paramBindingOptions); - spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes..."; - spinner.start(); - await extensionsApi.createInstance({ - projectId, - instanceId, - extensionSource: source, - extensionVersionRef: extVersion?.ref, - params: paramBindings, - }); - spinner.stop(); - utils.logLabeledSuccess( - logPrefix, - `Successfully installed your instance of ${clc.bold(spec.displayName || spec.name)}! ` + - `Its Instance ID is ${clc.bold(instanceId)}.` - ); - break; - case "updateExisting": - paramBindingOptions = await paramHelper.getParams({ - projectId, - paramSpecs: spec.params, - nonInteractive, - paramsEnvPath, - instanceId, - }); - paramBindings = getBaseParamBindings(paramBindingOptions); - spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes..."; - spinner.start(); - await update({ - projectId, - instanceId, - source, - extRef: extVersion?.ref, - params: paramBindings, - }); - spinner.stop(); - utils.logLabeledSuccess( - logPrefix, - `Successfully updated your instance of ${clc.bold(spec.displayName || spec.name)}! ` + - `Its Instance ID is ${clc.bold(instanceId)}.` - ); - break; - case "cancel": - return; - } - utils.logLabeledBullet( - logPrefix, - marked( - "Go to the Firebase console to view instructions for using your extension, " + - `which may include some required post-installation tasks: ${utils.consoleUrl( - projectId, - `/extensions/instances/${instanceId}?tab=usage` - )}` - ) - ); - logger.info( - marked( - "You can run `firebase ext` to view available Firebase Extensions commands, " + - "including those to update, reconfigure, or delete your installed extension." - ) - ); - manifest.showDeprecationWarning(); - } catch (err: any) { - if (spinner.isSpinning) { - spinner.fail(); - } - if (err instanceof FirebaseError) { - throw err; - } - throw new FirebaseError(`Error occurred installing extension: ${err.message}`, { - original: err, - }); - } + manifest.showPostDeprecationNotice(); } diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 8ef8ea9324f..2038970f77c 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -1,26 +1,11 @@ -import * as _ from "lodash"; -import * as clc from "cli-color"; -import * as ora from "ora"; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const { marked } = require("marked"); import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; -import { FirebaseError } from "../error"; -import { needProjectId } from "../projectUtils"; -import * as extensionsApi from "../extensions/extensionsApi"; -import * as secretsUtils from "../extensions/secretsUtils"; -import { - ensureExtensionsApiEnabled, - logPrefix, - resourceTypeToNiceName, - diagnoseAndFixProject, -} from "../extensions/extensionsHelper"; -import { promptOnce } from "../prompt"; +import { ensureExtensionsApiEnabled, diagnoseAndFixProject } from "../extensions/extensionsHelper"; import { requirePermissions } from "../requirePermissions"; -import * as utils from "../utils"; -import { logger } from "../logger"; import * as manifest from "../extensions/manifest"; import { Options } from "../options"; @@ -31,125 +16,12 @@ marked.setOptions({ export default new Command("ext:uninstall ") .description("uninstall an extension that is installed in your Firebase project by instance ID") .withForce() - .option( - "--local", - "remove from firebase.json rather than directly uninstall from a Firebase project" - ) .before(requirePermissions, ["firebaseextensions.instances.delete"]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) - .action(async (instanceId: string, options: Options) => { - if (options.local) { - const config = manifest.loadConfig(options); - manifest.removeFromManifest(instanceId, config); - manifest.showPreviewWarning(); - return; - } - - // TODO(b/220900194): Remove everything below and make --local the default behavior. - const projectId = needProjectId(options); - let instance; - try { - instance = await extensionsApi.getInstance(projectId, instanceId); - } catch (err: any) { - if (err.status === 404) { - return utils.reject(`No extension instance ${instanceId} in project ${projectId}.`, { - exit: 1, - }); - } - throw err; - } - - // Special case for pubsub-stream-bigquery extension - if (_.get(instance, "config.source.spec.name") === "pubsub-stream-bigquery") { - return consoleUninstallOnly(projectId, instanceId); - } - - if (!options.force) { - const serviceAccountMessage = `Uninstalling deletes the service account used by this extension instance:\n${clc.bold( - instance.serviceAccountEmail - )}\n\n`; - const managedSecrets = await secretsUtils.getManagedSecrets(instance); - const resourcesMessage = _.get(instance, "config.source.spec.resources", []).length - ? "Uninstalling deletes all extension resources created for this extension instance:\n" + - instance.config.source.spec.resources - .map((resource: extensionsApi.Resource) => - clc.bold( - `- ${resourceTypeToNiceName[resource.type] || resource.type}: ${resource.name} \n` - ) - ) - .join("") + - managedSecrets - .map(secretsUtils.prettySecretName) - .map((s) => clc.bold(`- Secret: ${s}\n`)) - .join("") + - "\n" - : ""; - const artifactsMessage = - `The following ${clc.bold("will not")} be deleted:\n` + - "Any artifacts (for example, stored images) created by this extension instance.\n" + - "Any other project resources with which this extension instance interacted.\n"; - - const extensionDeletionMessage = - `Here's what will happen when you uninstall ${clc.bold(instanceId)} from project ${clc.bold( - projectId - )}. Be aware that this cannot be undone.\n\n` + - `${serviceAccountMessage}` + - `${resourcesMessage}` + - `${artifactsMessage}`; - - logger.info(extensionDeletionMessage); - const confirmedExtensionDeletion = await promptOnce({ - type: "confirm", - default: true, - message: "Are you sure that you wish to uninstall this extension?", - }); - if (!confirmedExtensionDeletion) { - return utils.reject("Command aborted.", { exit: 1 }); - } - } - - const spinner = ora( - ` ${clc.green.bold(logPrefix)}: uninstalling ${clc.bold( - instanceId - )}. This usually takes 1 to 2 minutes...` - ); - spinner.start(); - try { - spinner.info(); - spinner.text = ` ${clc.green.bold(logPrefix)}: deleting your extension instance's resources.`; - spinner.start(); - await extensionsApi.deleteInstance(projectId, instanceId); - spinner.succeed( - ` ${clc.green.bold(logPrefix)}: deleted your extension instance's resources.` - ); - } catch (err: any) { - if (spinner.isSpinning) { - spinner.fail(); - } - if (err instanceof FirebaseError) { - throw err; - } - return utils.reject(`Error occurred uninstalling extension ${instanceId}`, { original: err }); - } - utils.logLabeledSuccess(logPrefix, `uninstalled ${clc.bold(instanceId)}`); - manifest.showDeprecationWarning(); + .action((instanceId: string, options: Options) => { + const config = manifest.loadConfig(options); + manifest.removeFromManifest(instanceId, config); + manifest.showPostDeprecationNotice(); }); - -/** - * We do not currently support uninstalling extensions that require additional uninstall steps to be taken in the CLI. Direct them to the Console to uninstall the extension. - * - * @param projectId ID of the user's project - * @param instanceId ID of the extension instance - * @return a void Promise - */ -function consoleUninstallOnly(projectId: string, instanceId: string): Promise { - const instanceURL = `https://console.firebase.google.com/project/${projectId}/extensions/instances/${instanceId}`; - const consoleUninstall = - "This extension has additional uninstall checks that are not currently supported by the CLI, and can only be uninstalled through the Firebase Console. " + - `Please visit **[${instanceURL}](${instanceURL})** to uninstall this extension.`; - logger.info("\n"); - utils.logLabeledWarning(logPrefix, marked(consoleUninstall)); - return Promise.resolve(); -} diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index a5779641f41..935e4033f9d 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -1,19 +1,12 @@ import * as clc from "cli-color"; -import * as _ from "lodash"; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const { marked } = require("marked"); -import * as ora from "ora"; import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; import { FirebaseError } from "../error"; -import { displayNode10UpdateBillingNotice } from "../extensions/billingMigrationHelper"; -import { enableBilling } from "../extensions/checkProjectBilling"; -import { checkBillingEnabled } from "../gcp/cloudbilling"; import * as extensionsApi from "../extensions/extensionsApi"; -import * as secretsUtils from "../extensions/secretsUtils"; -import * as provisioningHelper from "../extensions/provisioningHelper"; import { ensureExtensionsApiEnabled, logPrefix, @@ -24,17 +17,7 @@ import { isLocalPath, } from "../extensions/extensionsHelper"; import * as paramHelper from "../extensions/paramHelper"; -import { - displayChanges, - update, - UpdateOptions, - updateFromLocalSource, - updateFromUrlSource, - updateToVersionFromPublisherSource, - updateFromPublisherSource, - getExistingSourceOrigin, - inferUpdateSource, -} from "../extensions/updateHelper"; +import { inferUpdateSource } from "../extensions/updateHelper"; import * as refs from "../extensions/refs"; import { getProjectId, needProjectId } from "../projectUtils"; import { requirePermissions } from "../requirePermissions"; @@ -65,332 +48,96 @@ export default new Command("ext:update [updateSource]") .before(diagnoseAndFixProject) .withForce() .option("--params ", "name of params variables file with .env format.") - .option( - "--local", - "save the update to firebase.json rather than directly update an existing Extension instance on a Firebase project" - ) .action(async (instanceId: string, updateSource: string, options: Options) => { - if (options.local) { - const projectId = getProjectId(options); - const config = manifest.loadConfig(options); - - const oldRefOrPath = manifest.getInstanceTarget(instanceId, config); - if (isLocalPath(oldRefOrPath)) { - throw new FirebaseError( - `Updating an extension with local source is not neccessary. ` + - `Rerun "firebase deploy" or restart the emulator after making changes to your local extension source. ` + - `If you've edited the extension param spec, you can edit an extension instance's params ` + - `interactively by running "firebase ext:configure --local {instance-id}"` - ); - } - - const oldRef = manifest.getInstanceRef(instanceId, config); - const oldExtensionVersion = await extensionsApi.getExtensionVersion( - refs.toExtensionVersionRef(oldRef) + const projectId = getProjectId(options); + const config = manifest.loadConfig(options); + + const oldRefOrPath = manifest.getInstanceTarget(instanceId, config); + if (isLocalPath(oldRefOrPath)) { + throw new FirebaseError( + `Updating an extension with local source is not neccessary. ` + + `Rerun "firebase deploy" or restart the emulator after making changes to your local extension source. ` + + `If you've edited the extension param spec, you can edit an extension instance's params ` + + `interactively by running "firebase ext:configure --local {instance-id}"` ); - updateSource = inferUpdateSource(updateSource, refs.toExtensionRef(oldRef)); - - const newSourceOrigin = getSourceOrigin(updateSource); - if ( - ![SourceOrigin.PUBLISHED_EXTENSION, SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes( - newSourceOrigin - ) - ) { - throw new FirebaseError(`Only updating to a published extension version is allowed`); - } + } - const newExtensionVersion = await extensionsApi.getExtensionVersion(updateSource); + const oldRef = manifest.getInstanceRef(instanceId, config); + const oldExtensionVersion = await extensionsApi.getExtensionVersion( + refs.toExtensionVersionRef(oldRef) + ); + updateSource = inferUpdateSource(updateSource, refs.toExtensionRef(oldRef)); + + const newSourceOrigin = getSourceOrigin(updateSource); + if ( + ![SourceOrigin.PUBLISHED_EXTENSION, SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes( + newSourceOrigin + ) + ) { + throw new FirebaseError(`Only updating to a published extension version is allowed`); + } - if (oldExtensionVersion.ref === newExtensionVersion.ref) { - utils.logLabeledBullet( - logPrefix, - `${clc.bold(instanceId)} is already up to date. Its version is ${clc.bold( - newExtensionVersion.ref - )}.` - ); - return; - } + const newExtensionVersion = await extensionsApi.getExtensionVersion(updateSource); + if (oldExtensionVersion.ref === newExtensionVersion.ref) { utils.logLabeledBullet( logPrefix, - `Updating ${clc.bold(instanceId)} from version ${clc.bold( - oldExtensionVersion.ref - )} to version ${clc.bold(newExtensionVersion.ref)}.` - ); - - if ( - !(await confirm({ - nonInteractive: options.nonInteractive, - force: options.force, - default: false, - })) - ) { - utils.logLabeledBullet(logPrefix, "Update aborted."); - return; - } - - const oldParamValues = manifest.readInstanceParam({ - instanceId, - projectDir: config.projectDir, - }); - - const newParamBindingOptions = await paramHelper.getParamsForUpdate({ - spec: oldExtensionVersion.spec, - newSpec: newExtensionVersion.spec, - currentParams: oldParamValues, - projectId, - paramsEnvPath: (options.params ?? "") as string, - nonInteractive: options.nonInteractive, - instanceId, - }); - - await manifest.writeToManifest( - [ - { - instanceId, - ref: refs.parse(newExtensionVersion.ref), - params: newParamBindingOptions, - extensionSpec: newExtensionVersion.spec, - extensionVersion: newExtensionVersion, - }, - ], - config, - { - nonInteractive: options.nonInteractive, - force: true, // Skip asking for permission again - } + `${clc.bold(instanceId)} is already up to date. Its version is ${clc.bold( + newExtensionVersion.ref + )}.` ); - manifest.showPreviewWarning(); return; } - const spinner = ora(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`); - try { - const projectId = needProjectId(options); - let existingInstance: extensionsApi.ExtensionInstance; - try { - existingInstance = await extensionsApi.getInstance(projectId, instanceId); - } catch (err: any) { - if (err.status === 404) { - throw new FirebaseError( - `Extension instance '${clc.bold(instanceId)}' not found in project '${clc.bold( - projectId - )}'.` - ); - } - throw err; - } - const existingSpec: extensionsApi.ExtensionSpec = existingInstance.config.source.spec; - if (existingInstance.config.source.state === "DELETED") { - throw new FirebaseError( - `Instance '${clc.bold( - instanceId - )}' cannot be updated anymore because the underlying extension was unpublished from Firebase's registry of extensions. Going forward, you will only be able to re-configure or uninstall this instance.` - ); - } - const existingParams = existingInstance.config.params; - const existingSource = existingInstance.config.source.name; - - if (existingInstance.config.extensionRef) { - // User may provide abbreviated syntax in the update command (for example, providing no update source or just a semver) - // Decipher the explicit update source from the abbreviated syntax. - updateSource = inferUpdateSource(updateSource, existingInstance.config.extensionRef); - } - let newSourceName: string; - const existingSourceOrigin = await getExistingSourceOrigin( - projectId, - instanceId, - existingSpec.name, - existingSource - ); - const newSourceOrigin = getSourceOrigin(updateSource); - const validUpdate = isValidUpdate(existingSourceOrigin, newSourceOrigin); - if (!validUpdate) { - throw new FirebaseError( - `Cannot update from a(n) ${existingSourceOrigin} to a(n) ${newSourceOrigin}. Please provide a new source that is a(n) ${existingSourceOrigin} and try again.` - ); - } - // TODO: remove "falls through" once producer and registry experience are released - switch (newSourceOrigin) { - case SourceOrigin.LOCAL: - if (previews.extdev) { - newSourceName = await updateFromLocalSource( - projectId, - instanceId, - updateSource, - existingSpec - ); - break; - } - // falls through - // eslint-disable-next-line no-fallthrough - case SourceOrigin.URL: - if (previews.extdev) { - newSourceName = await updateFromUrlSource( - projectId, - instanceId, - updateSource, - existingSpec - ); - break; - } - case SourceOrigin.PUBLISHED_EXTENSION_VERSION: - newSourceName = await updateToVersionFromPublisherSource( - projectId, - instanceId, - updateSource, - existingSpec - ); - break; - case SourceOrigin.PUBLISHED_EXTENSION: - newSourceName = await updateFromPublisherSource( - projectId, - instanceId, - updateSource, - existingSpec - ); - break; - default: - throw new FirebaseError(`Unknown source '${clc.bold(updateSource)}.'`); - } - - if ( - !(await confirm({ - nonInteractive: options.nonInteractive, - force: options.force, - default: true, - })) - ) { - throw new FirebaseError(`Update cancelled.`); - } - - // TODO(fix): currently exploiting an oversight in this method call to make calls to both - // the getExtensionSource endpoint and getExtenionVersion endpoint. Only ExtensionSources - // are returned by this method, so in the case of a getExtensionVersion call, only overlapping - // fields like name and ExtensionSpec are surfaced. - // We should fix this. - const newSource = await extensionsApi.getSource(newSourceName); - const newSpec = newSource.spec; - - if ( - ![SourceOrigin.LOCAL, SourceOrigin.URL].includes(newSourceOrigin) && - existingSpec.version === newSpec.version - ) { - utils.logLabeledBullet( - logPrefix, - `${clc.bold(instanceId)} is already up to date. Its version is ${clc.bold( - existingSpec.version - )}.` - ); - const retry = await confirm({ - nonInteractive: options.nonInteractive, - force: options.force, - default: false, - }); - if (!retry) { - utils.logLabeledBullet(logPrefix, "Update aborted."); - return; - } - } + utils.logLabeledBullet( + logPrefix, + `Updating ${clc.bold(instanceId)} from version ${clc.bold( + oldExtensionVersion.ref + )} to version ${clc.bold(newExtensionVersion.ref)}.` + ); - await displayChanges({ - spec: existingSpec, - newSpec: newSpec, + if ( + !(await confirm({ nonInteractive: options.nonInteractive, force: options.force, - }); - - await provisioningHelper.checkProductsProvisioned(projectId, newSpec); + default: false, + })) + ) { + utils.logLabeledBullet(logPrefix, "Update aborted."); + return; + } - const usesSecrets = secretsUtils.usesSecrets(newSpec); - if (newSpec.billingRequired || usesSecrets) { - const enabled = await checkBillingEnabled(projectId); - displayNode10UpdateBillingNotice(existingSpec, newSpec); - if ( - !(await confirm({ - nonInteractive: options.nonInteractive, - force: options.force, - default: true, - })) - ) { - throw new FirebaseError("Update cancelled."); - } - if (!enabled) { - if (!options.nonInteractive) { - await enableBilling(projectId); - } else { - throw new FirebaseError( - "The extension requires your project to be upgraded to the Blaze plan. " + - "To run this command in non-interactive mode, first upgrade your project: " + - marked( - `https://console.cloud.google.com/billing/linkedaccount?project=${projectId}` - ) - ); - } - } - if (usesSecrets) { - await secretsUtils.ensureSecretManagerApiEnabled(options); - } - } - // make a copy of existingParams -- they get overridden by paramHelper.getParamsForUpdate - const oldParamValues = { ...existingParams }; - const newParamBindings = await paramHelper.getParamsForUpdate({ - spec: existingSpec, - newSpec, - currentParams: existingParams, - projectId, - paramsEnvPath: (options.params ?? "") as string, + const oldParamValues = manifest.readInstanceParam({ + instanceId, + projectDir: config.projectDir, + }); + + const newParamBindingOptions = await paramHelper.getParamsForUpdate({ + spec: oldExtensionVersion.spec, + newSpec: newExtensionVersion.spec, + currentParams: oldParamValues, + projectId, + paramsEnvPath: (options.params ?? "") as string, + nonInteractive: options.nonInteractive, + instanceId, + }); + + await manifest.writeToManifest( + [ + { + instanceId, + ref: refs.parse(newExtensionVersion.ref), + params: newParamBindingOptions, + extensionSpec: newExtensionVersion.spec, + extensionVersion: newExtensionVersion, + }, + ], + config, + { nonInteractive: options.nonInteractive, - instanceId, - }); - const newParams = paramHelper.getBaseParamBindings(newParamBindings); - - spinner.start(); - const updateOptions: UpdateOptions = { - projectId, - instanceId, - }; - if (newSourceName.includes("publisher")) { - updateOptions.extRef = refs.toExtensionVersionRef(refs.parse(newSourceName)); - } else { - updateOptions.source = newSource; - } - if (!_.isEqual(newParams, oldParamValues)) { - updateOptions.params = newParams; - } - await update(updateOptions); - spinner.stop(); - utils.logLabeledSuccess(logPrefix, `successfully updated ${clc.bold(instanceId)}.`); - utils.logLabeledBullet( - logPrefix, - marked( - `You can view your updated instance in the Firebase console: ${utils.consoleUrl( - projectId, - `/extensions/instances/${instanceId}?tab=usage` - )}` - ) - ); - manifest.showDeprecationWarning(); - } catch (err: any) { - if (spinner.isSpinning) { - spinner.fail(); - } - if (!(err instanceof FirebaseError)) { - throw new FirebaseError(`Error occurred while updating the instance: ${err.message}`, { - original: err, - }); + force: true, // Skip asking for permission again } - throw err; - } - }); - -function isValidUpdate(existingSourceOrigin: SourceOrigin, newSourceOrigin: SourceOrigin): boolean { - if (existingSourceOrigin === SourceOrigin.PUBLISHED_EXTENSION) { - return [SourceOrigin.PUBLISHED_EXTENSION, SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes( - newSourceOrigin ); - } else if (existingSourceOrigin === SourceOrigin.LOCAL) { - return [SourceOrigin.LOCAL, SourceOrigin.URL].includes(newSourceOrigin); - } - return false; -} + manifest.showPostDeprecationNotice(); + return; + }); diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index a70f7d175d2..313e99a0308 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -264,33 +264,17 @@ function readParamsFile(projectDir: string, fileName: string): Record Date: Wed, 27 Apr 2022 09:43:50 -0700 Subject: [PATCH 0269/1699] Adding deprecation warning for --params flag (#4476) --- src/extensions/paramHelper.ts | 2 ++ src/extensions/warnings.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 8bffbf3a1e5..c7aa7d889f3 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -15,6 +15,7 @@ import * as askUserForParam from "./askUserForParam"; import { track } from "../track"; import * as env from "../functions/env"; import { cloneDeep } from "../utils"; +import { paramsFlagDeprecationWarning } from "./warnings"; /** * Interface for holding different param values for different environments/configs. @@ -116,6 +117,7 @@ export async function getParams(args: { paramsMessage ); } else if (args.paramsEnvPath) { + paramsFlagDeprecationWarning(); params = getParamsFromFile({ paramSpecs: args.paramSpecs, paramsEnvPath: args.paramsEnvPath, diff --git a/src/extensions/warnings.ts b/src/extensions/warnings.ts index 37040d8bd3f..5a125ab42b1 100644 --- a/src/extensions/warnings.ts +++ b/src/extensions/warnings.ts @@ -10,6 +10,7 @@ import { humanReadable } from "../deploy/extensions/deploymentSummary"; import { InstanceSpec, getExtension, getExtensionVersion } from "../deploy/extensions/planner"; import { partition } from "../functional"; import * as utils from "../utils"; +import { logger } from "../logger"; interface displayEAPWarningParameters { publisherId: string; @@ -119,3 +120,14 @@ export async function displayWarningsForDeploy(instancesToCreate: InstanceSpec[] } return experimental.length > 0 || eapExtensions.length > 0; } + +/** + * paramsFlagDeprecationWarning displays a warning about the future depreaction of the --params flag. + */ +export function paramsFlagDeprecationWarning() { + logger.warn( + "The --params flag is deprecated and will be removed in firebase-tools@11. " + + "Instead, use an extensions manifest and `firebase deploy --only extensions` to deploy extensions noninteractively. " + + "See https://firebase.google.com/docs/extensions/manifest for more details" + ); +} From fac6271722f5b903e1631960210880a0510915c9 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 27 Apr 2022 10:04:34 -0700 Subject: [PATCH 0270/1699] Update to new major version of cli-color (#4488) * update to new major version of cli-color * Making CI happy by using npm@8.5 --- npm-shrinkwrap.json | 356 ++++++++++++++++---------------------------- package.json | 2 +- 2 files changed, 128 insertions(+), 230 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 3a9cbe4c410..000b52f5e84 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -16,7 +16,7 @@ "body-parser": "^1.19.0", "chokidar": "^3.0.2", "cjson": "^0.3.1", - "cli-color": "^1.2.0", + "cli-color": "^2.0.2", "cli-table": "0.3.11", "commander": "^4.0.1", "configstore": "^5.0.1", @@ -684,71 +684,6 @@ "xmlhttprequest": "1.8.0" } }, - "node_modules/@firebase/app-compat": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.18.tgz", - "integrity": "sha512-YXmMLQro2g2xlNnzB6zVxYoFx9sJS/JDEQy6vsj3FpMUuARaImipL6W8KuGfH+tJ3M+q38qRaFROk5gK6PoCrQ==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/app": "0.7.17", - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/app": { - "version": "0.7.17", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.17.tgz", - "integrity": "sha512-OnZab790eMwRxkUs7o/kgniAzSBxecDTGEk1PVhiG0HQhKrIf+R7lgqOZHDb/2GJsX12jby1p/Z5+WJCBxVbJQ==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/component": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", - "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/util": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", - "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true, - "peer": true - }, "node_modules/@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", @@ -1223,16 +1158,6 @@ "tslib": "^1.11.1" } }, - "node_modules/@firebase/util": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", - "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, "node_modules/@firebase/webchannel-wrapper": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", @@ -3963,16 +3888,18 @@ } }, "node_modules/cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.2.tgz", + "integrity": "sha512-g4JYjrTW9MGtCziFNjkqp3IMpGhnJyeB0lOtRPjQkYhXzKYr6tYnXKyEVnMzITxhpbahsEW9KsxOYIDKwcsIBw==", "dependencies": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", + "d": "^1.0.1", + "es5-ext": "^0.10.59", "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/cli-cursor": { @@ -4916,13 +4843,17 @@ } }, "node_modules/es5-ext": { - "version": "0.10.50", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", - "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "version": "0.10.61", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", + "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", + "hasInstallScript": true, "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/es6-error": { @@ -4948,12 +4879,12 @@ "dev": true }, "node_modules/es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "^1.0.1", + "ext": "^1.1.2" } }, "node_modules/es6-weak-map": { @@ -5770,6 +5701,19 @@ "node": ">= 0.10.0" } }, + "node_modules/ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "dependencies": { + "type": "^2.5.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", + "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7589,9 +7533,9 @@ } }, "node_modules/is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, "node_modules/is-stream": { "version": "1.1.0", @@ -8691,18 +8635,18 @@ } }, "node_modules/memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", "dependencies": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" } }, "node_modules/meow": { @@ -9231,9 +9175,9 @@ } }, "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/nice-try": { "version": "1.0.5", @@ -10447,6 +10391,15 @@ "node": ">=10" } }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -12675,9 +12628,9 @@ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, "node_modules/type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", - "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "node_modules/type-check": { "version": "0.3.2", @@ -14114,73 +14067,6 @@ } } }, - "@firebase/app-compat": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.18.tgz", - "integrity": "sha512-YXmMLQro2g2xlNnzB6zVxYoFx9sJS/JDEQy6vsj3FpMUuARaImipL6W8KuGfH+tJ3M+q38qRaFROk5gK6PoCrQ==", - "dev": true, - "peer": true, - "requires": { - "@firebase/app": "0.7.17", - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "@firebase/app": { - "version": "0.7.17", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.17.tgz", - "integrity": "sha512-OnZab790eMwRxkUs7o/kgniAzSBxecDTGEk1PVhiG0HQhKrIf+R7lgqOZHDb/2GJsX12jby1p/Z5+WJCBxVbJQ==", - "dev": true, - "peer": true, - "requires": { - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "@firebase/component": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", - "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", - "dev": true, - "peer": true, - "requires": { - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/util": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", - "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true, - "peer": true - } - } - }, "@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", @@ -14602,16 +14488,6 @@ "dev": true, "requires": {} }, - "@firebase/util": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", - "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^1.11.1" - } - }, "@firebase/webchannel-wrapper": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", @@ -16818,16 +16694,15 @@ "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" }, "cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.2.tgz", + "integrity": "sha512-g4JYjrTW9MGtCziFNjkqp3IMpGhnJyeB0lOtRPjQkYhXzKYr6tYnXKyEVnMzITxhpbahsEW9KsxOYIDKwcsIBw==", "requires": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", + "d": "^1.0.1", + "es5-ext": "^0.10.59", "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" } }, "cli-cursor": { @@ -17595,13 +17470,13 @@ } }, "es5-ext": { - "version": "0.10.50", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", - "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "version": "0.10.61", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", + "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" } }, "es6-error": { @@ -17627,12 +17502,12 @@ "dev": true }, "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "^1.0.1", + "ext": "^1.1.2" } }, "es6-weak-map": { @@ -18223,6 +18098,21 @@ "vary": "~1.1.2" } }, + "ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "requires": { + "type": "^2.5.0" + }, + "dependencies": { + "type": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", + "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -19659,9 +19549,9 @@ "dev": true }, "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, "is-stream": { "version": "1.1.0", @@ -20575,18 +20465,18 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" } }, "meow": { @@ -20975,9 +20865,9 @@ "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" }, "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "nice-try": { "version": "1.0.5", @@ -21910,6 +21800,14 @@ "requires": { "err-code": "^2.0.2", "retry": "^0.12.0" + }, + "dependencies": { + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true + } } }, "propagate": { @@ -23681,9 +23579,9 @@ } }, "type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", - "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "type-check": { "version": "0.3.2", diff --git a/package.json b/package.json index f8440a48508..5eaa94d3f20 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "body-parser": "^1.19.0", "chokidar": "^3.0.2", "cjson": "^0.3.1", - "cli-color": "^1.2.0", + "cli-color": "^2.0.2", "cli-table": "0.3.11", "commander": "^4.0.1", "configstore": "^5.0.1", From 00fc159fb5fe9ea48e3d95df140c91d1eaeddf1e Mon Sep 17 00:00:00 2001 From: Elvis Sun Date: Wed, 27 Apr 2022 13:11:41 -0400 Subject: [PATCH 0271/1699] Remove params flag in ext:* for v11 (#4487) * Remove params in ext:* * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/commands/ext-configure.ts | 4 ++-- src/commands/ext-install.ts | 5 ++--- src/commands/ext-update.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..ac6d40d5aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Remove `params` flag from ext:install, ext:update, ext:configure commands as they are replaced by the Extensions Manifest. See https://firebase.google.com/docs/extensions/manifest for more details. diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 128881564f8..0e4db5de279 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -33,7 +33,6 @@ marked.setOptions({ export default new Command("ext:configure ") .description("configure an existing extension instance") .withForce() - .option("--params ", "path of params file with .env format.") .before(requirePermissions, [ "firebaseextensions.instances.update", "firebaseextensions.instances.get", @@ -81,7 +80,8 @@ export default new Command("ext:configure ") projectId, paramSpecs: tbdParams, nonInteractive: false, - paramsEnvPath: (options.params ?? "") as string, + // TODO(b/230598656): Clean up paramsEnvPath after v11 launch. + paramsEnvPath: "", instanceId, reconfiguring: true, }); diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 7ad71381b4e..70f7da49332 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -48,15 +48,14 @@ export default new Command("ext:install [extensionName]") "or run with `-i` to see all available extensions." ) .withForce() - // TODO(b/221037520): Deprecate the params flag then remove it in the next breaking version. - .option("--params ", "name of params variables file with .env format.") .before(requirePermissions, ["firebaseextensions.instances.create"]) .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) .action(async (extensionName: string, options: Options) => { const projectId = getProjectId(options); - const paramsEnvPath = (options.params ?? "") as string; + // TODO(b/230598656): Clean up paramsEnvPath after v11 launch. + const paramsEnvPath = ""; let learnMore = false; if (!extensionName) { if (options.interactive) { diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 935e4033f9d..9984160638d 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -47,7 +47,6 @@ export default new Command("ext:update [updateSource]") .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) .withForce() - .option("--params ", "name of params variables file with .env format.") .action(async (instanceId: string, updateSource: string, options: Options) => { const projectId = getProjectId(options); const config = manifest.loadConfig(options); @@ -117,7 +116,8 @@ export default new Command("ext:update [updateSource]") newSpec: newExtensionVersion.spec, currentParams: oldParamValues, projectId, - paramsEnvPath: (options.params ?? "") as string, + // TODO(b/230598656): Clean up paramsEnvPath after v11 launch. + paramsEnvPath: "", nonInteractive: options.nonInteractive, instanceId, }); From 28b3467d0ca6bfd3b088ebd285d7e8641d67ca31 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 27 Apr 2022 11:34:24 -0700 Subject: [PATCH 0272/1699] Improving localhost detection for functions emulator (#4462) * Improving localhost detection for functions emulator * Update src/emulator/functionsEmulatorUtils.ts Co-authored-by: Yuchen Shi * Adding test for ipv6 loopback Co-authored-by: Yuchen Shi --- src/emulator/functionsEmulatorRuntime.ts | 4 +- src/emulator/functionsEmulatorUtils.ts | 7 +++ .../emulators/functionsEmulatorUtils.spec.ts | 45 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index e3b4c17a516..7cc4997fc7c 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -19,7 +19,7 @@ import { HttpConstants, SignatureType, } from "./functionsEmulatorShared"; -import { compareVersionStrings } from "./functionsEmulatorUtils"; +import { compareVersionStrings, isLocalHost } from "./functionsEmulatorUtils"; let functionModule: any; let FUNCTION_TARGET_NAME: string; @@ -365,7 +365,7 @@ function initializeNetworkFiltering(frb: FunctionsRuntimeBundle): void { .filter((v) => v); const href = (hrefs.length && hrefs[0]) || ""; - if (href && !history[href] && !href.startsWith("http://localhost")) { + if (href && !history[href] && !isLocalHost(href)) { history[href] = true; if (href.indexOf("googleapis.com") !== -1) { new EmulatorLog("SYSTEM", "googleapis-network-access", "", { diff --git a/src/emulator/functionsEmulatorUtils.ts b/src/emulator/functionsEmulatorUtils.ts index ed7bcb340a3..c9da07bb015 100644 --- a/src/emulator/functionsEmulatorUtils.ts +++ b/src/emulator/functionsEmulatorUtils.ts @@ -131,3 +131,10 @@ export function compareVersionStrings(a?: string, b?: string) { return 0; } + +/** + * Check if a url is localhost + */ +export function isLocalHost(href: string): boolean { + return !!href.match(/^(http(s)?:\/\/)?(localhost|127.0.0.1|\[::1])/); +} diff --git a/src/test/emulators/functionsEmulatorUtils.spec.ts b/src/test/emulators/functionsEmulatorUtils.spec.ts index c82aa59a5d6..7f97d582dd6 100644 --- a/src/test/emulators/functionsEmulatorUtils.spec.ts +++ b/src/test/emulators/functionsEmulatorUtils.spec.ts @@ -5,6 +5,7 @@ import { trimSlashes, compareVersionStrings, parseRuntimeVersion, + isLocalHost, } from "../../emulator/functionsEmulatorUtils"; describe("FunctionsEmulatorUtils", () => { @@ -138,4 +139,48 @@ describe("FunctionsEmulatorUtils", () => { expect(parseRuntimeVersion("banana")).to.eql(undefined); }); }); + + describe("isLocalHost", () => { + const testCases: { + desc: string; + href: string; + expected: boolean; + }[] = [ + { + desc: "should return true for localhost", + href: "http://localhost:4000", + expected: true, + }, + { + desc: "should return true for 127.0.0.1", + href: "127.0.0.1:5001/firestore", + expected: true, + }, + { + desc: "should return true for ipv6 loopback", + href: "[::1]:5001/firestore", + expected: true, + }, + { + desc: "should work with https", + href: "https://127.0.0.1:5001/firestore", + expected: true, + }, + { + desc: "should return false for external uri", + href: "http://google.com/what-is-localhost", + expected: false, + }, + { + desc: "should return false for external ip", + href: "123:100:99:12", + expected: false, + }, + ]; + for (const t of testCases) { + it(t.desc, () => { + expect(isLocalHost(t.href)).to.eq(t.expected); + }); + } + }); }); From 58a95e020118d9890649722086b10f89f0369e7c Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Wed, 27 Apr 2022 12:01:43 -0700 Subject: [PATCH 0273/1699] Make grantToken tenant-aware (#4475) Adds multi-tenancy support on grantToken endpoint by pulling tenant ID from refresh token. Changes include: - Make refresh token minting / validating stateless by serializing RefreshTokenRecord instead of representing with random string - Changing server side logic to check request body's refresh token for tenant ID (note that `grantToken` is the only endpoint in which the tenant ID cannot be obtained from the request path, query params, or ID token in the request body) - Added additional unit tests for `grantToken` edge cases Fixes https://github.com/firebase/firebase-tools/issues/4414. --- CHANGELOG.md | 1 + src/emulator/auth/operations.ts | 1 - src/emulator/auth/server.ts | 15 ++- src/emulator/auth/state.ts | 71 +++++++------- src/test/emulators/auth/misc.spec.ts | 138 ++++++++++++++++++++++++++- src/test/emulators/auth/rest.spec.ts | 6 +- 6 files changed, 191 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f76f03bd6..3acd51ad50e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Updates `superstatic` to `v8` to fix audit issues. +- Make grantToken tenant-aware (#4475) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index e56de93f214..a0ef81bd192 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -1704,7 +1704,6 @@ function grantToken( assert(reqBody.refreshToken, "MISSING_REFRESH_TOKEN"); const refreshTokenRecord = state.validateRefreshToken(reqBody.refreshToken); - assert(refreshTokenRecord, "INVALID_REFRESH_TOKEN"); assert(!refreshTokenRecord.user.disabled, "USER_DISABLED"); const tokens = issueTokens(state, refreshTokenRecord.user, refreshTokenRecord.provider, { extraClaims: refreshTokenRecord.extraClaims, diff --git a/src/emulator/auth/server.ts b/src/emulator/auth/server.ts index 11af742acd0..1c3364299b0 100644 --- a/src/emulator/auth/server.ts +++ b/src/emulator/auth/server.ts @@ -7,7 +7,7 @@ import { OpenAPIObject, PathsObject, ServerObject, OperationObject } from "opena import { EmulatorLogger } from "../emulatorLogger"; import { Emulators } from "../types"; import { authOperations, AuthOps, AuthOperation, FirebaseJwtPayload } from "./operations"; -import { AgentProjectState, ProjectState } from "./state"; +import { AgentProjectState, decodeRefreshToken, ProjectState } from "./state"; import apiSpecUntyped from "./apiSpec"; import { PromiseController, @@ -506,6 +506,19 @@ function toExegesisController( targetTenantId = targetTenantId || decoded?.payload.firebase.tenant; } + // Need to check refresh token for tenant ID for grantToken endpoint + if (ctx.requestBody?.refreshToken) { + const refreshTokenRecord = decodeRefreshToken(ctx.requestBody!.refreshToken); + if (refreshTokenRecord.tenantId && targetTenantId) { + // Shouldn't ever reach this assertion, but adding for completeness + assert( + refreshTokenRecord.tenantId === targetTenantId, + "TENANT_ID_MISMATCH: ((Refresh token tenant ID does not match target tenant ID.))" + ); + } + targetTenantId = targetTenantId || refreshTokenRecord.tenantId; + } + return operation(getProjectStateById(targetProjectId, targetTenantId), ctx.requestBody, ctx); }; } diff --git a/src/emulator/auth/state.ts b/src/emulator/auth/state.ts index 51b04751a37..d07d03aac1f 100644 --- a/src/emulator/auth/state.ts +++ b/src/emulator/auth/state.ts @@ -9,7 +9,7 @@ import { } from "./utils"; import { MakeRequired } from "./utils"; import { AuthCloudFunction } from "./cloudFunctions"; -import { assert } from "./errors"; +import { assert, BadRequestError } from "./errors"; import { MfaEnrollments, Schemas } from "./types"; export const PROVIDER_PASSWORD = "password"; @@ -27,8 +27,6 @@ export abstract class ProjectState { private localIdForPhoneNumber: Map = new Map(); private localIdsForProviderEmail: Map> = new Map(); private userIdForProviderRawId: Map> = new Map(); - private refreshTokens: Map = new Map(); - private refreshTokensForLocalId: Map> = new Map(); private oobs: Map = new Map(); private verificationCodes: Map = new Map(); private temporaryProofs: Map = new Map(); @@ -123,15 +121,6 @@ export abstract class ProjectState { deleteUser(user: UserInfo): void { this.users.delete(user.localId); this.removeUserFromIndex(user); - - const refreshTokens = this.refreshTokensForLocalId.get(user.localId); - if (refreshTokens) { - this.refreshTokensForLocalId.delete(user.localId); - for (const refreshToken of refreshTokens) { - this.refreshTokens.delete(refreshToken); - } - } - this.authCloudFunction.dispatch("delete", user); } @@ -399,34 +388,30 @@ export abstract class ProjectState { } = {} ): string { const localId = userInfo.localId; - const refreshToken = randomBase64UrlStr(204); - this.refreshTokens.set(refreshToken, { + const refreshTokenRecord = { + _AuthEmulatorRefreshToken: "DO NOT MODIFY", localId, provider, extraClaims, + projectId: this.projectId, secondFactor, tenantId: userInfo.tenantId, - }); - let refreshTokens = this.refreshTokensForLocalId.get(localId); - if (!refreshTokens) { - refreshTokens = new Set(); - this.refreshTokensForLocalId.set(localId, refreshTokens); - } - refreshTokens.add(refreshToken); + }; + const refreshToken = encodeRefreshToken(refreshTokenRecord); return refreshToken; } - validateRefreshToken(refreshToken: string): - | { - user: UserInfo; - provider: string; - extraClaims: Record; - secondFactor?: SecondFactorRecord; - } - | undefined { - const record = this.refreshTokens.get(refreshToken); - if (!record) { - return undefined; + validateRefreshToken(refreshToken: string): { + user: UserInfo; + provider: string; + extraClaims: Record; + secondFactor?: SecondFactorRecord; + } { + const record = decodeRefreshToken(refreshToken); + assert(record.projectId === this.projectId, "INVALID_REFRESH_TOKEN"); + if (this instanceof TenantProjectState) { + // Shouldn't ever reach this assertion, but adding for completeness + assert(record.tenantId === this.tenantId, "TENANT_ID_MISMATCH"); } return { user: this.getUserByLocalIdAssertingExists(record.localId), @@ -495,8 +480,6 @@ export abstract class ProjectState { this.localIdForPhoneNumber.clear(); this.localIdsForProviderEmail.clear(); this.userIdForProviderRawId.clear(); - this.refreshTokens.clear(); - this.refreshTokensForLocalId.clear(); // We do not clear OOBs / phone verification codes since some of those may // still be valid (e.g. email link / phone sign-in may still create a new @@ -871,10 +854,12 @@ export type Config = { blockingFunctions: BlockingFunctionsConfig; }; -interface RefreshTokenRecord { +export interface RefreshTokenRecord { + _AuthEmulatorRefreshToken: string; localId: string; provider: string; extraClaims: Record; + projectId: string; secondFactor?: SecondFactorRecord; tenantId?: string; } @@ -922,6 +907,22 @@ interface TemporaryProofRecord { // a bit easier. Therefore, there's no need to record createdAt timestamps. } +export function encodeRefreshToken(refreshTokenRecord: RefreshTokenRecord): string { + return Buffer.from(JSON.stringify(refreshTokenRecord), "utf8").toString("base64"); +} + +export function decodeRefreshToken(refreshTokenString: string): RefreshTokenRecord { + let refreshTokenRecord: RefreshTokenRecord; + try { + const json = Buffer.from(refreshTokenString, "base64").toString("utf8"); + refreshTokenRecord = JSON.parse(json) as RefreshTokenRecord; + } catch { + throw new BadRequestError("INVALID_REFRESH_TOKEN"); + } + assert(refreshTokenRecord._AuthEmulatorRefreshToken, "INVALID_REFRESH_TOKEN"); + return refreshTokenRecord; +} + function getProviderEmailsForUser(user: UserInfo): Set { const emails = new Set(); user.providerUserInfo?.forEach(({ email }) => { diff --git a/src/test/emulators/auth/misc.spec.ts b/src/test/emulators/auth/misc.spec.ts index 73bd187ba55..71c352ffc71 100644 --- a/src/test/emulators/auth/misc.spec.ts +++ b/src/test/emulators/auth/misc.spec.ts @@ -1,6 +1,11 @@ import { expect } from "chai"; import { decode as decodeJwt, JwtHeader } from "jsonwebtoken"; -import { UserInfo } from "../../../emulator/auth/state"; +import { + decodeRefreshToken, + encodeRefreshToken, + RefreshTokenRecord, + UserInfo, +} from "../../../emulator/auth/state"; import { deleteAccount, getAccountInfoByIdToken, @@ -47,6 +52,40 @@ describeAuthEmulator("token refresh", ({ authApi, getClock }) => { }); }); + it("should exchange refresh tokens for new tokens in a tenant project", async () => { + const tenant = await registerTenant(authApi(), PROJECT_ID, { + disableAuth: false, + allowPasswordSignup: true, + }); + const { refreshToken, localId } = await registerUser(authApi(), { + email: "alice@example.com", + password: "notasecret", + tenantId: tenant.tenantId, + }); + + await authApi() + .post("/securetoken.googleapis.com/v1/token") + .type("form") + // snake_case parameters also work, per OAuth 2.0 spec. + .send({ refresh_token: refreshToken, grantType: "refresh_token" }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body.id_token).to.be.a("string"); + expect(res.body.access_token).to.equal(res.body.id_token); + expect(res.body.refresh_token).to.be.a("string"); + expect(res.body.expires_in) + .to.be.a("string") + .matches(/[0-9]+/); + expect(res.body.project_id).to.equal("12345"); + expect(res.body.token_type).to.equal("Bearer"); + expect(res.body.user_id).to.equal(localId); + + const refreshTokenRecord = decodeRefreshToken(res.body.refresh_token); + expect(refreshTokenRecord.tenantId).to.equal(tenant.tenantId); + }); + }); + it("should populate auth_time to match lastLoginAt (in seconds since epoch)", async () => { getClock().tick(444); // Make timestamps a bit more interesting (non-zero). const emailUser = { email: "alice@example.com", password: "notasecret" }; @@ -75,6 +114,62 @@ describeAuthEmulator("token refresh", ({ authApi, getClock }) => { expect(decoded!.payload.auth_time).to.equal(lastLoginAtSeconds); }); + it("should error if grant type is missing", async () => { + const { refreshToken } = await registerAnonUser(authApi()); + + await authApi() + .post("/securetoken.googleapis.com/v1/token") + .type("form") + // snake_case parameters also work, per OAuth 2.0 spec. + .send({ refresh_token: refreshToken }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).to.contain("MISSING_GRANT_TYPE"); + }); + }); + + it("should error if grant type is not refresh_token", async () => { + const { refreshToken } = await registerAnonUser(authApi()); + + await authApi() + .post("/securetoken.googleapis.com/v1/token") + .type("form") + // snake_case parameters also work, per OAuth 2.0 spec. + .send({ refresh_token: refreshToken, grantType: "other_grant_type" }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).to.contain("INVALID_GRANT_TYPE"); + }); + }); + + it("should error if refresh token is missing", async () => { + await authApi() + .post("/securetoken.googleapis.com/v1/token") + .type("form") + // snake_case parameters also work, per OAuth 2.0 spec. + .send({ grantType: "refresh_token" }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).to.contain("MISSING_REFRESH_TOKEN"); + }); + }); + + it("should error on malformed refresh tokens", async () => { + await authApi() + .post("/securetoken.googleapis.com/v1/token") + .type("form") + // snake_case parameters also work, per OAuth 2.0 spec. + .send({ refresh_token: "malformedToken", grantType: "refresh_token" }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).to.contain("INVALID_REFRESH_TOKEN"); + }); + }); + it("should error if user is disabled", async () => { const { refreshToken, localId } = await registerAnonUser(authApi()); await updateAccountByLocalId(authApi(), localId, { disableUser: true }); @@ -107,6 +202,47 @@ describeAuthEmulator("token refresh", ({ authApi, getClock }) => { .equals("UNSUPPORTED_PASSTHROUGH_OPERATION"); }); }); + + it("should error when refresh tokens are from a different project", async () => { + const refreshTokenRecord = { + _AuthEmulatorRefreshToken: "DO NOT MODIFY", + localId: "localId", + provider: "provider", + extraClaims: {}, + projectId: "notMatchingProjectId", + }; + const refreshToken = encodeRefreshToken(refreshTokenRecord); + + await authApi() + .post("/securetoken.googleapis.com/v1/token") + .type("form") + .send({ refresh_token: refreshToken, grantType: "refresh_token" }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error).to.have.property("message").equals("INVALID_REFRESH_TOKEN"); + }); + }); + + it("should error on refresh tokens without required fields", async () => { + const refreshTokenRecord = { + localId: "localId", + provider: "provider", + extraClaims: {}, + projectId: "notMatchingProjectId", + }; + const refreshToken = encodeRefreshToken(refreshTokenRecord as RefreshTokenRecord); + + await authApi() + .post("/securetoken.googleapis.com/v1/token") + .type("form") + .send({ refresh_token: refreshToken, grantType: "refresh_token" }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error).to.have.property("message").equals("INVALID_REFRESH_TOKEN"); + }); + }); }); describeAuthEmulator("createSessionCookie", ({ authApi }) => { diff --git a/src/test/emulators/auth/rest.spec.ts b/src/test/emulators/auth/rest.spec.ts index a0d1bbb850e..f275a3c767d 100644 --- a/src/test/emulators/auth/rest.spec.ts +++ b/src/test/emulators/auth/rest.spec.ts @@ -180,7 +180,7 @@ describeAuthEmulator("authentication", ({ authApi }) => { }); }); - it("should deny requests where tenant IDs do not match in the token and path", async () => { + it("should deny requests where tenant IDs do not match in the ID token and path", async () => { const tenant = await registerTenant(authApi(), PROJECT_ID, { disableAuth: false, allowPasswordSignup: true, @@ -203,12 +203,12 @@ describeAuthEmulator("authentication", ({ authApi }) => { }); }); - it("should deny requests where tenant IDs do not match in the token and request body", async () => { + it("should deny requests where tenant IDs do not match in the ID token and request body", async () => { const tenant = await registerTenant(authApi(), PROJECT_ID, { disableAuth: false, allowPasswordSignup: true, }); - const { idToken, localId } = await registerUser(authApi(), { + const { idToken } = await registerUser(authApi(), { email: "alice@example.com", password: "notasecret", tenantId: tenant.tenantId, From c8fc63942dd0d25faf2f7f9339d0ea65ff3daa17 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 27 Apr 2022 13:13:46 -0700 Subject: [PATCH 0274/1699] Fix database emulator rules error parsing. Fix #4454. (#4489) --- CHANGELOG.md | 1 + src/emulator/databaseEmulator.ts | 47 ++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3acd51ad50e..c3229a081c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Updates `superstatic` to `v8` to fix audit issues. - Make grantToken tenant-aware (#4475) +- Fix database emulator rules error parsing (#4454). diff --git a/src/emulator/databaseEmulator.ts b/src/emulator/databaseEmulator.ts index bc340aba67c..f6ea6abeb10 100644 --- a/src/emulator/databaseEmulator.ts +++ b/src/emulator/databaseEmulator.ts @@ -83,8 +83,16 @@ export class DatabaseEmulator implements EmulatorInstance { if (!c.instance) { continue; } - - await this.updateRules(c.instance, c.rules); + try { + await this.updateRules(c.instance, c.rules); + } catch (e: any) { + const rulesError = this.prettyPrintRulesError(c.rules, e); + this.logger.logLabeled("WARN", "database", rulesError); + this.logger.logLabeled("WARN", "database", "Failed to update rules"); + throw new FirebaseError( + `Failed to load initial ${Constants.description(this.getName())} rules:\n${rulesError}` + ); + } } } } @@ -173,12 +181,41 @@ export class DatabaseEmulator implements EmulatorInstance { if (e.context && e.context.body) { throw e.context.body.error; } - throw e.original; + if (e.original) { + throw e.original; + } + throw e; } } - private prettyPrintRulesError(filePath: string, error: string): string { + private prettyPrintRulesError(filePath: string, error: unknown): string { + let errStr; + switch (typeof error) { + case "string": + errStr = error; + break; + case "object": + if (error != null && "message" in error) { + const message = (error as { message: unknown }).message; + errStr = `${message}`; + if (typeof message === "string") { + try { + // message may be JSON with {error: string} in it + const parsed = JSON.parse(message); + if (typeof parsed === "object" && parsed.error) { + errStr = `${parsed.error}`; + } + } catch (_) { + // Probably not JSON, just output the string itself as above. + } + } + break; + } + // fallthrough + default: + errStr = `Unknown error: ${JSON.stringify(error)}`; + } const relativePath = path.relative(process.cwd(), filePath); - return `${clc.cyan(relativePath)}:${error.trim()}`; + return `${clc.cyan(relativePath)}:${errStr.trim()}`; } } From 9732789be5ab296d92422eb687233a11dcc26366 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 27 Apr 2022 15:18:09 -0700 Subject: [PATCH 0275/1699] Fix timestamp format in Auth Emulator events. Fix #3093. (#4491) --- CHANGELOG.md | 1 + src/emulator/auth/cloudFunctions.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3229a081c4..900aeeebdf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Updates `superstatic` to `v8` to fix audit issues. - Make grantToken tenant-aware (#4475) - Fix database emulator rules error parsing (#4454). +- Fix timestamp format in Auth Emulator events (#3093). diff --git a/src/emulator/auth/cloudFunctions.ts b/src/emulator/auth/cloudFunctions.ts index 757a7a066b8..73f93013447 100644 --- a/src/emulator/auth/cloudFunctions.ts +++ b/src/emulator/auth/cloudFunctions.ts @@ -85,8 +85,12 @@ export class AuthCloudFunction { phoneNumber: user.phoneNumber, disabled: user.disabled, metadata: { - creationTime: user.createdAt, - lastSignInTime: user.lastLoginAt, + creationTime: user.createdAt + ? new Date(parseInt(user.createdAt, 10)).toISOString() + : undefined, + lastSignInTime: user.lastLoginAt + ? new Date(parseInt(user.lastLoginAt, 10)).toISOString() + : undefined, }, customClaims: JSON.parse(user.customAttributes || "{}"), providerData: user.providerUserInfo, From 7e9db6b6d7ec1aaedf6fb0d204c991955eb178a6 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 27 Apr 2022 15:32:59 -0700 Subject: [PATCH 0276/1699] Address review feedback. (#4490) --- src/emulator/databaseEmulator.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/emulator/databaseEmulator.ts b/src/emulator/databaseEmulator.ts index f6ea6abeb10..f116aac3da2 100644 --- a/src/emulator/databaseEmulator.ts +++ b/src/emulator/databaseEmulator.ts @@ -181,13 +181,11 @@ export class DatabaseEmulator implements EmulatorInstance { if (e.context && e.context.body) { throw e.context.body.error; } - if (e.original) { - throw e.original; - } - throw e; + throw e.original ?? e; } } + // TODO: tests private prettyPrintRulesError(filePath: string, error: unknown): string { let errStr; switch (typeof error) { From 83bd23dac67816f76d43f1e09d138bde9c081e34 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 28 Apr 2022 11:14:16 -0700 Subject: [PATCH 0277/1699] Serialize loading functions backend for Functions Emulator to avoid race conditions. (#4492) Container contract based function trigger discovery process spins up a control API server to announce /functions.yaml. When we load multiple functions backend in parallel, they sometimes end up having conflict over port. Serializing the load resolves the issue, but slows down the overall loading process. Slowdown may be a big hit to extensions emulator, delaying the emulator initialization by few more seconds. Setting up a PR to discuss changes. --- src/emulator/functionsEmulator.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index d8a1422080d..fe31ec730e4 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -438,7 +438,6 @@ export class FunctionsEmulator implements EmulatorInstance { } async connect(): Promise { - const loadTriggerPromises: Promise[] = []; for (const backend of this.args.emulatableBackends) { this.logger.logLabeled( "BULLET", @@ -461,9 +460,8 @@ export class FunctionsEmulator implements EmulatorInstance { return debouncedLoadTriggers(); }); - loadTriggerPromises.push(this.loadTriggers(backend, /* force= */ true)); + await this.loadTriggers(backend, /* force= */ true); } - await Promise.all(loadTriggerPromises); await this.performPostLoadOperations(); return; } @@ -1358,11 +1356,9 @@ export class FunctionsEmulator implements EmulatorInstance { async reloadTriggers() { this.triggerGeneration++; - const loadTriggerPromises = []; for (const backend of this.args.emulatableBackends) { - loadTriggerPromises.push(this.loadTriggers(backend)); + await this.loadTriggers(backend); } - await Promise.all(loadTriggerPromises); await this.performPostLoadOperations(); return; } From 466bdaa86de353eb090dc40006d5a1a9414de9da Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 28 Apr 2022 11:29:00 -0700 Subject: [PATCH 0278/1699] Fix broken caching in ContainerCleaner (#4477) * Fix broken caching in ContainerCleaner * Update src/deploy/functions/containerCleaner.ts Co-authored-by: Daniel Lee Co-authored-by: Daniel Lee --- src/deploy/functions/containerCleaner.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/deploy/functions/containerCleaner.ts b/src/deploy/functions/containerCleaner.ts index eaf2d04118d..8c08b99300f 100644 --- a/src/deploy/functions/containerCleaner.ts +++ b/src/deploy/functions/containerCleaner.ts @@ -6,7 +6,6 @@ import * as clc from "cli-color"; import { FirebaseError } from "../../error"; -import { previews } from "../../previews"; import { artifactRegistryDomain, containerRegistryDomain } from "../../api"; import { logger } from "../../logger"; import * as artifactregistry from "../../gcp/artifactregistry"; @@ -250,7 +249,6 @@ function getHelper(cache: Record, subdomain: string): Dock * @param projectId: the current project that contains GCF artifacts * @param location: the specific region to search for artifacts. If omitted, will search all locations. * @param dockerHelpers: a map of {@link SUBDOMAINS} to {@link DockerHelper}. If omitted, will use the default value and create each {@link DockerHelper} internally. - * * @throws {@link FirebaseError} * Thrown if the provided location is not a valid Google Cloud region or we fail to search subdomains. */ @@ -313,7 +311,6 @@ export async function listGcfPaths( * @param projectId: the current project that contains GCF artifacts * @param location: the specific region to be clean up. If omitted, will delete all locations. * @param dockerHelpers: a map of {@link SUBDOMAINS} to {@link DockerHelper}. If omitted, will use the default value and create each {@link DockerHelper} internally. - * * @throws {@link FirebaseError} * Thrown if the provided location is not a valid Google Cloud region or we fail to delete subdomains. */ @@ -360,20 +357,24 @@ export interface Stat { export class DockerHelper { readonly client: docker.Client; - readonly cache: Record = {}; + readonly cache: Record> = {}; constructor(origin: string) { this.client = new docker.Client(origin); } + // N.B. It is very important that this function assigns to the cache before + // yielding the runloop or parallel executions will race their LS calls, which + // are each recursive, leading to possible N^2 executions. async ls(path: string): Promise { - if (!this.cache[path]) { - const raw = await retry(() => this.client.listTags(path)); - this.cache[path] = { - tags: raw.tags, - digests: Object.keys(raw.manifest), - children: raw.child, - }; + if (!(path in this.cache)) { + this.cache[path] = retry(() => this.client.listTags(path)).then((res) => { + return { + tags: res.tags, + digests: Object.keys(res.manifest), + children: res.child, + }; + }); } return this.cache[path]; } From bbe67aa715447eca8ccc8802852e0ff8da115b3e Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 28 Apr 2022 11:56:15 -0700 Subject: [PATCH 0279/1699] Use mebibytes over hybrid mega/mebi (#4474) * Use mebibytes over hybrid mega/mebi * Use Gi friendly names * Fix empty comments --- src/gcp/cloudfunctionsv2.ts | 20 +++++++++++++++---- src/test/gcp/cloudfunctionsv2.spec.ts | 28 +++++++++++++++++---------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 5245844d778..39b900e7eb6 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -201,7 +201,7 @@ const BYTES_PER_UNIT: Record = { * Must serve the same results as * https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity.go */ -export function megabytes(memory: string): number { +export function mebibytes(memory: string): number { const re = /^([0-9]+(\.[0-9]*)?)(Ki|Mi|Gi|Ti|k|M|G|T|([eE]([0-9]+)))?$/; const matches = re.exec(memory); if (!matches) { @@ -215,7 +215,7 @@ export function megabytes(memory: string): number { const suffix = matches[3] || ""; bytes = quantity * BYTES_PER_UNIT[suffix as MemoryUnit]; } - return bytes / 1e6; + return bytes / (1 << 20); } /** @@ -386,6 +386,9 @@ export async function deleteFunction(cloudFunction: string): Promise } } +/** + * Generate a v2 Cloud Function API object from a versionless Endpoint object. + */ export function functionFromEndpoint(endpoint: backend.Endpoint, source: StorageSource) { if (endpoint.platform !== "gcfv2") { throw new FirebaseError( @@ -428,7 +431,13 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage endpoint, "availableMemory", "availableMemoryMb", - (mb: string) => `${mb}M` + (mb: number) => { + if (mb > 1024) { + return `${mb / 1024}Gi`; + } else { + return `${mb}Mi`; + } + } ); proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances"); proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances"); @@ -507,6 +516,9 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage return gcfFunction; } +/** + * Generate a versionless Endpoint object from a v2 Cloud Function API object. + */ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoint { const [, project, , region, , id] = gcfFunction.name.split("/"); let trigger: backend.Triggered; @@ -581,7 +593,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi gcfFunction.serviceConfig, "availableMemoryMb", "availableMemory", - megabytes + mebibytes ); proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount"); proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount"); diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 817938d08f1..396c53a38ea 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -52,19 +52,27 @@ describe("cloudfunctionsv2", () => { }; describe("megabytes", () => { + enum Bytes { + KB = 1e3, + MB = 1e6, + GB = 1e9, + KiB = 1 << 10, + MiB = 1 << 20, + GiB = 1 << 30, + } it("Should handle decimal SI units", () => { - expect(cloudfunctionsv2.megabytes("1000k")).to.equal(1); - expect(cloudfunctionsv2.megabytes("1.5M")).to.equal(1.5); - expect(cloudfunctionsv2.megabytes("1G")).to.equal(1000); + expect(cloudfunctionsv2.mebibytes("1000k")).to.equal((1000 * Bytes.KB) / Bytes.MiB); + expect(cloudfunctionsv2.mebibytes("1.5M")).to.equal((1.5 * Bytes.MB) / Bytes.MiB); + expect(cloudfunctionsv2.mebibytes("1G")).to.equal(Bytes.GB / Bytes.MiB); }); it("Should handle binary SI units", () => { - expect(cloudfunctionsv2.megabytes("1Mi")).to.equal((1 << 20) / 1e6); - expect(cloudfunctionsv2.megabytes("1Gi")).to.equal((1 << 30) / 1e6); + expect(cloudfunctionsv2.mebibytes("1Mi")).to.equal(Bytes.MiB / Bytes.MiB); + expect(cloudfunctionsv2.mebibytes("1Gi")).to.equal(Bytes.GiB / Bytes.MiB); }); it("Should handle no unit", () => { - expect(cloudfunctionsv2.megabytes("100000")).to.equal(0.1); - expect(cloudfunctionsv2.megabytes("1e9")).to.equal(1000); - expect(cloudfunctionsv2.megabytes("1.5E6")).to.equal(1.5); + expect(cloudfunctionsv2.mebibytes("100000")).to.equal(100000 / Bytes.MiB); + expect(cloudfunctionsv2.mebibytes("1e9")).to.equal(1e9 / Bytes.MiB); + expect(cloudfunctionsv2.mebibytes("1.5E6")).to.equal((1.5 * 1e6) / Bytes.MiB); }); }); describe("functionFromEndpoint", () => { @@ -269,7 +277,7 @@ describe("cloudfunctionsv2", () => { maxInstanceCount: 42, minInstanceCount: 1, timeoutSeconds: 15, - availableMemory: "128M", + availableMemory: "128Mi", environmentVariables: { FUNCTION_SIGNATURE_TYPE: "cloudevent" }, }, }; @@ -454,7 +462,7 @@ describe("cloudfunctionsv2", () => { ...extraFields, vpcConnector: vpc.connector, vpcConnectorEgressSettings: vpc.egressSettings, - availableMemory: "128M", + availableMemory: "128Mi", }, labels: { foo: "bar", From 2f2f3e080e2f057237041dc9459b7d91da096ed1 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 28 Apr 2022 13:25:36 -0700 Subject: [PATCH 0280/1699] Retry function deploy ops on server errors (#4495) --- src/deploy/functions/release/executor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deploy/functions/release/executor.ts b/src/deploy/functions/release/executor.ts index 6e26c982d12..0bf75b6c2e8 100644 --- a/src/deploy/functions/release/executor.ts +++ b/src/deploy/functions/release/executor.ts @@ -30,7 +30,7 @@ async function handler(op: Operation): Promise { err.context?.response?.statusCode || err.original?.code || err.original?.context?.response?.statusCode; - if (code === 429 || code === 409) { + if (code === 429 || code === 409 || code === 503) { throw err; } op.error = err; From 7f2672aaa4cbbb8aa0b660c3a21ff58b913d7e65 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Thu, 28 Apr 2022 16:47:04 -0400 Subject: [PATCH 0281/1699] Remove eventarc service agent binding (#4496) * comment out sa role * yanking eventarc service agent * removing tests --- src/deploy/functions/checkIam.ts | 16 ------------- src/test/deploy/functions/checkIam.spec.ts | 28 ---------------------- 2 files changed, 44 deletions(-) diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index 9b8409e086a..8381d4cb6f1 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -18,7 +18,6 @@ const PERMISSION = "cloudfunctions.functions.setIamPolicy"; export const SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator"; export const RUN_INVOKER_ROLE = "roles/run.invoker"; export const EVENTARC_EVENT_RECEIVER_ROLE = "roles/eventarc.eventReceiver"; -export const EVENTARC_SERVICE_AGENT_ROLE = "roles/eventarc.serviceAgent"; /** * Checks to see if the authenticated account has `iam.serviceAccounts.actAs` permissions @@ -188,20 +187,6 @@ export function obtainDefaultComputeServiceAgentBindings( return [invokerBinding, eventReceiverBinding]; } -/** - * Finds the required project level IAM bindings for the eventarc service agent. - * If a user enables eventarc for the first time, this grant can take a while to propagate and deployment will fail. - * @param projectNumber project number - * @param existingPolicy the project level IAM policy - */ -export function obtainEventarcServiceAgentBindings( - projectNumber: string, - existingPolicy: iam.Policy -): iam.Binding[] { - const eventarcServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`; - return [obtainBinding(existingPolicy, eventarcServiceAgent, EVENTARC_SERVICE_AGENT_ROLE)]; -} - /** Helper to merge all required bindings into the IAM policy */ export function mergeBindings(policy: iam.Policy, allRequiredBindings: iam.Binding[][]) { for (const requiredBindings of allRequiredBindings) { @@ -268,7 +253,6 @@ export async function ensureServiceAgentRoles( if (haveServices.length === 0) { allRequiredBindings.push(obtainPubSubServiceAgentBindings(projectNumber, policy)); allRequiredBindings.push(obtainDefaultComputeServiceAgentBindings(projectNumber, policy)); - allRequiredBindings.push(obtainEventarcServiceAgentBindings(projectNumber, policy)); } if (!allRequiredBindings.find((bindings) => bindings.length > 0)) { return; diff --git a/src/test/deploy/functions/checkIam.spec.ts b/src/test/deploy/functions/checkIam.spec.ts index 617dd45717c..e7e7eaddbb3 100644 --- a/src/test/deploy/functions/checkIam.spec.ts +++ b/src/test/deploy/functions/checkIam.spec.ts @@ -189,22 +189,6 @@ describe("checkIam", () => { }); }); - describe("obtainEventarcServiceAgentBindings", () => { - it("should add the binding", () => { - const policy = { ...iamPolicy }; - - const bindings = checkIam.obtainEventarcServiceAgentBindings(projectNumber, policy); - - expect(bindings.length).to.equal(1); - expect(bindings[0]).to.deep.equal({ - role: checkIam.EVENTARC_SERVICE_AGENT_ROLE, - members: [ - `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`, - ], - }); - }); - }); - describe("mergeBindings", () => { it("should skip empty or duplicate bindings", () => { const policy = { @@ -369,12 +353,6 @@ describe("checkIam", () => { role: checkIam.EVENTARC_EVENT_RECEIVER_ROLE, members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`], }, - { - role: checkIam.EVENTARC_SERVICE_AGENT_ROLE, - members: [ - `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`, - ], - }, ], }; storageStub.resolves(STORAGE_RES); @@ -475,12 +453,6 @@ describe("checkIam", () => { role: checkIam.EVENTARC_EVENT_RECEIVER_ROLE, members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`], }, - { - role: checkIam.EVENTARC_SERVICE_AGENT_ROLE, - members: [ - `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`, - ], - }, ], }; getIamStub.resolves({ From 89d4dd8dcafa18f44d5088c918f36047b49309b6 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 28 Apr 2022 14:13:30 -0700 Subject: [PATCH 0282/1699] Fixing invalid version error when emulating functions (#4497) * Fixing invalid version error when emulating functions * oops --- CHANGELOG.md | 1 + src/deploy/functions/runtimes/node/index.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 900aeeebdf7..deda19ac6e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Make grantToken tenant-aware (#4475) - Fix database emulator rules error parsing (#4454). - Fix timestamp format in Auth Emulator events (#3093). +- Fix `TypeError: Invalid Version:` error when emulating functions (#4403). diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index b706c4713ca..b87f0fc0318 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -129,6 +129,18 @@ export class Delegate { env: backend.EnvironmentVariables ): Promise { if (previews.functionsv2) { + if (!semver.valid(this.sdkVersion)) { + logger.debug( + `Could not parse firebase-functions version '${this.sdkVersion}' into semver. Falling back to parseTriggers.` + ); + return parseTriggers.discoverBackend( + this.projectId, + this.sourceDir, + this.runtime, + config, + env + ); + } if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) { logLabeledWarning( "functions", From 289b15c98dbce71c52bdc1988b04b624d0c9dcc2 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Thu, 28 Apr 2022 14:29:51 -0700 Subject: [PATCH 0283/1699] Filter out invalid prefixes and items from listing. (#4493) * Filter out invalid prefixes and items from listing. * Add test and address review feedback. * Add changelog entry. * One last rename. --- CHANGELOG.md | 1 + scripts/storage-emulator-integration/tests.ts | 106 ++++++++++++++++++ src/emulator/storage/apis/firebase.ts | 34 +++++- 3 files changed, 137 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index deda19ac6e6..00de1cf1233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Fix database emulator rules error parsing (#4454). - Fix timestamp format in Auth Emulator events (#3093). - Fix `TypeError: Invalid Version:` error when emulating functions (#4403). +- Fix Storage Emulator showing folder placeholders in list results. diff --git a/scripts/storage-emulator-integration/tests.ts b/scripts/storage-emulator-integration/tests.ts index 72b5bb60595..3b93ffac699 100644 --- a/scripts/storage-emulator-integration/tests.ts +++ b/scripts/storage-emulator-integration/tests.ts @@ -5,6 +5,7 @@ import * as fs from "fs"; import * as path from "path"; import * as http from "http"; import * as https from "https"; +import fetch from "node-fetch"; import * as puppeteer from "puppeteer"; import { Bucket, Storage, CopyOptions } from "@google-cloud/storage"; import supertest = require("supertest"); @@ -49,6 +50,16 @@ const TEST_CONFIG = { keepBrowserOpen: false, }; +const EMPTY_FOLDER_DATA = `--boundary\r +Content-Type: application/json\r +\r +{"contentType":"text/plain"}\r +--boundary\r +Content-Type: text/plain\r +\r +--boundary--\r +`; + // Temp directory to store generated files. let tmpDir: string; @@ -1710,6 +1721,101 @@ describe("Storage emulator", () => { items: [], }); }); + context("with folder placeholders", () => { + beforeEach(async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + const refs = [ + "testing/abc", // empty folder inside testing/ + "testing/storage_ref", // also an implicit prefix with files + ]; + for (const ref of refs) { + // Use REST API to create the folder placeholders since SDK won't + // allow refs with trailing slashes. + await fetch( + `${STORAGE_EMULATOR_HOST}/upload/storage/v1/b/${storageBucket}/o?name=${encodeURIComponent( + ref + )}/`, + { + headers: { + "Content-Type": "multipart/related; boundary=boundary", + }, + method: "POST", + body: Buffer.from(EMPTY_FOLDER_DATA, "utf8"), + } + ); + } + }); + + it("folder placeholder should not be listed under itself", async () => { + const listResult = await page.evaluate(async () => { + const list = await firebase.storage().ref("/testing/abc").listAll(); + return { + prefixes: list.prefixes.map((prefix) => prefix.name), + items: list.items.map((item) => item.name), + }; + }); + + expect(listResult).to.deep.equal({ + prefixes: [], + items: [], + }); + }); + + it("folder placeholder should be listed as a prefix but not an item under parent", async () => { + const listResult = await page.evaluate(async () => { + const list = await firebase.storage().ref("/testing").listAll(); + return { + prefixes: list.prefixes.map((prefix) => prefix.name), + items: list.items.map((item) => item.name), + }; + }); + + expect(listResult).to.deep.equal({ + prefixes: ["abc", "somePathEndsWithDoubleSlash", "storage_ref"], + items: [], + }); + }); + }); + + context("with invalid prefixes and items", () => { + beforeEach(async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + const refs = ["list//foo", "list/bar//", "list/baz//qux"]; + for (const ref of refs) { + // Use REST API to create the folder placeholders since SDK won't + // allow refs with trailing slashes. + await fetch( + `${STORAGE_EMULATOR_HOST}/upload/storage/v1/b/${storageBucket}/o?name=${encodeURIComponent( + ref + )}`, + { + headers: { + "Content-Type": "multipart/related; boundary=boundary", + }, + method: "POST", + body: Buffer.from(EMPTY_FOLDER_DATA, "utf8"), + } + ); + } + }); + + it("list result should not include show invalid prefixes and items", async () => { + const listResult = await page.evaluate(async () => { + const list = await firebase.storage().ref("/list").listAll(); + return { + prefixes: list.prefixes.map((prefix) => prefix.name), + items: list.items.map((item) => item.name), + }; + }); + + expect(listResult).to.deep.equal({ + prefixes: ["bar", "baz"], // only implicit prefixes, (no bar//) + items: [], // no valid items + }); + }); + }); }); describe("#list()", () => { diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index f28581ec56e..a8b0462d7c0 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -169,11 +169,12 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { } return res.status(200).json({ nextPageToken: listResponse.nextPageToken, - prefixes: listResponse.prefixes ?? [], - items: - listResponse.items?.map((item) => { + prefixes: (listResponse.prefixes ?? []).filter(isValidPrefix), + items: (listResponse.items ?? []) + .filter((item) => isValidNonEncodedPathString(item.name)) + .map((item) => { return { name: item.name, bucket: item.bucket }; - }) ?? [], + }), }); }); @@ -517,3 +518,28 @@ function setObjectHeaders( res.setHeader("Content-Language", metadata.contentLanguage); } } + +function isValidPrefix(prefix: string): boolean { + // See go/firebase-storage-backend-valid-path + return isValidNonEncodedPathString(removeAtMostOneTrailingSlash(prefix)); +} + +function isValidNonEncodedPathString(path: string): boolean { + // See go/firebase-storage-backend-valid-path + if (path.startsWith("/")) { + path = path.substring(1); + } + if (!path) { + return false; + } + for (const pathSegment of path.split("/")) { + if (!pathSegment) { + return false; + } + } + return true; +} + +function removeAtMostOneTrailingSlash(path: string): string { + return path.replace(/\/$/, ""); +} From 3b2c915ef474ef9176a2795e5f50ff805021cb20 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Thu, 28 Apr 2022 16:14:34 -0700 Subject: [PATCH 0284/1699] Release Emulator UI v1.6.6. (#4499) * Release Emulator UI v1.6.6. * Update CHANGELOG.md Co-authored-by: joehan Co-authored-by: joehan --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00de1cf1233..964d120b4f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,4 +3,5 @@ - Fix database emulator rules error parsing (#4454). - Fix timestamp format in Auth Emulator events (#3093). - Fix `TypeError: Invalid Version:` error when emulating functions (#4403). +- Fix issue with creating folders from Storage Emulator UI (https://github.com/firebase/firebase-tools-ui/issues/738). - Fix Storage Emulator showing folder placeholders in list results. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 83b2fd3621b..eb934ff8181 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -97,15 +97,15 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe }, } : { - version: "1.6.5", - downloadPath: path.join(CACHE_DIR, "ui-v1.6.5.zip"), - unzipDir: path.join(CACHE_DIR, "ui-v1.6.5"), - binaryPath: path.join(CACHE_DIR, "ui-v1.6.5", "server.bundle.js"), + version: "1.6.6", + downloadPath: path.join(CACHE_DIR, "ui-v1.6.6.zip"), + unzipDir: path.join(CACHE_DIR, "ui-v1.6.6"), + binaryPath: path.join(CACHE_DIR, "ui-v1.6.6", "server.bundle.js"), opts: { cacheDir: CACHE_DIR, - remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.5.zip", - expectedSize: 3816994, - expectedChecksum: "92dfff4b2ef8ab616e8a60cc93e0a00b", + remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.6.zip", + expectedSize: 3817247, + expectedChecksum: "c80a3f0ae1e3f682ace0a18a9cdd2861", namePrefix: "ui", }, }, From 8613c74599e622ab1bacf6f9c55572b8f11782c2 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 28 Apr 2022 23:24:53 +0000 Subject: [PATCH 0285/1699] 10.7.2 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 000b52f5e84..e3a17217bd9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.7.1", + "version": "10.7.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.7.1", + "version": "10.7.2", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 5eaa94d3f20..1f32a1a452c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.7.1", + "version": "10.7.2", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From eced7091fd75a77a65b9db9a5e78fcab20c820f8 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 28 Apr 2022 23:25:16 +0000 Subject: [PATCH 0286/1699] [firebase-release] Removed change log and reset repo after 10.7.2 release --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 964d120b4f7..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +0,0 @@ -- Updates `superstatic` to `v8` to fix audit issues. -- Make grantToken tenant-aware (#4475) -- Fix database emulator rules error parsing (#4454). -- Fix timestamp format in Auth Emulator events (#3093). -- Fix `TypeError: Invalid Version:` error when emulating functions (#4403). -- Fix issue with creating folders from Storage Emulator UI (https://github.com/firebase/firebase-tools-ui/issues/738). -- Fix Storage Emulator showing folder placeholders in list results. From 27090ad3382ca0e4084faa9d8c5997dcbe84a1c4 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Fri, 29 Apr 2022 10:37:20 -0700 Subject: [PATCH 0287/1699] fix missing packages from npm shrinkwrap --- npm-shrinkwrap.json | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 81812f9ec1d..530078b993e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -693,6 +693,71 @@ "xmlhttprequest": "1.8.0" } }, + "node_modules/@firebase/app-compat": { + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.23.tgz", + "integrity": "sha512-c0QOhU2UVxZ7N5++nLQgKZ899ZC8+/ESa8VCzsQDwBw1T3MFAD1cG40KhB+CGtp/uYk/w6Jtk8k0xyZu6O2LOg==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/app": "0.7.22", + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/app": { + "version": "0.7.22", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.22.tgz", + "integrity": "sha512-v3AXSCwAvZyIFzOGgPAYtzjltm1M9R4U4yqsIBPf5B4ryaT1EGK+3ETZUOckNl5y2YwdKRJVPDDore+B2xg0Ug==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/component": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", + "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "dev": true, + "peer": true, + "dependencies": { + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/@firebase/util": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", + "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "peer": true + }, "node_modules/@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", @@ -1167,6 +1232,23 @@ "tslib": "^1.11.1" } }, + "node_modules/@firebase/util": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.4.1.tgz", + "integrity": "sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "peer": true + }, "node_modules/@firebase/webchannel-wrapper": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", @@ -14137,6 +14219,73 @@ } } }, + "@firebase/app-compat": { + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.23.tgz", + "integrity": "sha512-c0QOhU2UVxZ7N5++nLQgKZ899ZC8+/ESa8VCzsQDwBw1T3MFAD1cG40KhB+CGtp/uYk/w6Jtk8k0xyZu6O2LOg==", + "dev": true, + "peer": true, + "requires": { + "@firebase/app": "0.7.22", + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/app": { + "version": "0.7.22", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.22.tgz", + "integrity": "sha512-v3AXSCwAvZyIFzOGgPAYtzjltm1M9R4U4yqsIBPf5B4ryaT1EGK+3ETZUOckNl5y2YwdKRJVPDDore+B2xg0Ug==", + "dev": true, + "peer": true, + "requires": { + "@firebase/component": "0.5.13", + "@firebase/logger": "0.3.2", + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "@firebase/component": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", + "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "dev": true, + "peer": true, + "requires": { + "@firebase/util": "1.5.2", + "tslib": "^2.1.0" + } + }, + "@firebase/logger": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", + "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", + "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "peer": true + } + } + }, "@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", @@ -14558,6 +14707,25 @@ "dev": true, "requires": {} }, + "@firebase/util": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.4.1.tgz", + "integrity": "sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "peer": true + } + } + }, "@firebase/webchannel-wrapper": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", From c5e135195996ae3373f40e7ce1f9c0d0b094b3e9 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Fri, 29 Apr 2022 15:28:48 -0700 Subject: [PATCH 0288/1699] Adds preview flag guarded code path to start using Builds in CF3 deploys (#4498) --- src/deploy/functions/build.ts | 13 ++++++++++++- src/deploy/functions/prepare.ts | 14 +++++++++++--- src/previews.ts | 2 ++ .../runtimes/node/parseTriggers.spec.ts | 16 ++++++++-------- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index dce88f174ff..867ccda3b22 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -252,7 +252,18 @@ function isMemoryOption(value: backend.MemoryOptions | any): value is backend.Me /** Converts a build specification into a Backend representation, with all Params resolved and interpolated */ // TODO(vsfan): resolve build.Params // TODO(vsfan): handle Expression types -export function resolveBackend(build: Build): backend.Backend { +export function resolveBackend(build: Build, userEnvs: Record): backend.Backend { + for (const param of build.params) { + const expectedEnv = param.param; + if (!userEnvs.hasOwnProperty(expectedEnv)) { + throw new FirebaseError( + "Build specified parameter " + + expectedEnv + + " but it was not present in the user dotenv files" + ); + } + } + const bkEndpoints: Array = []; for (const endpointId of Object.keys(build.endpoints)) { const endpoint = build.endpoints[endpointId]; diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 1440729a7b1..c32f79d9d29 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -2,6 +2,7 @@ import * as clc from "cli-color"; import * as args from "./args"; import * as backend from "./backend"; +import * as build from "./build"; import * as ensureApiEnabled from "../../ensureApiEnabled"; import * as functionsConfig from "../../functionsConfig"; import * as functionsEnv from "../../functions/env"; @@ -107,9 +108,16 @@ export async function prepare( projectAlias: options.projectAlias, }; const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt); - logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`); - const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs); - wantBackend.environmentVariables = { ...userEnvs, ...firebaseEnvs }; + const envs = { ...userEnvs, ...firebaseEnvs }; + let wantBackend: backend.Backend; + if (previews.functionsparams) { + const wantBuild = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs); + wantBackend = build.resolveBackend(wantBuild, userEnvs); + } else { + logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`); + wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs); + } + wantBackend.environmentVariables = envs; for (const endpoint of backend.allEndpoints(wantBackend)) { endpoint.environmentVariables = wantBackend.environmentVariables; endpoint.codebase = codebase; diff --git a/src/previews.ts b/src/previews.ts index 013d65414b0..f98e4bde65c 100644 --- a/src/previews.ts +++ b/src/previews.ts @@ -13,6 +13,7 @@ interface PreviewFlags { artifactregistry: boolean; emulatoruisnapshot: boolean; frameworkawareness: boolean; + functionsparams: boolean; } export const previews: PreviewFlags = { @@ -28,6 +29,7 @@ export const previews: PreviewFlags = { artifactregistry: false, emulatoruisnapshot: false, frameworkawareness: false, + functionsparams: false, ...(configstore.get("previews") as Partial), }; diff --git a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts index 2909b053988..87d0b58be10 100644 --- a/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts +++ b/src/test/deploy/functions/runtimes/node/parseTriggers.spec.ts @@ -74,7 +74,7 @@ describe("addResourcesToBuild", () => { ...BASIC_BACKEND_ENDPOINT, httpsTrigger: {}, }); - const convertedBackend: backend.Backend = build.resolveBackend(expected); + const convertedBackend: backend.Backend = build.resolveBackend(expected, {}); expect(convertedBackend).to.deep.equal(expectedBackend); }); @@ -104,7 +104,7 @@ describe("addResourcesToBuild", () => { callableTrigger: {}, labels: {}, }); - const convertedBackend: backend.Backend = build.resolveBackend(expected); + const convertedBackend: backend.Backend = build.resolveBackend(expected, {}); expect(convertedBackend).to.deep.equal(expectedBackend); }); @@ -140,7 +140,7 @@ describe("addResourcesToBuild", () => { }, ], }; - const convertedBackend: backend.Backend = build.resolveBackend(expected); + const convertedBackend: backend.Backend = build.resolveBackend(expected, {}); expect(convertedBackend).to.deep.equal(expectedBackend); }); @@ -208,7 +208,7 @@ describe("addResourcesToBuild", () => { }, ...backendConfig, }); - const convertedBackend: backend.Backend = build.resolveBackend(expected); + const convertedBackend: backend.Backend = build.resolveBackend(expected, {}); expect(convertedBackend).to.deep.equal(expectedBackend); }); @@ -244,7 +244,7 @@ describe("addResourcesToBuild", () => { ...BASIC_BACKEND_ENDPOINT, eventTrigger, }); - const convertedBackend: backend.Backend = build.resolveBackend(expected); + const convertedBackend: backend.Backend = build.resolveBackend(expected, {}); expect(convertedBackend).to.deep.equal(expectedBackend); }); @@ -278,7 +278,7 @@ describe("addResourcesToBuild", () => { region: "europe-west1", } ); - const convertedBackend: backend.Backend = build.resolveBackend(expected); + const convertedBackend: backend.Backend = build.resolveBackend(expected, {}); expect(convertedBackend).to.deep.equal(expectedBackend); }); @@ -360,7 +360,7 @@ describe("addResourcesToBuild", () => { }, ], }; - const convertedBackend: backend.Backend = build.resolveBackend(expected); + const convertedBackend: backend.Backend = build.resolveBackend(expected, {}); expect(convertedBackend).to.deep.equal(expectedBackend); }); @@ -392,7 +392,7 @@ describe("addResourcesToBuild", () => { connector: "", }, }); - const convertedBackend: backend.Backend = build.resolveBackend(expected); + const convertedBackend: backend.Backend = build.resolveBackend(expected, {}); expect(convertedBackend).to.deep.equal(expectedBackend); }); }); From e9c2eaf24f834cf2cfcf6ae71a9dc8e6eec145e9 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 2 May 2022 12:01:33 -0700 Subject: [PATCH 0289/1699] Fixing an issue where not providing a value for optional secrets would cause NPEs (#4507) --- src/extensions/manifest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index a70f7d175d2..e9448d865c4 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -78,7 +78,7 @@ export async function writeLocalSecrets( const writeBuffer: Record = {}; const locallyOverridenSecretParams = extensionSpec.params.filter( - (p) => p.type === ParamType.SECRET && spec.params[p.param].local + (p) => p.type === ParamType.SECRET && spec.params[p.param]?.local ); for (const paramSpec of locallyOverridenSecretParams) { const key = paramSpec.param; From c8cb86b2220d7f9d97c57958a26b8c7a6eb79b2f Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 2 May 2022 15:10:05 -0700 Subject: [PATCH 0290/1699] Wire up CPU options (#4449) * Add CPU option and improve schema validation. * Checkpoint * Fix issues by waiting on updates to the Run service to resolve * Fix tests * Work around transpiler bug * Fix broken test * Add support for large VM sizes * Undefined -> Unknown * Undefined -> Unknown * PR feedback --- src/deploy/functions/backend.ts | 81 +++++- src/deploy/functions/prepare.ts | 35 ++- src/deploy/functions/pricing.ts | 4 +- src/deploy/functions/release/fabricator.ts | 116 ++++++-- .../functions/runtimes/discovery/v1alpha1.ts | 3 +- src/deploy/functions/validate.ts | 10 +- src/gcp/run.ts | 26 +- src/test/deploy/functions/backend.spec.ts | 53 +++- src/test/deploy/functions/pricing.spec.ts | 6 +- .../functions/release/fabricator.spec.ts | 272 ++++++++++++++++-- src/test/deploy/functions/validate.spec.ts | 33 ++- src/throttler/throttler.ts | 42 +-- 12 files changed, 592 insertions(+), 89 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index a9ee425a823..ef6a3b5e334 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -1,12 +1,13 @@ import * as proto from "../../gcp/proto"; import * as gcf from "../../gcp/cloudfunctions"; import * as gcfV2 from "../../gcp/cloudfunctionsv2"; +import * as run from "../../gcp/run"; import * as utils from "../../utils"; import * as runtimes from "./runtimes"; import { FirebaseError } from "../../error"; import { Context } from "./args"; import { previews } from "../../previews"; -import { flattenArray } from "../../functional"; +import { flattenArray, zip } from "../../functional"; /** Retry settings for a ScheduleSpec. */ export interface ScheduleRetryConfig { @@ -136,7 +137,6 @@ export interface BlockingTrigger { eventType: string; options?: Record; } - export interface BlockingTriggered { blockingTrigger: BlockingTrigger; } @@ -169,8 +169,10 @@ export const AllIngressSettings: IngressSettings[] = [ "ALLOW_INTERNAL_ONLY", "ALLOW_INTERNAL_AND_GCLB", ]; -export type MemoryOptions = 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192; -export const AllMemoryOptions: MemoryOptions[] = [128, 256, 512, 1024, 2048, 4096, 8192]; +export type MemoryOptions = 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768; +export const AllMemoryOptions: MemoryOptions[] = [ + 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, +]; /** Returns a human-readable name with MB or GB suffix for a MemoryOption (MB). */ export function memoryOptionDisplayName(option: MemoryOptions): string { @@ -182,11 +184,54 @@ export function memoryOptionDisplayName(option: MemoryOptions): string { 2048: "2GB", 4096: "4GB", 8192: "8GB", + 16384: "16GB", + 32768: "32GB", }[option]; } +/** + * Returns the gen 1 mapping of CPU for RAM. Used whenever a customer sets cpu to "gcf_gen1". + * Note that these values must be the right number of decimal places and include + * rounding errors (e.g. 0.1666 instead of 0.1667) so that we match GCF's + * behavior and don't unnecessarily create a new Run revision because our target + * CPU doesn't exactly match their CPU. + */ +export function memoryToGen1Cpu(memory: MemoryOptions): number { + return { + 128: 0.0833, // ~1/12 + 256: 0.1666, // ~1/6 + 512: 0.3333, // ~1/3 + 1024: 0.5833, // ~5/7 + 2048: 1, + 4096: 2, + 8192: 2, + 16384: 3, + 32768: 4, + }[memory]; +} + +/** + * The amount of CPU we allocate in V2. + * Where these don't match with memoryToGen1Cpu we must manually configure these + * at the run service. + */ +export function memoryToGen2Cpu(memory: MemoryOptions): number { + return { + 128: 1, + 256: 1, + 512: 1, + 1024: 1, + 2048: 1, + 4096: 2, + 8192: 2, + 16384: 3, + 32768: 4, + }[memory]; +} + +export const DEFAULT_CONCURRENCY = 80; export const DEFAULT_MEMORY: MemoryOptions = 256; -export const MIN_MEMORY_FOR_CONCURRENCY: MemoryOptions = 2048; +export const MIN_CPU_FOR_CONCURRENCY = 1; export const SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" }); /** @@ -487,19 +532,31 @@ async function loadExistingBackend(ctx: Context & PrivateContextFields): Promise let gcfV2Results; try { gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId); + const runResults = await Promise.all( + gcfV2Results.functions.map((fn) => run.getService(fn.serviceConfig.service!)) + ); + for (const [apiFunction, runService] of zip(gcfV2Results.functions, runResults)) { + // I don't know why but code complete knows apiFunction is a gcfv2.CloudFunction + // and the compiler thinks it's type {}. + const endpoint = gcfV2.endpointFromFunction(apiFunction as any); + endpoint.concurrency = runService.spec.template.spec.containerConcurrency || 1; + // N.B. We don't generally do anything with multiple containers, but we + // might have to figure out WTF to do here if we're updating multiple containers + // and our only reference point is the image. Hopefully by then we'll be + // on the next gen infrastructure and have state we can refer back to. + endpoint.cpu = +runService.spec.template.spec.containers[0].resources.limits.cpu; + + ctx.existingBackend.endpoints[endpoint.region] = + ctx.existingBackend.endpoints[endpoint.region] || {}; + ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint; + } + ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable; } catch (err: any) { if (err.status === 404 && err.message?.toLowerCase().includes("method not found")) { return; // customer has preview enabled without allowlist set } throw err; } - for (const apiFunction of gcfV2Results.functions) { - const endpoint = gcfV2.endpointFromFunction(apiFunction); - ctx.existingBackend.endpoints[endpoint.region] = - ctx.existingBackend.endpoints[endpoint.region] || {}; - ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint; - } - ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable; } /** diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index c32f79d9d29..21a1dca0fc3 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -35,6 +35,9 @@ function hasUserConfig(config: Record): boolean { return Object.keys(config).length > 1; } +/** + * Prepare functions codebases for deploy. + */ export async function prepare( context: args.Context, options: Options, @@ -171,12 +174,13 @@ export async function prepare( backend.allEndpoints(await backend.existingBackend(context)) ); for (const [codebase, wantBackend] of Object.entries(wantBackends)) { - const haveBackend = haveBackends[codebase] || { ...backend.empty() }; + const haveBackend = haveBackends[codebase] || backend.empty(); payload.functions[codebase] = { wantBackend, haveBackend }; } for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) { inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase)); await ensureTriggerRegions(wantBackend); + resolveCpu(wantBackend); validate.endpointsAreValid(wantBackend); inferBlockingDetails(wantBackend); } @@ -270,6 +274,17 @@ export function inferDetailsFromExisting( wantE.availableMemoryMb = haveE.availableMemoryMb; } + // N.B. This code doesn't handle automatic downgrading of concurrency if + // the customer sets CPU <1. We'll instead error that you can't have both. + // We may want to handle this case, though it might also be surprising to + // customers if they _don't_ get an error and we silently drop concurrency. + if (!wantE.concurrency && haveE.concurrency) { + wantE.concurrency = haveE.concurrency; + } + if (!wantE.cpu && haveE.cpu) { + wantE.cpu = haveE.cpu; + } + wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS"; maybeCopyTriggerRegion(wantE, haveE); @@ -326,3 +341,21 @@ export function inferBlockingDetails(want: backend.Backend): void { blockingEp.blockingTrigger.options.refreshToken = refreshToken; } } + +/** + * Assigns the CPU level to a function based on its memory if CPU is not + * provided and sets concurrency based on the CPU level if not provided. + * After this function, CPU will be a real number and not "gcf_gen1". + */ +export function resolveCpu(want: backend.Backend): void { + for (const e of backend.allEndpoints(want)) { + if (e.platform === "gcfv1") { + continue; + } + if (e.cpu === "gcf_gen1") { + e.cpu = backend.memoryToGen1Cpu(e.availableMemoryMb || backend.DEFAULT_MEMORY); + } else if (!e.cpu) { + e.cpu = backend.memoryToGen2Cpu(e.availableMemoryMb || backend.DEFAULT_MEMORY); + } + } +} diff --git a/src/deploy/functions/pricing.ts b/src/deploy/functions/pricing.ts index 35dc33d9376..9042d65d855 100644 --- a/src/deploy/functions/pricing.ts +++ b/src/deploy/functions/pricing.ts @@ -187,12 +187,12 @@ export function monthlyMinInstanceCost(endpoints: backend.Endpoint[]): number { usage["gcfv1"][tier].cpu + cpu * SECONDS_PER_MONTH * endpoint.minInstances; } else { // V2 is currently fixed at 1vCPU. - const cpu = 1; const tier = V2_REGION_TO_TIER[endpoint.region]; usage["gcfv2"][tier].ram = usage["gcfv2"][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances; usage["gcfv2"][tier].cpu = - usage["gcfv2"][tier].cpu + cpu * SECONDS_PER_MONTH * endpoint.minInstances; + usage["gcfv2"][tier].cpu + + (endpoint.cpu as number) * SECONDS_PER_MONTH * endpoint.minInstances; } } diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 310d08d70f4..b8ed8e16d84 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -24,6 +24,7 @@ import * as scheduler from "../../../gcp/cloudscheduler"; import * as utils from "../../../utils"; import * as services from "../services"; import { AUTH_BLOCKING_EVENTS } from "../../../functions/events/v1"; +import { backoff } from "../../../throttler/throttler"; // TODO: Tune this for better performance. const gcfV1PollerOptions: Omit = { @@ -337,12 +338,23 @@ export class Fabricator { } const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; - if (mem >= backend.MIN_MEMORY_FOR_CONCURRENCY && endpoint.concurrency !== 1) { - await this.setConcurrency( - endpoint, - serviceName, - endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY - ); + + // "CustomCPU" here means "not the CPU that GCF gives us". GCF automatically + // sets the CPU for a Run service based on the RAM settings. The value GCF + // uses is memoryToGen1Cpu. + const hasCustomCPU = endpoint.cpu !== backend.memoryToGen1Cpu(mem); + // I don't love editing an input in this function, but the code gets ugly + // otherwise. It's nice to not resolve concurrency in the prepare stage + // because it helps us know whether we should call setRunTraits on update. + if (!endpoint.concurrency) { + endpoint.concurrency = + (endpoint.cpu as number) >= backend.MIN_CPU_FOR_CONCURRENCY + ? backend.DEFAULT_CONCURRENCY + : 1; + } + const hasConcurrency = endpoint.concurrency !== 1; + if (hasCustomCPU || hasConcurrency) { + await this.setRunTraits(serviceName, endpoint); } } @@ -431,8 +443,24 @@ export class Fabricator { .catch(rethrowAs(endpoint, "set invoker")); } - if (endpoint.concurrency) { - await this.setConcurrency(endpoint, serviceName, endpoint.concurrency); + // Ideally we'd avoid a read of the Cloud Run service. We need to read if we + // believe a setting (CPU or concurrency) has changed because the user + // changed their mind or if GCF has stamped over the user's choice (we're + // using custom VM shapes). + const hasCustomCPU = + endpoint.cpu !== + backend.memoryToGen1Cpu(endpoint.availableMemoryMb || backend.DEFAULT_MEMORY); + const explicitConcurrency = endpoint.concurrency !== undefined; + if (hasCustomCPU || explicitConcurrency) { + // GCF may have stamped over the CPU to be <1 which would reset concurrency. + // We may have to reapply defaults. + if (endpoint.concurrency === undefined) { + endpoint.concurrency = + (endpoint.cpu as number) < backend.MIN_CPU_FOR_CONCURRENCY + ? 1 + : backend.DEFAULT_CONCURRENCY; + } + await this.setRunTraits(serviceName, endpoint); } } @@ -466,23 +494,40 @@ export class Fabricator { .catch(rethrowAs(endpoint, "delete")); } - async setConcurrency( - endpoint: backend.Endpoint, - serviceName: string, - concurrency: number - ): Promise { + async setRunTraits(serviceName: string, endpoint: backend.Endpoint): Promise { await this.functionExecutor .run(async () => { - const service = await run.getService(serviceName); - if (service.spec.template.spec.containerConcurrency === concurrency) { - logger.debug("Skipping setConcurrency on", serviceName, " because it already matches"); + let service = await run.getService(serviceName); + let changed = false; + if (service.spec.template.spec.containerConcurrency !== endpoint.concurrency) { + service.spec.template.spec.containerConcurrency = endpoint.concurrency; + changed = true; + } + + if (+service.spec.template.spec.containers[0].resources.limits.cpu !== endpoint.cpu) { + service.spec.template.spec.containers[0].resources.limits.cpu = `${ + endpoint.cpu as number + }`; + changed = true; + } + + if (!changed) { + logger.debug("Skipping setRunTraits on", serviceName, " because it already matches"); return; } delete service.status; delete (service.spec.template.metadata as any).name; - service.spec.template.spec.containerConcurrency = concurrency; - await run.replaceService(serviceName, service); + service = await run.replaceService(serviceName, service); + + // Now we need to wait for reconciliation or we might delete the docker + // image while the service is still rolling out a new revision. + let retry = 0; + while (!exports.serviceIsResolved(service)) { + await backoff(retry, 2, 30); + retry = retry + 1; + service = await run.getService(serviceName); + } }) .catch(rethrowAs(endpoint, "set concurrency")); } @@ -606,3 +651,38 @@ export class Fabricator { utils.logSuccess(`${clc.bold.green(`functions[${label}]`)} Successful ${op} operation.`); } } + +/** + * Returns whether a service is resolved (all transitions have completed). + */ +export function serviceIsResolved(service: run.Service): boolean { + if (service.status?.observedGeneration !== service.metadata.generation) { + logger.debug( + `Service ${service.metadata.name} is not resolved because` + + `observed generation ${service.status?.observedGeneration} does not ` + + `match spec generation ${service.metadata.generation}` + ); + return false; + } + const readyCondition = service.status?.conditions?.find((condition) => { + return condition.type === "Ready"; + }); + + if (readyCondition?.status === "Unknown") { + logger.debug( + `Waiting for service ${service.metadata.name} to be ready. ` + + `Status is ${JSON.stringify(service.status?.conditions)}` + ); + return false; + } else if (readyCondition?.status === "True") { + return true; + } + logger.debug( + `Service ${service.metadata.name} has unexpected ready status ${JSON.stringify( + readyCondition + )}. It may have failed rollout.` + ); + throw new FirebaseError( + `Unexpected Status ${readyCondition?.status} for service ${service.metadata.name}` + ); +} diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index f101ba80df4..47ec2555495 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -241,7 +241,8 @@ function parseEndpoints( "vpc", "labels", "ingressSettings", - "environmentVariables" + "environmentVariables", + "cpu" ); allParsed.push(parsed); } diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 6449d84316a..7f3584c978b 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -34,14 +34,14 @@ export function endpointsAreValid(wantBackend: backend.Backend): void { if ((endpoint.concurrency || 1) === 1) { return false; } - const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; - return mem < backend.MIN_MEMORY_FOR_CONCURRENCY; + return (endpoint.cpu as number) < backend.MIN_CPU_FOR_CONCURRENCY; }) .map((endpoint) => endpoint.id); if (tooSmallForConcurrency.length) { - const msg = `Cannot set concurency on the functions ${tooSmallForConcurrency.join( - "," - )} because they have fewer than 2GB memory`; + const msg = + "The following functions are configured to allow concurrent " + + "execution and less than one full CPU. This is not supported: " + + tooSmallForConcurrency.join(","); throw new FirebaseError(msg); } } diff --git a/src/gcp/run.ts b/src/gcp/run.ts index 9142ab37985..dafe9194a3e 100644 --- a/src/gcp/run.ts +++ b/src/gcp/run.ts @@ -76,14 +76,27 @@ export interface ServiceStatus { export interface Service { apiVersion: "serving.knative.dev/v1"; - kind: "service"; + kind: "Service"; metadata: ObjectMetadata; spec: ServiceSpec; status?: ServiceStatus; } +export interface Container { + image: string; + ports: Array<{ name: string; containerPort: number }>; + env: Record; + resources: { + limits: { + cpu: string; + memory: string; + }; + }; +} + export interface RevisionSpec { containerConcurrency?: number | null; + containers: Container[]; } export interface RevisionTemplate { @@ -113,6 +126,9 @@ export interface IamPolicy { etag?: string; } +/** + * Gets a service with a given name. + */ export async function getService(name: string): Promise { try { const response = await client.get(name); @@ -124,6 +140,9 @@ export async function getService(name: string): Promise { } } +/** + * Replaces a service spec. + */ export async function replaceService(name: string, service: Service): Promise { try { const response = await client.put(name, service); @@ -164,6 +183,9 @@ export async function setIamPolicy( } } +/** + * Gets IAM policy for a service. + */ export async function getIamPolicy( serviceName: string, httpClient: Client = client @@ -183,7 +205,6 @@ export async function getIamPolicy( * @param projectId id of the project * @param serviceName cloud run service * @param invoker an array of invoker strings - * * @throws {@link FirebaseError} on an empty invoker, when the IAM Polciy fails to be grabbed or set */ export async function setInvokerCreate( @@ -213,7 +234,6 @@ export async function setInvokerCreate( * @param projectId id of the project * @param serviceName cloud run service * @param invoker an array of invoker strings - * * @throws {@link FirebaseError} on an empty invoker, when the IAM Polciy fails to be grabbed or set */ export async function setInvokerUpdate( diff --git a/src/test/deploy/functions/backend.spec.ts b/src/test/deploy/functions/backend.spec.ts index 0c55230f2f9..276736d0739 100644 --- a/src/test/deploy/functions/backend.spec.ts +++ b/src/test/deploy/functions/backend.spec.ts @@ -7,6 +7,7 @@ import * as args from "../../../deploy/functions/args"; import * as backend from "../../../deploy/functions/backend"; import * as gcf from "../../../gcp/cloudfunctions"; import * as gcfV2 from "../../../gcp/cloudfunctionsv2"; +import * as run from "../../../gcp/run"; import * as utils from "../../../utils"; import * as projectConfig from "../../../functions/projectConfig"; @@ -47,7 +48,48 @@ describe("Backend", () => { }, environmentVariables: {}, }, - serviceConfig: {}, + serviceConfig: { + service: "projects/project/locations/region/services/service", + }, + }; + + const CLOUD_RUN_SERVICE: run.Service = { + apiVersion: "serving.knative.dev/v1", + kind: "Service", + metadata: { + name: "service", + namespace: "projectnumber", + }, + spec: { + template: { + spec: { + containerConcurrency: 80, + containers: [ + { + image: "image", + ports: [ + { + name: "main", + containerPort: 8080, + }, + ], + env: {}, + resources: { + limits: { + memory: "256MiB", + cpu: "1", + }, + }, + }, + ], + }, + metadata: { + name: "service", + namespace: "project", + }, + }, + traffic: [], + }, }; const RUN_URI = "https://id-nonce-region-project.run.app"; @@ -120,18 +162,21 @@ describe("Backend", () => { let listAllFunctions: sinon.SinonStub; let listAllFunctionsV2: sinon.SinonStub; let logLabeledWarning: sinon.SinonSpy; + let getService: sinon.SinonStub; beforeEach(() => { previews.functionsv2 = false; listAllFunctions = sinon.stub(gcf, "listAllFunctions").rejects("Unexpected call"); listAllFunctionsV2 = sinon.stub(gcfV2, "listAllFunctions").rejects("Unexpected v2 call"); logLabeledWarning = sinon.spy(utils, "logLabeledWarning"); + getService = sinon.stub(run, "getService").rejects("Unexpected call to getService"); }); afterEach(() => { listAllFunctions.restore(); listAllFunctionsV2.restore(); logLabeledWarning.restore(); + getService.restore(); }); function newContext(): args.Context { @@ -254,6 +299,9 @@ describe("Backend", () => { it("should read v2 functions when enabled", async () => { previews.functionsv2 = true; + getService + .withArgs(HAVE_CLOUD_FUNCTION_V2.serviceConfig.service!) + .resolves(CLOUD_RUN_SERVICE); listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: [], @@ -268,10 +316,13 @@ describe("Backend", () => { backend.of({ ...ENDPOINT, platform: "gcfv2", + concurrency: 80, + cpu: 1, httpsTrigger: {}, uri: HAVE_CLOUD_FUNCTION_V2.serviceConfig.uri, }) ); + expect(getService).to.have.been.called; }); it("should deduce features of scheduled functions", async () => { diff --git a/src/test/deploy/functions/pricing.spec.ts b/src/test/deploy/functions/pricing.spec.ts index f2d89704aed..e9416841109 100644 --- a/src/test/deploy/functions/pricing.spec.ts +++ b/src/test/deploy/functions/pricing.spec.ts @@ -224,6 +224,7 @@ describe("Functions Pricing", () => { region: "us-central1", minInstances: 1, availableMemoryMb: 256, + cpu: 1, }, ]); @@ -242,6 +243,7 @@ describe("Functions Pricing", () => { region: "europe-west3", minInstances: 1, availableMemoryMb: 256, + cpu: 1, }, ]); @@ -260,11 +262,12 @@ describe("Functions Pricing", () => { region: "europe-west3", minInstances: 1, availableMemoryMb: 4096, + cpu: 2, }, ]); const ramCost = pricing.V2_RATES.memoryGb[2] * 4 * SECONDS_PER_MONTH; - const cpuCost = pricing.V2_RATES.idleVCpu[2] * SECONDS_PER_MONTH; + const cpuCost = pricing.V2_RATES.idleVCpu[2] * 2 * SECONDS_PER_MONTH; const expected = v2CostAfterDiscounts(ramCost, cpuCost); expect(cost).to.equal(expected); @@ -283,6 +286,7 @@ describe("Functions Pricing", () => { platform: "gcfv2", region: "us-central1", minInstances: 1, + cpu: 1, }, ]); diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 3ba8892b477..e81bebd17e9 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -19,6 +19,7 @@ import * as v1events from "../../../../functions/events/v1"; import * as servicesNS from "../../../../deploy/functions/services"; import * as identityPlatformNS from "../../../../gcp/identityPlatform"; import { AuthBlockingService } from "../../../../deploy/functions/services/auth"; +import { define } from "mime"; describe("Fabricator", () => { // Stub all GCP APIs to make sure this test is hermetic @@ -123,6 +124,8 @@ describe("Fabricator", () => { region: "us-central1", entryPoint: "entrypoint", runtime: "nodejs16", + availableMemoryMb: 256, + cpu: backend.memoryToGen1Cpu(256), codebase: "default", ...JSON.parse(JSON.stringify(base)), ...trigger, @@ -421,11 +424,11 @@ describe("Fabricator", () => { }); describe("createV2Function", () => { - let setConcurrency: sinon.SinonStub; + let setRunTraits: sinon.SinonStub; beforeEach(() => { - setConcurrency = sinon.stub(fab, "setConcurrency"); - setConcurrency.resolves(); + setRunTraits = sinon.stub(fab, "setRunTraits"); + setRunTraits.resolves(); }); it("handles topics that already exist", async () => { @@ -501,25 +504,49 @@ describe("Fabricator", () => { ); }); - it("sets concurrency by default for large functions", async () => { + it("sets run traits by default for large functions", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); - const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2", availableMemoryMb: 2048 }); + const ep = endpoint( + { httpsTrigger: {} }, + { platform: "gcfv2", availableMemoryMb: 256, cpu: 1 } + ); + + await fab.createV2Function(ep); + expect(setRunTraits).to.have.been.calledWith("service", ep); + }); + + it("sets run traits for functions with concurrency", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint( + { httpsTrigger: {} }, + { platform: "gcfv2", availableMemoryMb: 2048, cpu: 1, concurrency: 80 } + ); await fab.createV2Function(ep); - expect(setConcurrency).to.have.been.calledWith(ep, "service", 80); + expect(setRunTraits).to.have.been.calledWith("service", ep); }); it("does not set concurrency by default for small functions", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); - const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2", availableMemoryMb: 256 }); + const ep = endpoint( + { httpsTrigger: {} }, + { + platform: "gcfv2", + availableMemoryMb: 256, + cpu: backend.memoryToGen1Cpu(256), + concurrency: 1, + } + ); await fab.createV2Function(ep); expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["public"]); - expect(setConcurrency).to.not.have.been.called; + expect(setRunTraits).to.not.have.been.called; }); it("sets explicit concurrency", async () => { @@ -528,11 +555,11 @@ describe("Fabricator", () => { run.setInvokerCreate.resolves(); const ep = endpoint( { httpsTrigger: {} }, - { platform: "gcfv2", availableMemoryMb: 2048, concurrency: 2 } + { platform: "gcfv2", availableMemoryMb: 2048, cpu: 1, concurrency: 2 } ); await fab.createV2Function(ep); - expect(setConcurrency).to.have.been.calledWith(ep, "service", 2); + expect(setRunTraits).to.have.been.calledWith("service", ep); }); describe("httpsTrigger", () => { @@ -641,6 +668,13 @@ describe("Fabricator", () => { }); describe("updateV2Function", () => { + let setRunTraits: sinon.SinonStub; + + beforeEach(() => { + setRunTraits = sinon.stub(fab, "setRunTraits"); + setRunTraits.rejects(new Error("Unexpected setRunTraits call")); + }); + it("throws on update function failure", async () => { gcfv2.updateFunction.rejects(new Error("Server failure")); @@ -734,6 +768,27 @@ describe("Fabricator", () => { await fab.updateV2Function(ep); expect(run.setInvokerUpdate).to.not.have.been.called; }); + + it("Reapplies customer VM preferences after GCF overwrite", async () => { + setRunTraits.resolves(); + gcfv2.updateFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerUpdate.resolves(); + const ep = endpoint( + { httpsTrigger: {} }, + { + platform: "gcfv2", + availableMemoryMb: 256, + cpu: 1, + } + ); + + await fab.updateV2Function(ep); + expect(setRunTraits).to.have.been.calledWithMatch("service", { + cpu: 1, + concurrency: backend.DEFAULT_CONCURRENCY, + }); + }); }); describe("deleteV2Function", () => { @@ -750,12 +805,18 @@ describe("Fabricator", () => { }); }); - describe("setConcurrency", () => { + describe("setRunTraits", () => { let service: runNS.Service; + let serviceIsResolved: sinon.SinonStub; + beforeEach(() => { + serviceIsResolved = sinon + .stub(fabricator, "serviceIsResolved") + .throws(new Error("Unexpected serviceIsResolved call")); + service = { apiVersion: "serving.knative.dev/v1", - kind: "service", + kind: "Service", metadata: { name: "service", namespace: "project", @@ -767,7 +828,25 @@ describe("Fabricator", () => { namespace: "project", }, spec: { - containerConcurrency: 80, + containerConcurrency: 1, + containers: [ + { + image: "image", + ports: [ + { + name: "main", + containerPort: 8080, + }, + ], + env: {}, + resources: { + limits: { + memory: "256M", + cpu: "0.1667", + }, + }, + }, + ], }, }, traffic: [], @@ -775,47 +854,198 @@ describe("Fabricator", () => { }; }); - it("sets concurrency when necessary", async () => { + afterEach(() => { + serviceIsResolved.restore(); + }); + + it("replaces the service to set concurrency", async () => { + run.getService.resolves(service); + run.replaceService.callsFake((name: string, svc: runNS.Service) => { + expect(svc.spec.template.spec.containerConcurrency).equals(80); + expect(svc.spec.template.spec.containers[0].resources.limits.cpu).equals("1"); + // Run throws if this field is set + expect(svc.spec.template.metadata.name).is.undefined; + return Promise.resolve(service); + }); + + serviceIsResolved.onFirstCall().returns(false).onSecondCall().returns(true); + + await fab.setRunTraits( + service.metadata.name, + endpoint( + { httpsTrigger: {} }, + { + cpu: 1, + concurrency: 80, + } + ) + ); + expect(run.replaceService).to.have.been.called; + expect(serviceIsResolved).to.have.been.calledTwice; + }); + + it("replaces the service to set CPU", async () => { + service.spec.template.spec.containers[0].resources.limits.cpu = (1 / 6).toString(); run.getService.resolves(service); run.replaceService.callsFake((name: string, svc: runNS.Service) => { expect(svc.spec.template.spec.containerConcurrency).equals(1); + expect(svc.spec.template.spec.containers[0].resources.limits.cpu).equals("1"); // Run throws if this field is set expect(svc.spec.template.metadata.name).is.undefined; return Promise.resolve(service); }); + serviceIsResolved.onFirstCall().returns(false).onSecondCall().returns(true); - await fab.setConcurrency(endpoint(), "service", 1); + await fab.setRunTraits( + service.metadata.name, + endpoint( + { httpsTrigger: {} }, + { + cpu: 1, + concurrency: 1, + } + ) + ); expect(run.replaceService).to.have.been.called; + expect(serviceIsResolved).to.have.been.calledTwice; }); - it("doesn't set concurrency when already at the correct value", async () => { + it("doesn't setRunTraits when already at the correct value", async () => { + service.spec.template.spec.containerConcurrency = 80; + service.spec.template.spec.containers[0].resources.limits.cpu = "1"; run.getService.resolves(service); - await fab.setConcurrency( - endpoint(), + await fab.setRunTraits( "service", - service.spec.template.spec.containerConcurrency! + endpoint( + { + httpsTrigger: {}, + }, + { + cpu: 1, + concurrency: 80, + } + ) ); expect(run.replaceService).to.not.have.been.called; + expect(serviceIsResolved).to.not.have.been.called; }); it("wraps errors", async () => { run.getService.rejects(new Error("Oh noes!")); - await expect(fab.setConcurrency(endpoint(), "service", 1)).to.eventually.be.rejectedWith( + await expect(fab.setRunTraits("service", endpoint())).to.eventually.be.rejectedWith( reporter.DeploymentError, "set concurrency" ); run.getService.resolves(service); run.replaceService.rejects(new Error("read only")); - await expect(fab.setConcurrency(endpoint(), "service", 1)).to.eventually.be.rejectedWith( + await expect(fab.setRunTraits("service", endpoint())).to.eventually.be.rejectedWith( reporter.DeploymentError, "set concurrency" ); }); }); + describe("serviceIsResolved", () => { + let service: runNS.Service; + beforeEach(() => { + service = { + apiVersion: "serving.knative.dev/v1", + kind: "Service", + metadata: { + name: "service", + namespace: "project", + generation: 2, + }, + spec: { + template: { + metadata: { + name: "service", + namespace: "project", + }, + spec: { + containerConcurrency: 1, + containers: [ + { + image: "image", + ports: [ + { + name: "main", + containerPort: 8080, + }, + ], + env: {}, + resources: { + limits: { + memory: "256M", + cpu: "0.1667", + }, + }, + }, + ], + }, + }, + traffic: [], + }, + status: { + observedGeneration: 2, + conditions: [ + { + status: "True", + type: "Ready", + reason: "Testing", + lastTransitionTime: "", + message: "", + severity: "Info", + }, + ], + latestCreatedRevisionName: "", + latestRevisionName: "", + traffic: [], + url: "", + address: { + url: "", + }, + }, + }; + }); + + it("returns false if the observed generation isn't the metageneration", () => { + service.status!.observedGeneration = 1; + service.metadata.generation = 2; + expect(fabricator.serviceIsResolved(service)).to.be.false; + }); + + it("returns false if the status is not ready", () => { + service.status!.observedGeneration = 2; + service.metadata.generation = 2; + service.status!.conditions[0].status = "Unknown"; + service.status!.conditions[0].type = "Ready"; + + expect(fabricator.serviceIsResolved(service)).to.be.false; + }); + + it("throws if we have an failed status", () => { + service.status!.observedGeneration = 2; + service.metadata.generation = 2; + service.status!.conditions[0].status = "False"; + service.status!.conditions[0].type = "Ready"; + + expect(() => fabricator.serviceIsResolved(service)).to.throw; + }); + + it("returns true if resolved", () => { + service.status!.observedGeneration = 2; + service.metadata.generation = 2; + service.status!.conditions[0].status = "True"; + service.status!.conditions[0].type = "Ready"; + + expect(fabricator.serviceIsResolved(service)).to.be.true; + }); + }); + describe("upsertScheduleV1", () => { const ep = endpoint({ scheduleTrigger: { diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index 516e259023c..83746fbd5b0 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -8,6 +8,7 @@ import * as projectPath from "../../../projectPath"; import * as secretManager from "../../../gcp/secretManager"; import * as backend from "../../../deploy/functions/backend"; import { BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT } from "../../../functions/events/v1"; +import { resolveCpu } from "../../../deploy/functions/prepare"; describe("validate", () => { describe("functionsDirectoryExists", () => { @@ -132,17 +133,30 @@ describe("validate", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, platform: "gcfv1", - availableMemoryMb: backend.MIN_MEMORY_FOR_CONCURRENCY, + availableMemoryMb: 256, concurrency: 2, }; expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw(/GCF gen 1/); }); + it("Disallows concurrency for low-CPU gen 2", () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + platform: "gcfv2", + cpu: 1 / 6, + concurrency: 2, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( + /concurrent execution and less than one full CPU/ + ); + }); + it("Allows endpoints with no mem and no concurrency", () => { expect(() => validate.endpointsAreValid(backend.of(ENDPOINT_BASE))).to.not.throw; }); - it("Allows endpionts with mem and no concurrency", () => { + it("Allows endpoints with mem and no concurrency", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, availableMemoryMb: 256, @@ -163,7 +177,9 @@ describe("validate", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, availableMemoryMb: mem, + cpu: "gcf_gen1", }; + resolveCpu(backend.of(ep)); expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; } }); @@ -173,8 +189,10 @@ describe("validate", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, availableMemoryMb: mem, + cpu: "gcf_gen1", concurrency: 42, }; + resolveCpu(backend.of(ep)); expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; } }); @@ -182,21 +200,24 @@ describe("validate", () => { it("disallows concurrency with too little memory (implicit)", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, + availableMemoryMb: 256, concurrency: 2, + cpu: "gcf_gen1", }; + resolveCpu(backend.of(ep)); expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( - /they have fewer than 2GB memory/ + /concurrent execution and less than one full CPU/ ); }); - it("disallows concurrency with too little memory (explicit)", () => { + it("Disallows concurrency with too little cpu (explicit)", () => { const ep: backend.Endpoint = { ...ENDPOINT_BASE, concurrency: 2, - availableMemoryMb: 512, + cpu: 0.5, }; expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( - /they have fewer than 2GB memory/ + /concurrent execution and less than one full CPU/ ); }); diff --git a/src/throttler/throttler.ts b/src/throttler/throttler.ts index d27d8592290..c43de05a029 100644 --- a/src/throttler/throttler.ts +++ b/src/throttler/throttler.ts @@ -3,13 +3,19 @@ import RetriesExhaustedError from "./errors/retries-exhausted-error"; import TimeoutError from "./errors/timeout-error"; import TaskError from "./errors/task-error"; -function backoff(retryNumber: number, delay: number, maxDelay: number): Promise { +/** + * Creates a promise to wait for the nth backoff. + */ +export function backoff(retryNumber: number, delay: number, maxDelay: number): Promise { return new Promise((resolve: () => void) => { setTimeout(resolve, timeToWait(retryNumber, delay, maxDelay)); }); } // Exported for unit testing. +/** + * time to wait between backoffs + */ export function timeToWait(retryNumber: number, delay: number, maxDelay: number): number { return Math.min(delay * Math.pow(2, retryNumber), maxDelay); } @@ -58,26 +64,26 @@ interface TaskData { * 2. Not specify the handler, but T must be () => R. */ export abstract class Throttler { - name: string = ""; - concurrency: number = 200; + name = ""; + concurrency = 200; handler: (task: T) => Promise = DEFAULT_HANDLER; - active: number = 0; - complete: number = 0; - success: number = 0; - errored: number = 0; - retried: number = 0; - total: number = 0; + active = 0; + complete = 0; + success = 0; + errored = 0; + retried = 0; + total = 0; taskDataMap = new Map>(); waits: Array<{ resolve: () => void; reject: (err: Error) => void }> = []; - min: number = 9999999999; - max: number = 0; - avg: number = 0; - retries: number = 0; - backoff: number = 200; - maxBackoff: number = 60000; // 1 minute - closed: boolean = false; - finished: boolean = false; - startTime: number = 0; + min = 9999999999; + max = 0; + avg = 0; + retries = 0; + backoff = 200; + maxBackoff = 60000; // 1 minute + closed = false; + finished = false; + startTime = 0; constructor(options: ThrottlerOptions) { if (options.name) { From c0f19a32845135108c75a1050024965cb1e3f52d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 2 May 2022 23:34:28 -0700 Subject: [PATCH 0291/1699] Add support for secrets in gcfv2 endpoints (#4451) We now read `secretEnvironmentVariables` property from the endpoint manifest and include the property when creating/updating v1 and v2 functions. Side note - if secretEnvVar in the manifest is missing `secret` property (i.e. doesn't specify the secret resource name), we'll assume that resource name matches the `key` property. This will be the case for all CF3 functions. --- CHANGELOG.md | 1 + src/deploy/functions/release/index.ts | 5 +- .../functions/runtimes/discovery/v1alpha1.ts | 18 +++++++ src/deploy/functions/validate.ts | 6 +-- src/gcp/cloudfunctions.ts | 5 +- src/gcp/cloudfunctionsv2.ts | 22 +++++++- src/test/deploy/functions/validate.spec.ts | 52 ++++++++++++------- src/test/gcp/cloudfunctionsv2.spec.ts | 14 +++++ 8 files changed, 95 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..8b96987c7de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Add support for secrets to v2 functions (#4451). diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index 1518a499873..f03c2f8aafc 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -106,7 +106,10 @@ export async function release( const projectId = needProjectId(options); const projectNumber = await needProjectNumber(options); // Re-load backend with all endpoints, not just the ones deployed. - const reloadedBackend = await backend.existingBackend({ projectId } as args.Context); + const reloadedBackend = await backend.existingBackend( + { projectId } as args.Context, + /* forceRefresh= */ true + ); const prunedResult = await secrets.pruneAndDestroySecrets( { projectId, projectNumber }, backend.allEndpoints(reloadedBackend) diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 47ec2555495..681e7e72ad6 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -244,6 +244,24 @@ function parseEndpoints( "environmentVariables", "cpu" ); + renameIfPresent( + parsed, + ep, + "secretEnvironmentVariables", + "secretEnvironmentVariables", + (senvs: ManifestEndpoint["secretEnvironmentVariables"]) => { + if (senvs && senvs.length > 0) { + ep.secretEnvironmentVariables = []; + for (const { key, secret } of senvs) { + ep.secretEnvironmentVariables.push({ + key, + secret: secret || key, // if secret is undefined, assume env var key == secret name + projectId: project, + }); + } + } + } + ); allParsed.push(parsed); } diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 7f3584c978b..928a4b67c82 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -136,17 +136,17 @@ export async function secretsAreValid(projectId: string, wantBackend: backend.Ba await validateSecretVersions(projectId, endpoints); } +const secretsSupportedPlatforms = ["gcfv1", "gcfv2"]; /** * Ensures that all endpoints specifying secret environment variables target platform that supports the feature. */ function validatePlatformTargets(endpoints: backend.Endpoint[]) { - const supportedPlatforms = ["gcfv1"]; - const unsupported = endpoints.filter((e) => !supportedPlatforms.includes(e.platform)); + const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform)); if (unsupported.length > 0) { const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`); throw new FirebaseError( `Tried to set secret environment variables on ${errs.join(", ")}. ` + - `Only ${supportedPlatforms.join(", ")} support secret environments.` + `Only ${secretsSupportedPlatforms.join(", ")} support secret environments.` ); } } diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index f26c316de7c..fe86ddb9ce8 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -370,12 +370,13 @@ export async function updateFunction( cloudFunction: Omit ): Promise { const endpoint = `/${cloudFunction.name}`; - // Keys in labels and environmentVariables are user defined, so we don't recurse + // Keys in labels and environmentVariables and secretEnvironmentVariables are user defined, so we don't recurse // for field masks. const fieldMasks = proto.fieldMasks( cloudFunction, /* doNotRecurseIn...=*/ "labels", - "environmentVariables" + "environmentVariables", + "secretEnvironmentVariables" ); // Failure policy is always an explicit policy and is only signified by the presence or absence of diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 39b900e7eb6..e4e72a45af4 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -92,6 +92,20 @@ export interface EventFilter { value: string; } +/** + * Configurations for secret environment variables attached to a cloud functions resource. + */ +export interface SecretEnvVar { + /* Name of the environment variable. */ + key: string; + /* Project identifier (or project number) of the project that contains the secret. */ + projectId: string; + /* Name of the secret in secret manager. e.g. MY_SECRET, NOT projects/abc/secrets/MY_SECRET */ + secret: string; + /* Version of the secret (version number or the string 'latest') */ + version?: string; +} + /** The Cloud Run service that underlies a Cloud Function. */ export interface ServiceConfig { // Output only @@ -104,6 +118,7 @@ export interface ServiceConfig { timeoutSeconds?: number; availableMemory?: string; environmentVariables?: Record; + secretEnvironmentVariables?: SecretEnvVar[]; maxInstanceCount?: number; minInstanceCount?: number; vpcConnector?: string; @@ -351,12 +366,13 @@ async function listFunctionsInternal( export async function updateFunction( cloudFunction: Omit ): Promise { - // Keys in labels and environmentVariables are user defined, so we don't recurse + // Keys in labels and environmentVariables and secretEnvironmentVariables are user defined, so we don't recurse // for field masks. const fieldMasks = proto.fieldMasks( cloudFunction, /* doNotRecurseIn...=*/ "labels", - "serviceConfig.environmentVariables" + "serviceConfig.environmentVariables", + "serviceConfig.secretEnvironmentVariables" ); try { const queryParams = { @@ -422,6 +438,7 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage gcfFunction.serviceConfig, endpoint, "environmentVariables", + "secretEnvironmentVariables", "serviceAccountEmail", "ingressSettings", "timeoutSeconds" @@ -586,6 +603,7 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi "serviceAccountEmail", "ingressSettings", "environmentVariables", + "secretEnvironmentVariables", "timeoutSeconds" ); proto.renameIfPresent( diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index 83746fbd5b0..aba8af7d76d 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -420,10 +420,12 @@ describe("validate", () => { expect(validate.secretsAreValid(project, b)).to.not.be.rejected; }); - it("fails validation given endpoint with secrets targeting unsupported platform", () => { + it("fails validation given non-existent secret version", () => { + secretVersionStub.rejects({ reason: "Secret version does not exist" }); + const b = backend.of({ ...ENDPOINT, - platform: "gcfv2", + platform: "gcfv1", secretEnvironmentVariables: [ { projectId: project, @@ -432,8 +434,10 @@ describe("validate", () => { }, ], }); - - expect(validate.secretsAreValid(project, b)).to.be.rejectedWith(FirebaseError); + expect(validate.secretsAreValid(project, b)).to.be.rejectedWith( + FirebaseError, + /Failed to validate secret version/ + ); }); it("fails validation given non-existent secret version", () => { @@ -450,7 +454,10 @@ describe("validate", () => { }, ], }); - expect(validate.secretsAreValid(project, b)).to.be.rejectedWith(FirebaseError); + expect(validate.secretsAreValid(project, b)).to.be.rejectedWith( + FirebaseError, + /Failed to validate secret versions/ + ); }); it("fails validation given disabled secret version", () => { @@ -471,7 +478,10 @@ describe("validate", () => { }, ], }); - expect(validate.secretsAreValid(project, b)).to.be.rejected; + expect(validate.secretsAreValid(project, b)).to.be.rejectedWith( + FirebaseError, + /Failed to validate secret versions/ + ); }); it("passes validation and resolves latest version given valid secret config", async () => { @@ -481,20 +491,22 @@ describe("validate", () => { state: "ENABLED", }); - const b = backend.of({ - ...ENDPOINT, - platform: "gcfv1", - secretEnvironmentVariables: [ - { - projectId: project, - secret: "MY_SECRET", - key: "MY_SECRET", - }, - ], - }); - - await validate.secretsAreValid(project, b); - expect(backend.allEndpoints(b)[0].secretEnvironmentVariables![0].version).to.equal("2"); + for (const platform of ["gcfv1" as const, "gcfv2" as const]) { + const b = backend.of({ + ...ENDPOINT, + platform, + secretEnvironmentVariables: [ + { + projectId: project, + secret: "MY_SECRET", + key: "MY_SECRET", + }, + ], + }); + + await validate.secretsAreValid(project, b); + expect(backend.allEndpoints(b)[0].secretEnvironmentVariables![0].version).to.equal("2"); + } }); }); }); diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 396c53a38ea..1c1606e0eb3 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -211,6 +211,13 @@ describe("cloudfunctionsv2", () => { environmentVariables: { FOO: "bar", }, + secretEnvironmentVariables: [ + { + secret: "MY_SECRET", + key: "MY_SECRET", + projectId: "project", + }, + ], }; const fullGcfFunction: Omit< @@ -227,6 +234,13 @@ describe("cloudfunctionsv2", () => { environmentVariables: { FOO: "bar", }, + secretEnvironmentVariables: [ + { + secret: "MY_SECRET", + key: "MY_SECRET", + projectId: "project", + }, + ], vpcConnector: "connector", vpcConnectorEgressSettings: "ALL_TRAFFIC", ingressSettings: "ALLOW_ALL", From c781e3c1b73763f0aaff951720f2ad480992212b Mon Sep 17 00:00:00 2001 From: huangjeff5 <64040981+huangjeff5@users.noreply.github.com> Date: Wed, 4 May 2022 09:08:13 -0400 Subject: [PATCH 0292/1699] Extensions Events CLI Experience (#4461) --- src/commands/ext-configure.ts | 60 +++++---- src/commands/ext-export.ts | 19 ++- src/commands/ext-install.ts | 40 +++++- src/commands/ext-update.ts | 38 +++--- src/deploy/extensions/planner.ts | 21 +++ src/deploy/extensions/tasks.ts | 13 ++ src/emulator/extensionsEmulator.ts | 4 +- src/extensions/askUserForEventsConfig.ts | 121 ++++++++++++++++++ src/extensions/export.ts | 6 + src/extensions/extensionsApi.ts | 89 ++++++++++++- src/extensions/updateHelper.ts | 20 ++- src/test/deploy/extensions/planner.spec.ts | 99 ++++++++++++++ src/test/emulators/extensionsEmulator.spec.ts | 11 ++ .../extensions/askUserForEventsConfig.spec.ts | 83 ++++++++++++ src/test/extensions/extensionsApi.spec.ts | 74 +++++++++-- src/test/extensions/manifest.spec.ts | 115 +++++++++++++++++ 16 files changed, 749 insertions(+), 64 deletions(-) create mode 100644 src/extensions/askUserForEventsConfig.ts create mode 100644 src/test/extensions/askUserForEventsConfig.spec.ts diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 8ea557307d0..860b9932660 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -25,6 +25,7 @@ import * as manifest from "../extensions/manifest"; import { Options } from "../options"; import { partition } from "../functional"; import { buildBindingOptionsWithBaseValue, getBaseParamBindings } from "../extensions/paramHelper"; +import * as askUserForEventsConfig from "../extensions/askUserForEventsConfig"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -90,6 +91,20 @@ export default new Command("ext:configure ") reconfiguring: true, }); + const eventsConfig = spec.events + ? await askUserForEventsConfig.askForEventsConfig( + spec.events, + "${param:PROJECT_ID}", + instanceId + ) + : undefined; + if (eventsConfig) { + mutableParamsBindingOptions.EVENTARC_CHANNEL = { baseValue: eventsConfig.channel }; + mutableParamsBindingOptions.ALLOWED_EVENT_TYPES = { + baseValue: eventsConfig.allowedEventTypes.join(","), + }; + } + // Merge with old immutable params. const newParamOptions = { ...buildBindingOptionsWithBaseValue(oldParamValues), @@ -115,29 +130,20 @@ export default new Command("ext:configure ") manifest.showPreviewWarning(); return; } - + if (!projectId) { + throw new FirebaseError( + `Project ID must be provided when re-configuring an instance outside of local mode.` + ); + } // TODO(b/220900194): Remove everything below and make --local the default behavior. const spinner = ora( `Configuring ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...` ); try { - let existingInstance: extensionsApi.ExtensionInstance; - try { - existingInstance = await extensionsApi.getInstance( - needProjectId({ projectId }), - instanceId - ); - } catch (err: any) { - if (err.status === 404) { - return utils.reject( - `No extension instance ${instanceId} found in project ${projectId}.`, - { - exit: 1, - } - ); - } - throw err; - } + const existingInstance = await extensionsApi.getInstance( + needProjectId({ projectId }), + instanceId + ); const paramSpecWithNewDefaults = paramHelper.getParamsWithCurrentValuesAsDefaults(existingInstance); const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => param.immutable); @@ -166,13 +172,23 @@ export default new Command("ext:configure ") ", uninstall the extension, then install a new instance of this extension." ); } - + // Call needProjectId to guarantee that project ID exists, or this errors out. + const pId = needProjectId({ projectId }); + const spec = existingInstance ? existingInstance.config.source.spec : undefined; + const eventsConfig = spec.events + ? await askUserForEventsConfig.askForEventsConfig(spec.events, pId, instanceId) + : undefined; spinner.start(); - const res = await extensionsApi.configureInstance({ - projectId: needProjectId({ projectId }), + + const configureOptions: any = { + projectId: pId, instanceId, params: paramBindings, - }); + canEmitEvents: eventsConfig ? true : false, + eventarcChannel: eventsConfig?.channel, + allowedEventTypes: eventsConfig?.allowedEventTypes, + }; + const res = await extensionsApi.configureInstance(configureOptions); spinner.stop(); utils.logLabeledSuccess(logPrefix, `successfully configured ${clc.bold(instanceId)}.`); utils.logLabeledBullet( diff --git a/src/commands/ext-export.ts b/src/commands/ext-export.ts index ae9cc1bd78c..67d6913543e 100644 --- a/src/commands/ext-export.ts +++ b/src/commands/ext-export.ts @@ -64,11 +64,20 @@ module.exports = new Command("ext:export") return; } - const manifestSpecs = withRef.map((spec) => ({ - instanceId: spec.instanceId, - ref: spec.ref, - params: buildBindingOptionsWithBaseValue(spec.params), - })); + const manifestSpecs = withRef.map((spec) => { + const paramCopy = { ...spec.params }; + if (spec.eventarcChannel) { + paramCopy.EVENTARC_CHANNEL = spec.eventarcChannel; + } + if (spec.allowedEventTypes) { + paramCopy.ALLOWED_EVENT_TYPES = spec.allowedEventTypes.join(","); + } + return { + instanceId: spec.instanceId, + ref: spec.ref, + params: buildBindingOptionsWithBaseValue(paramCopy), + }; + }); const existingConfig = manifest.loadConfig(options); await manifest.writeToManifest( diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 7245c897196..e13819b16b9 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -6,6 +6,7 @@ import TerminalRenderer = require("marked-terminal"); import * as askUserForConsent from "../extensions/askUserForConsent"; import { displayExtInfo } from "../extensions/displayExtensionInfo"; +import * as askUserForEventsConfig from "../extensions/askUserForEventsConfig"; import { displayNode10CreateBillingNotice } from "../extensions/billingMigrationHelper"; import { enableBilling } from "../extensions/checkProjectBilling"; import { checkBillingEnabled } from "../gcp/cloudbilling"; @@ -33,7 +34,7 @@ import { isLocalPath, canonicalizeRefInput, } from "../extensions/extensionsHelper"; -import { update } from "../extensions/updateHelper"; +import { update, UpdateOptions } from "../extensions/updateHelper"; import { getRandomString } from "../extensions/utils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; @@ -247,7 +248,19 @@ async function installToManifest(options: InstallExtensionOptions): Promise reason: `To access and manage secrets which are used by this extension. By using this product you agree to the terms and conditions of the following license: https://console.cloud.google.com/tos?id=cloud&project=${projectId}`, }); } + if (spec.events && spec.events.length > 0) { + apis.push({ + apiName: "eventarc.googleapis.com", + reason: `When events are enabled, the Eventarc API is required to provision an event channel and publish events.`, + }); + } if (apis.length) { askUserForConsent.displayApis(spec.displayName || spec.name, projectId, apis); const consented = await confirm({ nonInteractive, force, default: true }); @@ -358,6 +377,7 @@ async function installExtension(options: InstallExtensionOptions): Promise } let paramBindingOptions: { [key: string]: ParamBindingOptions }; let paramBindings: Record; + let eventsConfig: askUserForEventsConfig.InstanceEventsConfig | undefined; switch (choice) { case "installNew": instanceId = await promptForValidInstanceId(`${instanceId}-${getRandomString(4)}`); @@ -368,6 +388,9 @@ async function installExtension(options: InstallExtensionOptions): Promise paramsEnvPath, instanceId, }); + eventsConfig = spec.events + ? await askUserForEventsConfig.askForEventsConfig(spec.events, projectId, instanceId) + : undefined; paramBindings = getBaseParamBindings(paramBindingOptions); spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes..."; spinner.start(); @@ -377,6 +400,8 @@ async function installExtension(options: InstallExtensionOptions): Promise extensionSource: source, extensionVersionRef: extVersion?.ref, params: paramBindings, + allowedEventTypes: eventsConfig?.allowedEventTypes, + eventarcChannel: eventsConfig?.channel, }); spinner.stop(); utils.logLabeledSuccess( @@ -393,16 +418,23 @@ async function installExtension(options: InstallExtensionOptions): Promise paramsEnvPath, instanceId, }); + eventsConfig = spec.events + ? await askUserForEventsConfig.askForEventsConfig(spec.events, projectId, instanceId) + : undefined; paramBindings = getBaseParamBindings(paramBindingOptions); spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes..."; spinner.start(); - await update({ + const updateOptions: UpdateOptions = { projectId, instanceId, source, + canEmitEvents: eventsConfig ? true : false, + eventarcChannel: eventsConfig?.channel, + allowedEventTypes: eventsConfig?.allowedEventTypes, extRef: extVersion?.ref, params: paramBindings, - }); + }; + await update(updateOptions); spinner.stop(); utils.logLabeledSuccess( logPrefix, diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index a5779641f41..cf46282eae6 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -42,6 +42,7 @@ import * as utils from "../utils"; import { previews } from "../previews"; import * as manifest from "../extensions/manifest"; import { Options } from "../options"; +import * as askUserForEventsConfig from "../extensions/askUserForEventsConfig"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -143,7 +144,19 @@ export default new Command("ext:update [updateSource]") nonInteractive: options.nonInteractive, instanceId, }); - + const eventsConfig = newExtensionVersion.spec.events + ? await askUserForEventsConfig.askForEventsConfig( + newExtensionVersion.spec.events, + "${param:PROJECT_ID}", + instanceId + ) + : undefined; + if (eventsConfig) { + newParamBindingOptions.EVENTARC_CHANNEL = { baseValue: eventsConfig.channel }; + newParamBindingOptions.ALLOWED_EVENT_TYPES = { + baseValue: eventsConfig.allowedEventTypes.join(","), + }; + } await manifest.writeToManifest( [ { @@ -167,20 +180,8 @@ export default new Command("ext:update [updateSource]") const spinner = ora(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`); try { const projectId = needProjectId(options); - let existingInstance: extensionsApi.ExtensionInstance; - try { - existingInstance = await extensionsApi.getInstance(projectId, instanceId); - } catch (err: any) { - if (err.status === 404) { - throw new FirebaseError( - `Extension instance '${clc.bold(instanceId)}' not found in project '${clc.bold( - projectId - )}'.` - ); - } - throw err; - } - const existingSpec: extensionsApi.ExtensionSpec = existingInstance.config.source.spec; + const existingInstance = await extensionsApi.getInstance(projectId, instanceId); + const existingSpec = existingInstance.config.source.spec; if (existingInstance.config.source.state === "DELETED") { throw new FirebaseError( `Instance '${clc.bold( @@ -343,12 +344,17 @@ export default new Command("ext:update [updateSource]") nonInteractive: options.nonInteractive, instanceId, }); + const eventsConfig = newSpec.events + ? await askUserForEventsConfig.askForEventsConfig(newSpec.events, projectId, instanceId) + : undefined; const newParams = paramHelper.getBaseParamBindings(newParamBindings); - spinner.start(); const updateOptions: UpdateOptions = { projectId, instanceId, + canEmitEvents: eventsConfig ? true : false, + eventarcChannel: eventsConfig?.channel, + allowedEventTypes: eventsConfig?.allowedEventTypes, }; if (newSourceName.includes("publisher")) { updateOptions.extRef = refs.toExtensionVersionRef(refs.parse(newSourceName)); diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 19a78712a81..f359d1f8657 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -45,6 +45,8 @@ export interface ManifestInstanceSpec extends InstanceSpec { */ export interface DeploymentInstanceSpec extends InstanceSpec { params: Record; + allowedEventTypes?: string[]; + eventarcChannel?: string; } /** @@ -105,6 +107,8 @@ export async function have(projectId: string): Promise const dep: DeploymentInstanceSpec = { instanceId: i.name.split("/").pop()!, params: i.config.params, + allowedEventTypes: i.config.allowedEventTypes, + eventarcChannel: i.config.eventarcChannel, }; if (i.config.extensionRef) { const ref = refs.parse(i.config.extensionRef); @@ -151,11 +155,26 @@ export async function want(args: { const autoPopulatedParams = await getFirebaseProjectParams(args.projectId, args.emulatorMode); const subbedParams = substituteParams(params, autoPopulatedParams); + // ALLOWED_EVENT_TYPES can be undefined (user input not provided) or empty string (no events selected). + // If empty string, we want to pass an empty array. If it's undefined we want to pass through undefined. + const allowedEventTypes = + subbedParams.ALLOWED_EVENT_TYPES !== undefined + ? subbedParams.ALLOWED_EVENT_TYPES.split(",").filter((e) => e !== "") + : undefined; + const eventarcChannel = subbedParams.EVENTARC_CHANNEL; + + // Remove special params that are stored in the .env file but aren't actually params specified by the publisher. + // Currently, only environment variables needed for Events features are considered special params stored in .env files. + delete subbedParams["EVENTARC_CHANNEL"]; + delete subbedParams["ALLOWED_EVENT_TYPES"]; + if (isLocalPath(e[1])) { instanceSpecs.push({ instanceId, localPath: e[1], params: subbedParams, + allowedEventTypes: allowedEventTypes, + eventarcChannel: eventarcChannel, }); } else { const ref = refs.parse(e[1]); @@ -164,6 +183,8 @@ export async function want(args: { instanceId, ref, params: subbedParams, + allowedEventTypes: allowedEventTypes, + eventarcChannel: eventarcChannel, }); } } catch (err: any) { diff --git a/src/deploy/extensions/tasks.ts b/src/deploy/extensions/tasks.ts index d92913e3ce9..9fbafba2b33 100644 --- a/src/deploy/extensions/tasks.ts +++ b/src/deploy/extensions/tasks.ts @@ -50,6 +50,8 @@ export function createExtensionInstanceTask( instanceId: instanceSpec.instanceId, params: instanceSpec.params, extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref), + allowedEventTypes: instanceSpec.allowedEventTypes, + eventarcChannel: instanceSpec.eventarcChannel, validateOnly, }); } else if (instanceSpec.localPath) { @@ -59,6 +61,8 @@ export function createExtensionInstanceTask( instanceId: instanceSpec.instanceId, params: instanceSpec.params, extensionSource, + allowedEventTypes: instanceSpec.allowedEventTypes, + eventarcChannel: instanceSpec.eventarcChannel, validateOnly, }); } else { @@ -88,6 +92,9 @@ export function updateExtensionInstanceTask( instanceId: instanceSpec.instanceId, extRef: refs.toExtensionVersionRef(instanceSpec.ref!), params: instanceSpec.params, + canEmitEvents: !!instanceSpec.allowedEventTypes, + allowedEventTypes: instanceSpec.allowedEventTypes, + eventarcChannel: instanceSpec.eventarcChannel, validateOnly, }); } else if (instanceSpec.localPath) { @@ -97,6 +104,9 @@ export function updateExtensionInstanceTask( instanceId: instanceSpec.instanceId, extensionSource, validateOnly, + canEmitEvents: !!instanceSpec.allowedEventTypes, + allowedEventTypes: instanceSpec.allowedEventTypes, + eventarcChannel: instanceSpec.eventarcChannel, }); } else { throw new FirebaseError( @@ -124,6 +134,9 @@ export function configureExtensionInstanceTask( projectId, instanceId: instanceSpec.instanceId, params: instanceSpec.params, + canEmitEvents: !!instanceSpec.allowedEventTypes, + allowedEventTypes: instanceSpec.allowedEventTypes, + eventarcChannel: instanceSpec.eventarcChannel, validateOnly, }); } else if (instanceSpec.localPath) { diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index afeb080c888..a2d3a018b46 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -238,7 +238,7 @@ export class ExtensionsEmulator implements EmulatorInstance { return emulatableBackend; } - private autoPopulatedParams(instance: planner.InstanceSpec): Record { + private autoPopulatedParams(instance: planner.DeploymentInstanceSpec): Record { const projectId = this.args.projectId; return { PROJECT_ID: projectId ?? "", // TODO: Should this fallback to a default? @@ -246,6 +246,8 @@ export class ExtensionsEmulator implements EmulatorInstance { DATABASE_INSTANCE: projectId ?? "", DATABASE_URL: `https://${projectId}.firebaseio.com`, STORAGE_BUCKET: `${projectId}.appspot.com`, + ALLOWED_EVENT_TYPES: instance.allowedEventTypes ? instance.allowedEventTypes.join(",") : "", + EVENTARC_CHANNEL: instance.eventarcChannel ?? "", }; } diff --git a/src/extensions/askUserForEventsConfig.ts b/src/extensions/askUserForEventsConfig.ts new file mode 100644 index 00000000000..83e7ba443f6 --- /dev/null +++ b/src/extensions/askUserForEventsConfig.ts @@ -0,0 +1,121 @@ +import * as _ from "lodash"; +import { promptOnce } from "../prompt"; +import * as extensionsApi from "../extensions/extensionsApi"; +import * as utils from "../utils"; +import * as clc from "cli-color"; +import { logger } from "../logger"; +const { marked } = require("marked"); + +export interface InstanceEventsConfig { + channel: string; + allowedEventTypes: string[]; +} + +export function checkAllowedEventTypesResponse( + response: string[], + validEvents: extensionsApi.EventDescriptor[] +): boolean { + const validEventTypes = validEvents.map((e) => e.type); + if (response.length === 0) { + return false; + } + for (const e of response) { + if (!validEventTypes.includes(e)) { + utils.logWarning( + `Unexpected event type '${e}' was configured to be emitted. This event type is not part of the extension spec.` + ); + return false; + } + } + return true; +} + +export async function askForEventsConfig( + events: extensionsApi.EventDescriptor[], + projectId: string, + instanceId: string +): Promise { + logger.info( + `\n${clc.bold("Enable Events")}: ${marked( + "If you enable events, you can write custom event handlers ([https://firebase.google.com/docs/extensions/install-extensions#eventarc](https://firebase.google.com/docs/extensions/install-extensions#eventarc)) that respond to these events.\n\nYou can always enable or disable events later. Events will be emitted via Eventarc. Fees apply ([https://cloud.google.com/eventarc/pricing](https://cloud.google.com/eventarc/pricing))." + )}` + ); + if (!(await askShouldCollectEventsConfig())) { + return undefined; + } + let existingInstance: extensionsApi.ExtensionInstance | undefined; + try { + existingInstance = instanceId + ? await extensionsApi.getInstance(projectId, instanceId) + : undefined; + } catch { + /* If instance was not found, then this is an instance ID for a new instance. Don't preselect any values when displaying prompts to the user. */ + } + const preselectedTypes = existingInstance?.config.allowedEventTypes ?? []; + const oldLocation = existingInstance?.config.eventarcChannel?.split("/")[3]; + const location = await askForEventArcLocation(oldLocation); + const channel = `projects/${projectId}/locations/${location}/channels/firebase`; + const allowedEventTypes = await askForAllowedEventTypes(events, preselectedTypes); + return { channel, allowedEventTypes }; +} + +export async function askForAllowedEventTypes( + eventDescriptors: extensionsApi.EventDescriptor[], + preselectedTypes?: string[] +): Promise { + let valid = false; + let response: string[] = []; + const eventTypes = eventDescriptors.map((e, index) => ({ + checked: false, + name: `${index + 1}. ${e.type}\n ${e.description}`, + value: e.type, + })); + while (!valid) { + response = await promptOnce({ + name: "selectedEventTypesInput", + type: "checkbox", + default: preselectedTypes ?? [], + message: + `Please select the events [${eventTypes.length} types total] that this extension is permitted to emit. ` + + "You can implement your own handlers that trigger when these events are emitted to customize the extension's behavior. ", + choices: eventTypes, + pageSize: 20, + }); + valid = checkAllowedEventTypesResponse(response, eventDescriptors); + } + return response.filter((e) => e !== ""); +} + +export async function askShouldCollectEventsConfig(): Promise { + return promptOnce({ + type: "confirm", + name: "shouldCollectEvents", + message: `Would you like to enable events?`, + default: false, + }); +} + +export async function askForEventArcLocation(preselectedLocation?: string): Promise { + let valid = false; + const allowedRegions = ["us-central1", "us-west1", "europe-west4", "asia-northeast1"]; + let location = ""; + while (!valid) { + location = await promptOnce({ + name: "input", + type: "list", + default: preselectedLocation ?? "us-central1", + message: + "Which location would you like the Eventarc channel to live in? We recommend using the default option. A channel location that differs from the extension's Cloud Functions location can incur egress cost.", + choices: allowedRegions.map((e) => ({ checked: false, value: e })), + }); + valid = allowedRegions.includes(location); + if (!valid) { + utils.logWarning( + `Unexpected EventArc region '${location}' was specified. Allowed regions: ${allowedRegions.join( + ", " + )}` + ); + } + } + return location; +} diff --git a/src/extensions/export.ts b/src/extensions/export.ts index 871f6089347..9d91c48d3be 100644 --- a/src/extensions/export.ts +++ b/src/extensions/export.ts @@ -79,6 +79,12 @@ function displaySpecs(specs: DeploymentInstanceSpec[]): void { for (const p of Object.entries(spec.params)) { logger.info(`\t${p[0]}=${p[1]}`); } + if (spec.allowedEventTypes?.length) { + logger.info(`\tALLOWED_EVENTS=${spec.allowedEventTypes}`); + } + if (spec.eventarcChannel) { + logger.info(`\tEVENTARC_CHANNEL=${spec.eventarcChannel}`); + } logger.info(""); } } diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index 1cad070f076..bccc067078f 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -86,6 +86,8 @@ export interface ExtensionConfig { populatedPostinstallContent?: string; extensionRef?: string; extensionVersion?: string; + eventarcChannel?: string; + allowedEventTypes?: string[]; } export interface ExtensionSource { @@ -119,6 +121,12 @@ export interface ExtensionSpec { postinstallContent?: string; readmeContent?: string; externalServices?: ExternalService[]; + events?: EventDescriptor[]; +} + +export interface EventDescriptor { + type: string; + description: string; } export interface ExternalService { @@ -250,10 +258,14 @@ export async function createInstance(args: { extensionSource?: ExtensionSource; extensionVersionRef?: string; params: { [key: string]: string }; + allowedEventTypes?: string[]; + eventarcChannel?: string; validateOnly?: boolean; }): Promise { const config: any = { params: args.params, + allowedEventTypes: args.allowedEventTypes, + eventarcChannel: args.eventarcChannel, }; if (args.extensionSource && args.extensionVersionRef) { @@ -269,6 +281,12 @@ export async function createInstance(args: { } else { throw new FirebaseError("No ExtensionVersion or ExtensionSource provided but one is required."); } + if (args.allowedEventTypes) { + config.allowedEventTypes = args.allowedEventTypes; + } + if (args.eventarcChannel) { + config.eventarcChannel = args.eventarcChannel; + } return createInstanceHelper(args.projectId, args.instanceId, config, args.validateOnly); } @@ -297,8 +315,20 @@ export async function deleteInstance(projectId: string, instanceId: string): Pro * @param instanceId the id of the instance to delete */ export async function getInstance(projectId: string, instanceId: string): Promise { - const res = await apiClient.get(`/projects/${projectId}/instances/${instanceId}`); - return res.body; + try { + const res = await apiClient.get(`/projects/${projectId}/instances/${instanceId}`); + return res.body; + } catch (err: any) { + if (err.status === 404) { + throw new FirebaseError( + `Extension instance '${clc.bold(instanceId)}' not found in project '${clc.bold( + projectId + )}'.`, + { status: 404 } + ); + } + throw err; + } } /** @@ -335,15 +365,20 @@ export async function listInstances(projectId: string): Promise { - const res = await patchInstance({ + const reqBody: any = { projectId: args.projectId, instanceId: args.instanceId, updateMask: "config.params", @@ -353,8 +388,18 @@ export async function configureInstance(args: { params: args.params, }, }, - }); - return res; + }; + if (args.canEmitEvents) { + if (args.allowedEventTypes === undefined || args.eventarcChannel === undefined) { + throw new FirebaseError( + `This instance is configured to emit events, but either allowed event types or eventarc channel is undefined.` + ); + } + reqBody.data.config.allowedEventTypes = args.allowedEventTypes; + reqBody.data.config.eventarcChannel = args.eventarcChannel; + } + reqBody.updateMask += ",config.allowed_event_types,config.eventarc_channel"; + return patchInstance(reqBody); } /** @@ -364,6 +409,8 @@ export async function configureInstance(args: { * @param instanceId the id of the instance to configure * @param extensionSource the source for the version of the extension to update to * @param params params to configure the extension instance + * @param allowedEventTypes types of events (selected by consumer) that the extension is allowed to emit + * @param eventarcChannel fully qualified eventarc channel resource name to emit events to * @param validateOnly if true, only validates the update and makes no changes */ export async function updateInstance(args: { @@ -371,6 +418,9 @@ export async function updateInstance(args: { instanceId: string; extensionSource: ExtensionSource; params?: { [option: string]: string }; + canEmitEvents: boolean; + allowedEventTypes?: string[]; + eventarcChannel?: string; validateOnly?: boolean; }): Promise { const body: any = { @@ -383,7 +433,17 @@ export async function updateInstance(args: { body.config.params = args.params; updateMask += ",config.params"; } - return await patchInstance({ + if (args.canEmitEvents) { + if (args.allowedEventTypes === undefined || args.eventarcChannel === undefined) { + throw new FirebaseError( + `This instance is configured to emit events, but either allowed event types or eventarc channel is undefined.` + ); + } + body.config.allowedEventTypes = args.allowedEventTypes; + body.config.eventarcChannel = args.eventarcChannel; + } + updateMask += ",config.allowed_event_types,config.eventarc_channel"; + return patchInstance({ projectId: args.projectId, instanceId: args.instanceId, updateMask, @@ -399,6 +459,8 @@ export async function updateInstance(args: { * @param instanceId the id of the instance to configure * @param extRef reference for the extension to update to * @param params params to configure the extension instance + * @param allowedEventTypes types of events (selected by consumer) that the extension is allowed to emit + * @param eventarcChannel fully qualified eventarc channel resource name to emit events to * @param validateOnly if true, only validates the update and makes no changes */ export async function updateInstanceFromRegistry(args: { @@ -406,6 +468,9 @@ export async function updateInstanceFromRegistry(args: { instanceId: string; extRef: string; params?: { [option: string]: string }; + canEmitEvents: boolean; + allowedEventTypes?: string[]; + eventarcChannel?: string; validateOnly?: boolean; }): Promise { const ref = refs.parse(args.extRef); @@ -420,7 +485,17 @@ export async function updateInstanceFromRegistry(args: { body.config.params = args.params; updateMask += ",config.params"; } - return await patchInstance({ + if (args.canEmitEvents) { + if (args.allowedEventTypes === undefined || args.eventarcChannel === undefined) { + throw new FirebaseError( + `This instance is configured to emit events, but either allowed event types or eventarc channel is undefined.` + ); + } + body.config.allowedEventTypes = args.allowedEventTypes; + body.config.eventarcChannel = args.eventarcChannel; + } + updateMask += ",config.allowed_event_types,config.eventarc_channel"; + return patchInstance({ projectId: args.projectId, instanceId: args.instanceId, updateMask, diff --git a/src/extensions/updateHelper.ts b/src/extensions/updateHelper.ts index 74052463fff..6cc4f8f2174 100644 --- a/src/extensions/updateHelper.ts +++ b/src/extensions/updateHelper.ts @@ -122,6 +122,9 @@ export interface UpdateOptions { source?: extensionsApi.ExtensionSource; extRef?: string; params?: { [key: string]: string }; + canEmitEvents: boolean; + allowedEventTypes?: string[]; + eventarcChannel?: string; } /** @@ -133,13 +136,25 @@ export interface UpdateOptions { * @param updateOptions Info on the instance and associated resources to update */ export async function update(updateOptions: UpdateOptions): Promise { - const { projectId, instanceId, source, extRef, params } = updateOptions; + const { + projectId, + instanceId, + source, + extRef, + params, + canEmitEvents, + allowedEventTypes, + eventarcChannel, + } = updateOptions; if (extRef) { return await extensionsApi.updateInstanceFromRegistry({ projectId, instanceId, extRef, params, + canEmitEvents, + allowedEventTypes, + eventarcChannel, }); } else if (source) { return await extensionsApi.updateInstance({ @@ -147,6 +162,9 @@ export async function update(updateOptions: UpdateOptions): Promise { instanceId, extensionSource: source, params, + canEmitEvents, + allowedEventTypes, + eventarcChannel, }); } throw new FirebaseError( diff --git a/src/test/deploy/extensions/planner.spec.ts b/src/test/deploy/extensions/planner.spec.ts index cc7552a8c06..05d0aa47387 100644 --- a/src/test/deploy/extensions/planner.spec.ts +++ b/src/test/deploy/extensions/planner.spec.ts @@ -91,4 +91,103 @@ describe("Extensions Deployment Planner", () => { }); } }); + + describe("have", () => { + let listInstancesStub: sinon.SinonStub; + + const SPEC = { + version: "0.1.0", + author: { + authorName: "Firebase", + url: "https://firebase.google.com", + }, + resources: [], + name: "", + sourceUrl: "", + params: [], + }; + + const INSTANCE_WITH_EVENTS: extensionsApi.ExtensionInstance = { + name: "projects/my-test-proj/instances/image-resizer", + createTime: "2019-05-19T00:20:10.416947Z", + updateTime: "2019-05-19T00:20:10.416947Z", + state: "ACTIVE", + serviceAccountEmail: "", + config: { + params: {}, + extensionRef: "firebase/image-resizer", + extensionVersion: "0.1.0", + name: "projects/my-test-proj/instances/image-resizer/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", + createTime: "2019-05-19T00:20:10.416947Z", + eventarcChannel: "projects/my-test-proj/locations/us-central1/channels/firebase", + allowedEventTypes: ["google.firebase.custom-event-occurred"], + source: { + state: "ACTIVE", + spec: SPEC, + name: "", + packageUri: "", + hash: "", + }, + }, + }; + + const INSTANCE_SPEC_WITH_EVENTS: planner.DeploymentInstanceSpec = { + instanceId: "image-resizer", + params: {}, + allowedEventTypes: ["google.firebase.custom-event-occurred"], + eventarcChannel: "projects/my-test-proj/locations/us-central1/channels/firebase", + ref: { + publisherId: "firebase", + extensionId: "image-resizer", + version: "0.1.0", + }, + }; + + const INSTANCE_WITH_UNDEFINED_EVENTS_CONFIG = JSON.parse(JSON.stringify(INSTANCE_WITH_EVENTS)); + INSTANCE_WITH_UNDEFINED_EVENTS_CONFIG.config.eventarcChannel = undefined; + INSTANCE_WITH_UNDEFINED_EVENTS_CONFIG.config.allowedEventTypes = undefined; + + const INSTANCE_SPEC_WITH_UNDEFINED_EVENTS_CONFIG = JSON.parse( + JSON.stringify(INSTANCE_SPEC_WITH_EVENTS) + ); + INSTANCE_SPEC_WITH_UNDEFINED_EVENTS_CONFIG.eventarcChannel = undefined; + INSTANCE_SPEC_WITH_UNDEFINED_EVENTS_CONFIG.allowedEventTypes = undefined; + + const INSTANCE_WITH_EMPTY_EVENTS_CONFIG = JSON.parse(JSON.stringify(INSTANCE_WITH_EVENTS)); + INSTANCE_WITH_EMPTY_EVENTS_CONFIG.config.eventarcChannel = ""; + INSTANCE_WITH_EMPTY_EVENTS_CONFIG.config.allowedEventTypes = []; + + const INSTANCE_SPEC_WITH_EMPTY_EVENTS_CONFIG = JSON.parse( + JSON.stringify(INSTANCE_SPEC_WITH_EVENTS) + ); + INSTANCE_SPEC_WITH_EMPTY_EVENTS_CONFIG.eventarcChannel = ""; + INSTANCE_SPEC_WITH_EMPTY_EVENTS_CONFIG.allowedEventTypes = []; + + const cases = [ + { + description: "have() should return correct instance spec with events", + instances: [INSTANCE_WITH_EVENTS], + instanceSpecs: [INSTANCE_SPEC_WITH_EVENTS], + }, + { + description: "have() should return correct instance spec with undefined events config", + instances: [INSTANCE_WITH_UNDEFINED_EVENTS_CONFIG], + instanceSpecs: [INSTANCE_SPEC_WITH_UNDEFINED_EVENTS_CONFIG], + }, + { + description: "have() should return correct instance spec with empty events config", + instances: [INSTANCE_WITH_EMPTY_EVENTS_CONFIG], + instanceSpecs: [INSTANCE_SPEC_WITH_EMPTY_EVENTS_CONFIG], + }, + ]; + + for (const c of cases) { + it(c.description, async () => { + listInstancesStub = sinon.stub(extensionsApi, "listInstances").resolves(c.instances); + const have = await planner.have("test"); + expect(have).to.have.same.deep.members(c.instanceSpecs); + listInstancesStub.restore(); + }); + } + }); }); diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts index 4aa26ec85a9..2eaa1640d6b 100644 --- a/src/test/emulators/extensionsEmulator.spec.ts +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -74,7 +74,15 @@ describe("Extensions Emulator", () => { }, params: { LOCATION: "us-west1", + ALLOWED_EVENT_TYPES: + "google.firebase.image-resize-started,google.firebase.image-resize-completed", + EVENTARC_CHANNEL: "projects/sample-project/locations/us-central1/channels/firebase", }, + allowedEventTypes: [ + "google.firebase.image-resize-started", + "google.firebase.image-resize-completed", + ], + eventarcChannel: "projects/sample-project/locations/us-central1/channels/firebase", extension: TEST_EXTENSION, extensionVersion: TEST_EXTENSION_VERSION, }, @@ -86,6 +94,9 @@ describe("Extensions Emulator", () => { EXT_INSTANCE_ID: "ext-test", PROJECT_ID: "test-project", STORAGE_BUCKET: "test-project.appspot.com", + ALLOWED_EVENT_TYPES: + "google.firebase.image-resize-started,google.firebase.image-resize-completed", + EVENTARC_CHANNEL: "projects/sample-project/locations/us-central1/channels/firebase", }, secretEnv: [], extensionInstanceId: "ext-test", diff --git a/src/test/extensions/askUserForEventsConfig.spec.ts b/src/test/extensions/askUserForEventsConfig.spec.ts new file mode 100644 index 00000000000..aa0721a9434 --- /dev/null +++ b/src/test/extensions/askUserForEventsConfig.spec.ts @@ -0,0 +1,83 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; + +import { + askForEventArcLocation, + askForAllowedEventTypes, + checkAllowedEventTypesResponse, +} from "../../extensions/askUserForEventsConfig"; +import * as utils from "../../utils"; +import * as prompt from "../../prompt"; + +describe("checkAllowedEventTypesResponse", () => { + let logWarningSpy: sinon.SinonSpy; + beforeEach(() => { + logWarningSpy = sinon.spy(utils, "logWarning"); + }); + + afterEach(() => { + logWarningSpy.restore(); + }); + + it("should return false if allowed events is not part of extension spec's events list", () => { + expect( + checkAllowedEventTypesResponse( + ["google.firebase.nonexistent-event-occurred"], + [{ type: "google.firebase.custom-event-occurred", description: "A custom event occurred" }] + ) + ).to.equal(false); + expect( + logWarningSpy.calledWith( + "Unexpected event type 'google.firebase.nonexistent-event-occurred' was configured to be emitted. This event type is not part of the extension spec." + ) + ).to.equal(true); + }); + + it("should return true if every allowed event exists in extension spec's events list", () => { + expect( + checkAllowedEventTypesResponse( + ["google.firebase.custom-event-occurred"], + [{ type: "google.firebase.custom-event-occurred", description: "A custom event occurred" }] + ) + ).to.equal(true); + }); +}); + +describe("askForAllowedEventTypes", () => { + let promptStub: sinon.SinonStub; + + afterEach(() => { + promptStub.restore(); + }); + + it("should keep prompting user until valid input is given", async () => { + promptStub = sinon.stub(prompt, "promptOnce"); + promptStub.onCall(0).returns(["invalid"]); + promptStub.onCall(1).returns(["stillinvalid"]); + promptStub.onCall(2).returns(["google.firebase.custom-event-occurred"]); + await askForAllowedEventTypes([ + { type: "google.firebase.custom-event-occurred", description: "A custom event occurred" }, + ]); + expect(promptStub.calledThrice).to.be.true; + }); +}); + +describe("askForEventarcLocation", () => { + let promptStub: sinon.SinonStub; + + beforeEach(() => { + promptStub = sinon.stub(prompt, "promptOnce"); + promptStub.onCall(0).returns("invalid-region"); + promptStub.onCall(1).returns("still-invalid-region"); + promptStub.onCall(2).returns("us-central1"); + }); + + afterEach(() => { + promptStub.restore(); + }); + + it("should keep prompting user until valid input is given", async () => { + await askForEventArcLocation(); + expect(promptStub.calledThrice).to.be.true; + }); +}); diff --git a/src/test/extensions/extensionsApi.spec.ts b/src/test/extensions/extensionsApi.spec.ts index c97c0bf7103..c35d6b23e05 100644 --- a/src/test/extensions/extensionsApi.spec.ts +++ b/src/test/extensions/extensionsApi.spec.ts @@ -310,7 +310,10 @@ describe("extensions", () => { it("should make a PATCH call to the correct endpoint, and then poll on the returned operation", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) - .query({ updateMask: "config.params", validateOnly: "false" }) + .query({ + updateMask: "config.params,config.allowed_event_types,config.eventarc_channel", + validateOnly: "false", + }) .reply(200, { name: "operations/abc123" }); nock(api.extensionsOrigin) .get(`/${VERSION}/operations/abc123`) @@ -322,6 +325,7 @@ describe("extensions", () => { projectId: PROJECT_ID, instanceId: INSTANCE_ID, params: { MY_PARAM: "value" }, + canEmitEvents: false, }); expect(nock.isDone()).to.be.true; }); @@ -329,7 +333,10 @@ describe("extensions", () => { it("should make a PATCH and not poll if validateOnly=true", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) - .query({ updateMask: "config.params", validateOnly: "true" }) + .query({ + updateMask: "config.params,config.allowed_event_types,config.eventarc_channel", + validateOnly: "true", + }) .reply(200, { name: "operations/abc123", done: true }); await extensionsApi.configureInstance({ @@ -337,6 +344,7 @@ describe("extensions", () => { instanceId: INSTANCE_ID, params: { MY_PARAM: "value" }, validateOnly: true, + canEmitEvents: false, }); expect(nock.isDone()).to.be.true; }); @@ -344,7 +352,10 @@ describe("extensions", () => { it("should throw a FirebaseError if update returns an error response", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) - .query({ updateMask: "config.params", validateOnly: false }) + .query({ + updateMask: "config.params,config.allowed_event_types,config.eventarc_channel", + validateOnly: false, + }) .reply(500); await expect( @@ -352,6 +363,7 @@ describe("extensions", () => { projectId: PROJECT_ID, instanceId: INSTANCE_ID, params: { MY_PARAM: "value" }, + canEmitEvents: false, }) ).to.be.rejectedWith(FirebaseError, "HTTP Error: 500"); expect(nock.isDone()).to.be.true; @@ -406,7 +418,11 @@ describe("extensions", () => { it("should include config.param in updateMask is params are changed", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) - .query({ updateMask: "config.source.name,config.params", validateOnly: "false" }) + .query({ + updateMask: + "config.source.name,config.params,config.allowed_event_types,config.eventarc_channel", + validateOnly: "false", + }) .reply(200, { name: "operations/abc123" }); nock(api.extensionsOrigin).get(`/${VERSION}/operations/abc123`).reply(200, { done: true }); @@ -417,6 +433,7 @@ describe("extensions", () => { params: { MY_PARAM: "value", }, + canEmitEvents: false, }); expect(nock.isDone()).to.be.true; @@ -425,7 +442,10 @@ describe("extensions", () => { it("should not include config.param in updateMask is params aren't changed", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) - .query({ updateMask: "config.source.name", validateOnly: "false" }) + .query({ + updateMask: "config.source.name,config.allowed_event_types,config.eventarc_channel", + validateOnly: "false", + }) .reply(200, { name: "operations/abc123" }); nock(api.extensionsOrigin).get(`/${VERSION}/operations/abc123`).reply(200, { done: true }); @@ -433,15 +453,43 @@ describe("extensions", () => { projectId: PROJECT_ID, instanceId: INSTANCE_ID, extensionSource: testSource, + canEmitEvents: false, }); + expect(nock.isDone()).to.be.true; + }); + + it("should include config.allowed_event_types and config.eventarc_Channel in updateMask if events config is provided", async () => { + nock(api.extensionsOrigin) + .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) + .query({ + updateMask: + "config.source.name,config.params,config.allowed_event_types,config.eventarc_channel", + validateOnly: "false", + }) + .reply(200, { name: "operations/abc123" }); + nock(api.extensionsOrigin).get(`/${VERSION}/operations/abc123`).reply(200, { done: true }); + await extensionsApi.updateInstance({ + projectId: PROJECT_ID, + instanceId: INSTANCE_ID, + extensionSource: testSource, + params: { + MY_PARAM: "value", + }, + canEmitEvents: true, + eventarcChannel: "projects/${PROJECT_ID}/locations/us-central1/channels/firebase", + allowedEventTypes: ["google.firebase.custom-events-occurred"], + }); expect(nock.isDone()).to.be.true; }); it("should make a PATCH and not poll if validateOnly=true", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) - .query({ updateMask: "config.source.name", validateOnly: "true" }) + .query({ + updateMask: "config.source.name,config.allowed_event_types,config.eventarc_channel", + validateOnly: "true", + }) .reply(200, { name: "operations/abc123", done: true }); await extensionsApi.updateInstance({ @@ -449,6 +497,7 @@ describe("extensions", () => { instanceId: INSTANCE_ID, extensionSource: testSource, validateOnly: true, + canEmitEvents: false, }); expect(nock.isDone()).to.be.true; }); @@ -456,7 +505,10 @@ describe("extensions", () => { it("should make a PATCH and not poll if validateOnly=true", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) - .query({ updateMask: "config.source.name", validateOnly: "true" }) + .query({ + updateMask: "config.source.name,config.allowed_event_types,config.eventarc_channel", + validateOnly: "true", + }) .reply(200, { name: "operations/abc123", done: true }); await extensionsApi.updateInstance({ @@ -464,6 +516,7 @@ describe("extensions", () => { instanceId: INSTANCE_ID, extensionSource: testSource, validateOnly: true, + canEmitEvents: false, }); expect(nock.isDone()).to.be.true; }); @@ -471,7 +524,11 @@ describe("extensions", () => { it("should throw a FirebaseError if update returns an error response", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) - .query({ updateMask: "config.source.name,config.params", validateOnly: false }) + .query({ + updateMask: + "config.source.name,config.params,config.allowed_event_types,config.eventarc_channel", + validateOnly: false, + }) .reply(500); await expect( @@ -482,6 +539,7 @@ describe("extensions", () => { params: { MY_PARAM: "value", }, + canEmitEvents: false, }) ).to.be.rejectedWith(FirebaseError, "HTTP Error: 500"); diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts index 8f6e228f40a..3d04285deb0 100644 --- a/src/test/extensions/manifest.spec.ts +++ b/src/test/extensions/manifest.spec.ts @@ -313,6 +313,121 @@ describe("manifest", () => { ); }); + it("should write events-related env vars", async () => { + await manifest.writeToManifest( + [ + { + instanceId: "instance-1", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "1.0.0", + }, + params: { + b: { baseValue: "bulbasaur" }, + a: { baseValue: "absol" }, + EVENTARC_CHANNEL: { + baseValue: "projects/test-project/locations/us-central1/channels/firebase", + }, + ALLOWED_EVENT_TYPES: { baseValue: "google.firebase.custom-event-occurred" }, + }, + extensionSpec: { + name: "bigquery-export", + version: "1.0.0", + resources: [], + sourceUrl: "", + events: [ + { + type: "google.firebase.custom-event-occurred", + description: "Custom event occurred", + }, + ], + params: [ + { + param: "a", + label: "", + type: ParamType.STRING, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, + }, + { + instanceId: "instance-2", + ref: { + publisherId: "firebase", + extensionId: "bigquery-export", + version: "2.0.0", + }, + params: { + e: { baseValue: "eevee" }, + s: { baseValue: "squirtle" }, + EVENTARC_CHANNEL: { + baseValue: "projects/test-project/locations/us-central1/channels/firebase", + }, + ALLOWED_EVENT_TYPES: { baseValue: "google.firebase.custom-event-occurred" }, + }, + extensionSpec: { + name: "bigquery-export", + version: "2.0.0", + resources: [], + sourceUrl: "", + events: [ + { + type: "google.firebase.custom-event-occurred", + description: "Custom event occurred", + }, + ], + params: [ + { + param: "a", + label: "", + type: ParamType.STRING, + }, + { + param: "b", + label: "", + type: ParamType.STRING, + }, + ], + }, + }, + ], + generateBaseConfig(), + { nonInteractive: false, force: false } + ); + expect(writeProjectFileStub).calledWithExactly("firebase.json", { + extensions: { + "delete-user-data": "firebase/delete-user-data@0.1.12", + "delete-user-data-gm2h": "firebase/delete-user-data@0.1.12", + "instance-1": "firebase/bigquery-export@1.0.0", + "instance-2": "firebase/bigquery-export@2.0.0", + }, + }); + + expect(askWriteProjectFileStub).to.have.been.calledTwice; + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-1.env", + "a=absol\n" + + "ALLOWED_EVENT_TYPES=google.firebase.custom-event-occurred\n" + + "b=bulbasaur\n" + + "EVENTARC_CHANNEL=projects/test-project/locations/us-central1/channels/firebase", + false + ); + expect(askWriteProjectFileStub).calledWithExactly( + "extensions/instance-2.env", + "ALLOWED_EVENT_TYPES=google.firebase.custom-event-occurred\n" + + "e=eevee\n" + + "EVENTARC_CHANNEL=projects/test-project/locations/us-central1/channels/firebase\n" + + "s=squirtle", + false + ); + }); + it("should overwrite when user chooses to", async () => { // Chooses to overwrite instead of merge. sandbox.stub(prompt, "promptOnce").resolves(true); From 1a75c1c49da64f393504de84250a696d7c93df01 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 4 May 2022 08:27:54 -0700 Subject: [PATCH 0293/1699] Fixes a bug where paramaterized values were displayed but not written to .env files (#4515) * fixes a bug where paramaterized values were displayed but not written to .env files * Add changelog * merge conflict --- CHANGELOG.md | 1 + src/commands/ext-export.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b96987c7de..d0822a0e049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Add support for secrets to v2 functions (#4451). +- Fixes an issue where `ext:export` would write different param values than what it displayed in the prompt (#4515). diff --git a/src/commands/ext-export.ts b/src/commands/ext-export.ts index 67d6913543e..da4b1b3f06c 100644 --- a/src/commands/ext-export.ts +++ b/src/commands/ext-export.ts @@ -64,7 +64,7 @@ module.exports = new Command("ext:export") return; } - const manifestSpecs = withRef.map((spec) => { + const manifestSpecs = withRefSubbed.map((spec) => { const paramCopy = { ...spec.params }; if (spec.eventarcChannel) { paramCopy.EVENTARC_CHANNEL = spec.eventarcChannel; From 6dd0b84c883ef1d379927cbc72adfc1a7cc9b8e9 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Wed, 4 May 2022 13:25:50 -0400 Subject: [PATCH 0294/1699] Enhance the IAM role binding process (#4511) * change up ordering, add jump out points, and add sleep * linting * yanking out sleep, adding in a better error message for the first deploy, and replacing reduce with our functional module * adding changelog --- CHANGELOG.md | 1 + src/deploy/functions/checkIam.ts | 163 ++++++------ src/deploy/functions/prepare.ts | 16 +- src/deploy/functions/services/index.ts | 5 +- src/deploy/functions/services/storage.ts | 22 +- src/gcp/cloudfunctionsv2.ts | 11 + src/gcp/serviceusage.ts | 33 +++ src/test/deploy/functions/checkIam.spec.ts | 244 ++++++------------ .../deploy/functions/services/storage.spec.ts | 53 +--- 9 files changed, 241 insertions(+), 307 deletions(-) create mode 100644 src/gcp/serviceusage.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d0822a0e049..01a8fdf8a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Add support for secrets to v2 functions (#4451). - Fixes an issue where `ext:export` would write different param values than what it displayed in the prompt (#4515). +- Enhances the functions IAM permission process and updates the error messages (#4511). diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index 8381d4cb6f1..bdb4b05f302 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -115,6 +115,16 @@ export async function checkHttpIam( logger.debug("[functions] found setIamPolicy permission, proceeding with deploy"); } +/** obtain the pubsub service agent */ +function getPubsubServiceAgent(projectNumber: string): string { + return `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`; +} + +/** obtain the default compute service agent */ +function getDefaultComputeServiceAgent(projectNumber: string): string { + return `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`; +} + /** Callback reducer function */ function reduceEventsToServices(services: Array, endpoint: backend.Endpoint) { const service = serviceForEndpoint(endpoint); @@ -124,43 +134,18 @@ function reduceEventsToServices(services: Array, endpoint: backend.Endp return services; } -/** - * Returns the IAM bindings that grants the role to the service account - * @param existingPolicy the project level IAM policy - * @param serviceAccount the IAM service account - * @param role the role you want to grant - * @return the correct IAM binding - */ -export function obtainBinding( - existingPolicy: iam.Policy, - serviceAccount: string, - role: string -): iam.Binding { - let binding = existingPolicy.bindings.find((b) => b.role === role); - if (!binding) { - binding = { - role, - members: [], - }; - } - if (!binding.members.find((m) => m === serviceAccount)) { - binding.members.push(serviceAccount); - } - return binding; -} - /** * Finds the required project level IAM bindings for the Pub/Sub service agent. * If the user enabled Pub/Sub on or before April 8, 2021, then we must enable the token creator role. * @param projectNumber project number * @param existingPolicy the project level IAM policy */ -export function obtainPubSubServiceAgentBindings( - projectNumber: string, - existingPolicy: iam.Policy -): iam.Binding[] { - const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`; - return [obtainBinding(existingPolicy, pubsubServiceAgent, SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE)]; +export function obtainPubSubServiceAgentBindings(projectNumber: string): iam.Binding[] { + const serviceAccountTokenCreatorBinding: iam.Binding = { + role: SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, + members: [getPubsubServiceAgent(projectNumber)], + }; + return [serviceAccountTokenCreatorBinding]; } /** @@ -169,54 +154,73 @@ export function obtainPubSubServiceAgentBindings( * @param projectNumber project number * @param existingPolicy the project level IAM policy */ -export function obtainDefaultComputeServiceAgentBindings( - projectNumber: string, - existingPolicy: iam.Policy -): iam.Binding[] { - const defaultComputeServiceAgent = `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`; - const invokerBinding = obtainBinding( - existingPolicy, - defaultComputeServiceAgent, - RUN_INVOKER_ROLE - ); - const eventReceiverBinding = obtainBinding( - existingPolicy, - defaultComputeServiceAgent, - EVENTARC_EVENT_RECEIVER_ROLE - ); - return [invokerBinding, eventReceiverBinding]; +export function obtainDefaultComputeServiceAgentBindings(projectNumber: string): iam.Binding[] { + const defaultComputeServiceAgent = getDefaultComputeServiceAgent(projectNumber); + const runInvokerBinding: iam.Binding = { + role: RUN_INVOKER_ROLE, + members: [defaultComputeServiceAgent], + }; + const eventarcEventReceiverBinding: iam.Binding = { + role: EVENTARC_EVENT_RECEIVER_ROLE, + members: [defaultComputeServiceAgent], + }; + return [runInvokerBinding, eventarcEventReceiverBinding]; } -/** Helper to merge all required bindings into the IAM policy */ -export function mergeBindings(policy: iam.Policy, allRequiredBindings: iam.Binding[][]) { - for (const requiredBindings of allRequiredBindings) { - if (requiredBindings.length === 0) { +/** Helper to merge all required bindings into the IAM policy, returns boolean if the policy has been updated */ +export function mergeBindings(policy: iam.Policy, requiredBindings: iam.Binding[]): boolean { + let updated = false; + for (const requiredBinding of requiredBindings) { + const match = policy.bindings.find((b) => b.role === requiredBinding.role); + if (!match) { + updated = true; + policy.bindings.push(requiredBinding); continue; } - for (const requiredBinding of requiredBindings) { - const ndx = policy.bindings.findIndex( - (policyBinding) => policyBinding.role === requiredBinding.role - ); - if (ndx === -1) { - policy.bindings.push(requiredBinding); - continue; + for (const requiredMember of requiredBinding.members) { + if (!match.members.find((m) => m === requiredMember)) { + updated = true; + match.members.push(requiredMember); } - requiredBinding.members.forEach((updatedMember) => { - if (!policy.bindings[ndx].members.find((member) => member === updatedMember)) { - policy.bindings[ndx].members.push(updatedMember); - } - }); + } + } + return updated; +} + +/** Utility to print the required binding commands */ +function printManualIamConfig(requiredBindings: iam.Binding[], projectId: string) { + utils.logLabeledBullet( + "functions", + "Failed to verify the project has the correct IAM bindings for a successful deployment.", + "warn" + ); + utils.logLabeledBullet( + "functions", + "You can either re-run `firebase deploy` as a project owner or manually run the following set of `gcloud` commands:", + "warn" + ); + for (const binding of requiredBindings) { + for (const member of binding.members) { + utils.logLabeledBullet( + "functions", + `\`gcloud projects add-iam-policy-binding ${projectId} ` + + `--member=${member} ` + + `--role=${binding.role}\``, + "warn" + ); } } } /** * Checks and sets the roles for specific resource service agents + * @param projectId human readable project id * @param projectNumber project number * @param want backend that we want to deploy * @param have backend that we have currently deployed */ export async function ensureServiceAgentRoles( + projectId: string, projectNumber: string, want: backend.Backend, have: backend.Backend @@ -230,11 +234,28 @@ export async function ensureServiceAgentRoles( if (newServices.length === 0) { return; } + + // obtain all the bindings we need to have active in the project + const requiredBindingsPromises: Array>> = []; + for (const service of newServices) { + requiredBindingsPromises.push(service.requiredProjectBindings!(projectNumber)); + } + const nestedRequiredBindings = await Promise.all(requiredBindingsPromises); + const requiredBindings = [...flattenArray(nestedRequiredBindings)]; + if (haveServices.length === 0) { + requiredBindings.push(...obtainPubSubServiceAgentBindings(projectNumber)); + requiredBindings.push(...obtainDefaultComputeServiceAgentBindings(projectNumber)); + } + if (requiredBindings.length === 0) { + return; + } + // get the full project iam policy let policy: iam.Policy; try { policy = await getIamPolicy(projectNumber); } catch (err: any) { + printManualIamConfig(requiredBindings, projectId); utils.logLabeledBullet( "functions", "Could not verify the necessary IAM configuration for the following newly-integrated services: " + @@ -244,24 +265,16 @@ export async function ensureServiceAgentRoles( ); return; } - // run in parallel all the missingProjectBindings jobs - const findRequiredBindings: Array>> = []; - newServices.forEach((service) => - findRequiredBindings.push(service.requiredProjectBindings!(projectNumber, policy)) - ); - const allRequiredBindings = await Promise.all(findRequiredBindings); - if (haveServices.length === 0) { - allRequiredBindings.push(obtainPubSubServiceAgentBindings(projectNumber, policy)); - allRequiredBindings.push(obtainDefaultComputeServiceAgentBindings(projectNumber, policy)); - } - if (!allRequiredBindings.find((bindings) => bindings.length > 0)) { + const hasUpdatedBindings = mergeBindings(policy, requiredBindings); + if (!hasUpdatedBindings) { return; } - mergeBindings(policy, allRequiredBindings); + // set the updated policy try { await setIamPolicy(projectNumber, policy, "bindings"); } catch (err: any) { + printManualIamConfig(requiredBindings, projectId); throw new FirebaseError( "We failed to modify the IAM policy for the project. The functions " + "deployment requires specific roles to be granted to service agents," + diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 21a1dca0fc3..f0ae4f0f1f8 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -28,6 +28,7 @@ import { FirebaseError } from "../../error"; import { configForCodebase, normalizeAndValidate } from "../../functions/projectConfig"; import { previews } from "../../previews"; import { AUTH_BLOCKING_EVENTS } from "../../functions/events/v1"; +import { generateServiceIdentity } from "../../gcp/serviceusage"; function hasUserConfig(config: Record): boolean { // "firebase" key is always going to exist in runtime config. @@ -209,10 +210,10 @@ export async function prepare( return ensureApiEnabled.ensure(projectId, api, "functions", /* silent=*/ false); }) ); - // Note: Some of these are premium APIs that require billing to be enabled. - // We'd eventually have to add special error handling for billing APIs, but - // enableCloudBuild is called above and has this special casing already. if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { + // Note: Some of these are premium APIs that require billing to be enabled. + // We'd eventually have to add special error handling for billing APIs, but + // enableCloudBuild is called above and has this special casing already. const V2_APIS = [ "artifactregistry.googleapis.com", "run.googleapis.com", @@ -224,6 +225,13 @@ export async function prepare( return ensureApiEnabled.ensure(context.projectId, api, "functions"); }); await Promise.all(enablements); + // Need to manually kick off the p4sa activation of services + // that we use with IAM roles assignment. + const services = ["pubsub.googleapis.com", "eventarc.googleapis.com"]; + const generateServiceAccounts = services.map((service) => { + return generateServiceIdentity(projectNumber, service, "functions"); + }); + await Promise.all(generateServiceAccounts); } // ===Phase 5. Ask for user prompts for things might warrant user attentions. @@ -237,7 +245,7 @@ export async function prepare( // ===Phase 6. Finalize preparation by "fixing" all extraneous environment issues like IAM policies. // We limit the scope endpoints being deployed. await backend.checkAvailability(context, matchingBackend); - await ensureServiceAgentRoles(projectNumber, matchingBackend, haveBackend); + await ensureServiceAgentRoles(projectId, projectNumber, matchingBackend, haveBackend); await validate.secretsAreValid(projectId, matchingBackend); await ensure.secretAccess(projectId, matchingBackend, haveBackend); } diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 19982b337ad..29ba8569143 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -20,10 +20,7 @@ export interface Service { readonly api: string; // dispatch functions - requiredProjectBindings?: ( - projectNumber: string, - policy: iam.Policy - ) => Promise>; + requiredProjectBindings?: (projectNumber: string) => Promise>; ensureTriggerRegion: (ep: backend.Endpoint & backend.EventTriggered) => Promise; validateTrigger: (ep: backend.Endpoint, want: backend.Backend) => void; registerTrigger: (ep: backend.Endpoint) => Promise; diff --git a/src/deploy/functions/services/storage.ts b/src/deploy/functions/services/storage.ts index 51e11685f65..fb1cf090c97 100644 --- a/src/deploy/functions/services/storage.ts +++ b/src/deploy/functions/services/storage.ts @@ -12,23 +12,15 @@ const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher"; * @param projectId project identifier * @param existingPolicy the project level IAM policy */ -export async function obtainStorageBindings( - projectNumber: string, - existingPolicy: iam.Policy -): Promise> { +export async function obtainStorageBindings(projectNumber: string): Promise> { const storageResponse = await storage.getServiceAccount(projectNumber); const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`; - let pubsubBinding = existingPolicy.bindings.find((b) => b.role === PUBSUB_PUBLISHER_ROLE); - if (!pubsubBinding) { - pubsubBinding = { - role: PUBSUB_PUBLISHER_ROLE, - members: [], - }; - } - if (!pubsubBinding.members.find((m) => m === storageServiceAgent)) { - pubsubBinding.members.push(storageServiceAgent); // add service agent to role - } - return [pubsubBinding]; + + const pubsubPublisherBinding = { + role: PUBSUB_PUBLISHER_ROLE, + members: [storageServiceAgent], + }; + return [pubsubPublisherBinding]; } /** diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index e4e72a45af4..a3addfaee85 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -240,12 +240,23 @@ export function mebibytes(memory: string): number { * @param err The error returned from the operation. */ function functionsOpLogReject(funcName: string, type: string, err: any): void { + utils.logWarning(clc.bold.yellow("functions:") + ` ${err?.message}`); if (err?.context?.response?.statusCode === 429) { utils.logWarning( `${clc.bold.yellow( "functions:" )} got "Quota Exceeded" error while trying to ${type} ${funcName}. Waiting to retry...` ); + } else if ( + err?.message.includes( + "If you recently started to use Eventarc, it may take a few minutes before all necessary permissions are propagated to the Service Agent" + ) + ) { + utils.logWarning( + `${clc.bold.yellow( + "functions:" + )} since this is your first time using functions v2, we need a little bit longer to finish setting everything up, please retry the deployment in a few minutes.` + ); } else { utils.logWarning( clc.bold.yellow("functions:") + " failed to " + type + " function " + funcName diff --git a/src/gcp/serviceusage.ts b/src/gcp/serviceusage.ts new file mode 100644 index 00000000000..bca90812de7 --- /dev/null +++ b/src/gcp/serviceusage.ts @@ -0,0 +1,33 @@ +import { bold } from "cli-color"; +import { serviceUsageOrigin } from "../api"; +import { Client } from "../apiv2"; +import { FirebaseError } from "../error"; +import * as utils from "../utils"; + +const apiClient = new Client({ + urlPrefix: serviceUsageOrigin, + apiVersion: "v1beta1", +}); + +/** + * Generate the service account for the service. Note: not every service uses the endpoint. + * @param projectNumber gcp project number + * @param service the service api (ex~ pubsub.googleapis.com) + * @returns + */ +export async function generateServiceIdentity( + projectNumber: string, + service: string, + prefix: string +) { + utils.logLabeledBullet(prefix, `generating the service identity for ${bold(service)}...`); + try { + return await apiClient.post( + `projects/${projectNumber}/services/${service}:generateServiceIdentity` + ); + } catch (err: unknown) { + throw new FirebaseError(`Error generating the service identity for ${service}.`, { + original: err as Error, + }); + } +} diff --git a/src/test/deploy/functions/checkIam.spec.ts b/src/test/deploy/functions/checkIam.spec.ts index e7e7eaddbb3..2b08edebfa5 100644 --- a/src/test/deploy/functions/checkIam.spec.ts +++ b/src/test/deploy/functions/checkIam.spec.ts @@ -5,6 +5,7 @@ import * as storage from "../../../gcp/storage"; import * as rm from "../../../gcp/resourceManager"; import * as backend from "../../../deploy/functions/backend"; +const projectId = "my-project"; const projectNumber = "123456789"; const STORAGE_RES = { @@ -61,105 +62,9 @@ describe("checkIam", () => { ], }; - describe("obtainBinding", () => { - it("should assign the service account the role if they don't exist in the policy", () => { - const policy = { ...iamPolicy }; - const serviceAccount = "myServiceAccount"; - const role = "role/myrole"; - - const bindings = checkIam.obtainBinding(policy, serviceAccount, role); - - expect(bindings).to.deep.equal({ - role, - members: [serviceAccount], - }); - }); - - it("should append the service account as a member of the role when the role exists in the policy", () => { - const policy = { ...iamPolicy }; - const serviceAccount = "myServiceAccount"; - const role = "role/myrole"; - policy.bindings = [ - { - role, - members: ["someuser"], - }, - ]; - - const bindings = checkIam.obtainBinding(policy, serviceAccount, role); - - expect(bindings).to.deep.equal({ - role, - members: ["someuser", serviceAccount], - }); - }); - - it("should not add the role or service account if the policy already has the binding", () => { - const policy = { ...iamPolicy }; - const serviceAccount = "myServiceAccount"; - const role = "role/myrole"; - policy.bindings = [ - { - role, - members: [serviceAccount], - }, - ]; - - const bindings = checkIam.obtainBinding(policy, serviceAccount, role); - - expect(bindings).to.deep.equal({ - role, - members: [serviceAccount], - }); - }); - }); - describe("obtainPubSubServiceAgentBindings", () => { - it("should add the binding", () => { - const policy = { ...iamPolicy }; - - const bindings = checkIam.obtainPubSubServiceAgentBindings(projectNumber, policy); - - expect(bindings.length).to.equal(1); - expect(bindings[0]).to.deep.equal({ - role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: [`serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`], - }); - }); - - it("should add the service agent as a member", () => { - const policy = { ...iamPolicy }; - policy.bindings = [ - { - role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: ["someuser"], - }, - ]; - - const bindings = checkIam.obtainPubSubServiceAgentBindings(projectNumber, policy); - - expect(bindings.length).to.equal(1); - expect(bindings[0]).to.deep.equal({ - role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: [ - "someuser", - `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`, - ], - }); - }); - - it("should do nothing if we have the binding", () => { - const policy = { ...iamPolicy }; - policy.bindings = [ - { - role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE, - members: [ - `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`, - ], - }, - ]; - - const bindings = checkIam.obtainPubSubServiceAgentBindings(projectNumber, policy); + it("should obtain the bindings", () => { + const bindings = checkIam.obtainPubSubServiceAgentBindings(projectNumber); expect(bindings.length).to.equal(1); expect(bindings[0]).to.deep.equal({ @@ -170,10 +75,8 @@ describe("checkIam", () => { }); describe("obtainDefaultComputeServiceAgentBindings", () => { - it("should add both bindings", () => { - const policy = { ...iamPolicy }; - - const bindings = checkIam.obtainDefaultComputeServiceAgentBindings(projectNumber, policy); + it("should obtain the bindings", () => { + const bindings = checkIam.obtainDefaultComputeServiceAgentBindings(projectNumber); expect(bindings.length).to.equal(2); expect(bindings).to.include.deep.members([ @@ -190,27 +93,29 @@ describe("checkIam", () => { }); describe("mergeBindings", () => { - it("should skip empty or duplicate bindings", () => { + it("should not update the policy when the bindings are present", () => { const policy = { etag: "etag", version: 3, bindings: [BINDING], }; - checkIam.mergeBindings(policy, [[], [BINDING]]); + const updated = checkIam.mergeBindings(policy, [BINDING]); + expect(updated).to.be.false; expect(policy.bindings).to.deep.equal([BINDING]); }); - it("should update current binding", () => { + it("should update the members of a binding in the policy", () => { const policy = { etag: "etag", version: 3, bindings: [BINDING], }; - checkIam.mergeBindings(policy, [[{ role: "some/role", members: ["newuser"] }]]); + const updated = checkIam.mergeBindings(policy, [{ role: "some/role", members: ["newuser"] }]); + expect(updated).to.be.true; expect(policy.bindings).to.deep.equal([ { role: "some/role", @@ -219,44 +124,22 @@ describe("checkIam", () => { ]); }); - it("should add the binding", () => { + it("should add a new binding to the policy", () => { const policy = { etag: "etag", version: 3, bindings: [], }; - checkIam.mergeBindings(policy, [[BINDING]]); + const updated = checkIam.mergeBindings(policy, [BINDING]); + expect(updated).to.be.true; expect(policy.bindings).to.deep.equal([BINDING]); }); }); describe("ensureServiceAgentRoles", () => { - it("should return early if we fail to get the IAM policy", async () => { - getIamStub.rejects("Failed to get the IAM policy"); - const wantFn: backend.Endpoint = { - id: "wantFn", - entryPoint: "wantFn", - platform: "gcfv2", - eventTrigger: { - eventType: "google.cloud.storage.object.v1.finalized", - eventFilters: { bucket: "my-bucket" }, - retry: false, - }, - ...SPEC, - }; - - await expect( - checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.empty()) - ).to.not.be.rejected; - expect(getIamStub).to.have.been.calledOnce; - expect(getIamStub).to.have.been.calledWith(projectNumber); - expect(storageStub).to.not.have.been.called; - expect(setIamStub).to.not.have.been.called; - }); - - it("should skip v1, callable, and deployed functions", async () => { + it("should return early if we do not have new services", async () => { const v1EventFn: backend.Endpoint = { id: "v1eventfn", entryPoint: "v1Fn", @@ -288,6 +171,7 @@ describe("checkIam", () => { }; await checkIam.ensureServiceAgentRoles( + projectId, projectNumber, backend.of(wantFn), backend.of(v1EventFn, v2CallableFn, wantFn) @@ -298,7 +182,9 @@ describe("checkIam", () => { expect(setIamStub).to.not.have.been.called; }); - it("should skip if we have a deployed event fn of the same kind", async () => { + it("should return early if we fail to get the IAM policy", async () => { + storageStub.resolves(STORAGE_RES); + getIamStub.rejects("Failed to get the IAM policy"); const wantFn: backend.Endpoint = { id: "wantFn", entryPoint: "wantFn", @@ -310,23 +196,56 @@ describe("checkIam", () => { }, ...SPEC, }; - const haveFn: backend.Endpoint = { - id: "haveFn", - entryPoint: "haveFn", + + await expect( + checkIam.ensureServiceAgentRoles( + projectId, + projectNumber, + backend.of(wantFn), + backend.empty() + ) + ).to.not.be.rejected; + expect(storageStub).to.have.been.calledOnce; + expect(getIamStub).to.have.been.calledOnce; + expect(getIamStub).to.have.been.calledWith(projectNumber); + expect(setIamStub).to.not.have.been.called; + }); + + it("should error if we fail to set the IAM policy", async () => { + storageStub.resolves(STORAGE_RES); + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [BINDING], + }); + const wantFn: backend.Endpoint = { + id: "wantFn", + entryPoint: "wantFn", platform: "gcfv2", eventTrigger: { - eventType: "google.cloud.storage.object.v1.metadataUpdated", + eventType: "google.cloud.storage.object.v1.finalized", eventFilters: { bucket: "my-bucket" }, retry: false, }, ...SPEC, }; - await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.of(haveFn)); - - expect(storageStub).to.not.have.been.called; - expect(getIamStub).to.not.have.been.called; - expect(setIamStub).to.not.have.been.called; + await expect( + checkIam.ensureServiceAgentRoles( + projectId, + projectNumber, + backend.of(wantFn), + backend.empty() + ) + ).to.be.rejectedWith( + "We failed to modify the IAM policy for the project. The functions " + + "deployment requires specific roles to be granted to service agents," + + " otherwise the deployment will fail." + ); + expect(storageStub).to.have.been.calledOnce; + expect(getIamStub).to.have.been.calledOnce; + expect(getIamStub).to.have.been.calledWith(projectNumber); + expect(setIamStub).to.have.been.calledOnce; }); it("should add the pubsub publisher role and all default bindings for a new v2 storage function without v2 deployed functions", async () => { @@ -374,7 +293,12 @@ describe("checkIam", () => { ...SPEC, }; - await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.empty()); + await checkIam.ensureServiceAgentRoles( + projectId, + projectNumber, + backend.of(wantFn), + backend.empty() + ); expect(storageStub).to.have.been.calledOnce; expect(getIamStub).to.have.been.calledOnce; @@ -425,7 +349,12 @@ describe("checkIam", () => { ...SPEC, }; - await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.of(haveFn)); + await checkIam.ensureServiceAgentRoles( + projectId, + projectNumber, + backend.of(wantFn), + backend.of(haveFn) + ); expect(storageStub).to.have.been.calledOnce; expect(getIamStub).to.have.been.calledOnce; @@ -473,7 +402,12 @@ describe("checkIam", () => { ...SPEC, }; - await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.empty()); + await checkIam.ensureServiceAgentRoles( + projectId, + projectNumber, + backend.of(wantFn), + backend.empty() + ); expect(getIamStub).to.have.been.calledOnce; expect(setIamStub).to.have.been.calledOnce; @@ -481,17 +415,6 @@ describe("checkIam", () => { }); it("should not add bindings for a new v2 alerts function with v2 deployed functions", async () => { - const newIamPolicy = { - etag: "etag", - version: 3, - bindings: [BINDING], - }; - getIamStub.resolves({ - etag: "etag", - version: 3, - bindings: [BINDING], - }); - setIamStub.resolves(newIamPolicy); const wantFn: backend.Endpoint = { id: "wantFn", entryPoint: "wantFn", @@ -515,9 +438,14 @@ describe("checkIam", () => { ...SPEC, }; - await checkIam.ensureServiceAgentRoles(projectNumber, backend.of(wantFn), backend.of(haveFn)); + await checkIam.ensureServiceAgentRoles( + projectId, + projectNumber, + backend.of(wantFn), + backend.of(haveFn) + ); - expect(getIamStub).to.have.been.calledOnce; + expect(getIamStub).to.not.have.been.called; expect(setIamStub).to.not.have.been.called; }); }); diff --git a/src/test/deploy/functions/services/storage.spec.ts b/src/test/deploy/functions/services/storage.spec.ts index 38a21453e89..04be3ad0fe5 100644 --- a/src/test/deploy/functions/services/storage.spec.ts +++ b/src/test/deploy/functions/services/storage.spec.ts @@ -10,11 +10,6 @@ const STORAGE_RES = { kind: "storage#serviceAccount", }; -const BINDING = { - role: "some/role", - members: ["someuser"], -}; - describe("obtainStorageBindings", () => { let storageStub: sinon.SinonStub; @@ -28,55 +23,11 @@ describe("obtainStorageBindings", () => { sinon.verifyAndRestore(); }); - it("should return pubsub binding when missing from the policy", async () => { - storageStub.resolves(STORAGE_RES); - const existingPolicy = { - etag: "etag", - version: 3, - bindings: [BINDING], - }; - - const bindings = await obtainStorageBindings(projectNumber, existingPolicy); - - expect(bindings.length).to.equal(1); - expect(bindings[0]).to.deep.equal({ - role: "roles/pubsub.publisher", - members: [`serviceAccount:${STORAGE_RES.email_address}`], - }); - }); - - it("should return the updated pubsub binding from the policy", async () => { + it("should return the correct storage binding", async () => { storageStub.resolves(STORAGE_RES); - const existingPolicy = { - etag: "etag", - version: 3, - bindings: [BINDING, { role: "roles/pubsub.publisher", members: ["someuser"] }], - }; - - const bindings = await obtainStorageBindings(projectNumber, existingPolicy); - - expect(bindings.length).to.equal(1); - expect(bindings[0]).to.deep.equal({ - role: "roles/pubsub.publisher", - members: ["someuser", `serviceAccount:${STORAGE_RES.email_address}`], - }); - }); - it("should return the binding from policy if already present", async () => { - storageStub.resolves(STORAGE_RES); - const existingPolicy = { - etag: "etag", - version: 3, - bindings: [ - BINDING, - { - role: "roles/pubsub.publisher", - members: [`serviceAccount:${STORAGE_RES.email_address}`], - }, - ], - }; + const bindings = await obtainStorageBindings(projectNumber); - const bindings = await obtainStorageBindings(projectNumber, existingPolicy); expect(bindings.length).to.equal(1); expect(bindings[0]).to.deep.equal({ role: "roles/pubsub.publisher", From 1b54ca132a5ee6afcb4772303ab2df059c69682a Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 4 May 2022 11:02:03 -0700 Subject: [PATCH 0295/1699] Stop using github flavored markdown for warnings (#4517) --- src/extensions/warnings.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/extensions/warnings.ts b/src/extensions/warnings.ts index 5a125ab42b1..a5d3e686b19 100644 --- a/src/extensions/warnings.ts +++ b/src/extensions/warnings.ts @@ -102,7 +102,8 @@ export async function displayWarningsForDeploy(instancesToCreate: InstanceSpec[] marked( `The following are instances of ${clc.bold( "experimental" - )} extensions.They may not be production-ready. Their functionality may change in backward-incompatible ways before their official release, or they may be discontinued.\n${humanReadableList}\n` + )} extensions.They may not be production-ready. Their functionality may change in backward-incompatible ways before their official release, or they may be discontinued.\n${humanReadableList}\n`, + { gfm: false } ) ); } @@ -112,9 +113,10 @@ export async function displayWarningsForDeploy(instancesToCreate: InstanceSpec[] utils.logLabeledBullet( logPrefix, marked( - `These extensions are in preview and are built by a developer in the Extensions Publisher Early Access Program (http://bit.ly/firex-provider. Their functionality might change in backwards-incompatible ways. Since these extensions aren't built by Firebase, reach out to their publisher with questions about them.` + + `These extensions are in preview and are built by a developer in the Extensions Publisher Early Access Program (http://bit.ly/firex-provider). Their functionality might change in backwards-incompatible ways. Since these extensions aren't built by Firebase, reach out to their publisher with questions about them.` + ` They are provided “AS ISâ€, without any warranty, express or implied, from Google.` + - ` Google disclaims all liability for any damages, direct or indirect, resulting from the use of these extensions\n${humanReadableList}` + ` Google disclaims all liability for any damages, direct or indirect, resulting from the use of these extensions\n${humanReadableList}`, + { gfm: false } ) ); } From 76892d18a43f6ffac8e40242e17090d1299dd6ff Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 4 May 2022 13:42:51 -0700 Subject: [PATCH 0296/1699] Add `build:watch` to `package.json` when initing Typescript functions (#4520) * Add `build:watch` to `package.json` when initing Typescript functions * Update CHANGELOG.md Co-authored-by: Bryan Kendall Co-authored-by: Bryan Kendall --- CHANGELOG.md | 1 + templates/extensions/typescript/package.lint.json | 3 ++- templates/extensions/typescript/package.nolint.json | 3 ++- templates/init/functions/typescript/package.lint.json | 1 + templates/init/functions/typescript/package.nolint.json | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a8fdf8a2c..25fda35bc1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Add support for secrets to v2 functions (#4451). - Fixes an issue where `ext:export` would write different param values than what it displayed in the prompt (#4515). - Enhances the functions IAM permission process and updates the error messages (#4511). +- Adds 'build:watch' npm script to `firebase init` when initializing a project with Typescript functions. diff --git a/templates/extensions/typescript/package.lint.json b/templates/extensions/typescript/package.lint.json index 7dc16acf965..1dcfbe14840 100644 --- a/templates/extensions/typescript/package.lint.json +++ b/templates/extensions/typescript/package.lint.json @@ -2,7 +2,8 @@ "name": "functions", "scripts": { "lint": "eslint \"src/**/*\"", - "build": "tsc" + "build": "tsc", + "build:watch": "tsc --watch" }, "main": "lib/index.js", "dependencies": { diff --git a/templates/extensions/typescript/package.nolint.json b/templates/extensions/typescript/package.nolint.json index 01d543f37a0..3dfb7de37fc 100644 --- a/templates/extensions/typescript/package.nolint.json +++ b/templates/extensions/typescript/package.nolint.json @@ -1,7 +1,8 @@ { "name": "functions", "scripts": { - "build": "tsc" + "build": "tsc", + "build:watch": "tsc --watch" }, "main": "lib/index.js", "dependencies": { diff --git a/templates/init/functions/typescript/package.lint.json b/templates/init/functions/typescript/package.lint.json index 9b3e921f0c1..a85202ba068 100644 --- a/templates/init/functions/typescript/package.lint.json +++ b/templates/init/functions/typescript/package.lint.json @@ -3,6 +3,7 @@ "scripts": { "lint": "eslint --ext .js,.ts .", "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", diff --git a/templates/init/functions/typescript/package.nolint.json b/templates/init/functions/typescript/package.nolint.json index 8ae2d79ebef..1b96c436bb1 100644 --- a/templates/init/functions/typescript/package.nolint.json +++ b/templates/init/functions/typescript/package.nolint.json @@ -2,6 +2,7 @@ "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", From 5352c3c2f9721d34865cbd0d2e3f736337a15a20 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 5 May 2022 06:12:04 +0900 Subject: [PATCH 0297/1699] Stop pruning secrets on function deploys/secret updates. (#4519) These pruning are surprising users (see https://github.com/firebase/firebase-tools/issues/4459). I'm going to work on improving the UX of the pruning. Until then, users can still prune unused secrets via `functions:secrets:prune` command. --- CHANGELOG.md | 1 + src/commands/functions-secrets-set.ts | 22 +------------------- src/deploy/functions/release/index.ts | 30 --------------------------- 3 files changed, 2 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25fda35bc1f..07417b7e68e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ - Add support for secrets to v2 functions (#4451). - Fixes an issue where `ext:export` would write different param values than what it displayed in the prompt (#4515). - Enhances the functions IAM permission process and updates the error messages (#4511). +- Removes logic for automatically pruning stale secrets after function deploys (#4519). - Adds 'build:watch' npm script to `firebase init` when initializing a project with Typescript functions. diff --git a/src/commands/functions-secrets-set.ts b/src/commands/functions-secrets-set.ts index 98ea4cfb58a..69721f55d56 100644 --- a/src/commands/functions-secrets-set.ts +++ b/src/commands/functions-secrets-set.ts @@ -103,25 +103,5 @@ export default new Command("functions:secrets:set ") logBullet(`Updated function ${e.id}(${e.region}).`); return updated; }); - const updatedEndpoints = await Promise.all(updateOps); - - logBullet(`Pruning stale secrets...`); - const prunedResult = await pruneAndDestroySecrets( - { projectId, projectNumber }, - updatedEndpoints - ); - if (prunedResult.destroyed.length > 0) { - logBullet( - `Detroyed unused secret versions: ${prunedResult.destroyed - .map((s) => `${s.secret}@${s.version}`) - .join(", ")}` - ); - } - if (prunedResult.erred.length > 0) { - logWarning( - `Failed to destroy unused secret versions:\n\t${prunedResult.erred - .map((err) => err.message) - .join("\n\t")}` - ); - } + await Promise.all(updateOps); }); diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index f03c2f8aafc..ed5f5d12055 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -101,36 +101,6 @@ export async function release( if (allErrors.length) { const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors }; throw new FirebaseError("There was an error deploying functions", { ...opts, exit: 2 }); - } else { - if (secrets.of(haveEndpoints).length > 0) { - const projectId = needProjectId(options); - const projectNumber = await needProjectNumber(options); - // Re-load backend with all endpoints, not just the ones deployed. - const reloadedBackend = await backend.existingBackend( - { projectId } as args.Context, - /* forceRefresh= */ true - ); - const prunedResult = await secrets.pruneAndDestroySecrets( - { projectId, projectNumber }, - backend.allEndpoints(reloadedBackend) - ); - if (prunedResult.destroyed.length > 0) { - logLabeledBullet( - "functions", - `Destroyed unused secret versions: ${prunedResult.destroyed - .map((s) => `${s.secret}@${s.version}`) - .join(", ")}` - ); - } - if (prunedResult.erred.length > 0) { - logLabeledWarning( - "functions", - `Failed to destroy unused secret versions:\n\t${prunedResult.erred - .map((err) => err.message) - .join("\n\t")}` - ); - } - } } } From 5ea735ec96eec1eb4d016ea447e092dadbec7481 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 4 May 2022 14:39:24 -0700 Subject: [PATCH 0298/1699] Inlined.cpu.3 (#4514) 1. Added validation to CPU config 2. When tests weren't failing, I realized you need to say `throw()` not `throw` in Chai Expect, which meant none of our throwing tests were actually testing anything. Used find + replace to turn all `throw;` into `throw();` and fixed broken tests. --- src/deploy/functions/validate.ts | 34 ++++++++++++++++ src/test/accountExporter.spec.ts | 4 +- src/test/accountImporter.spec.ts | 4 +- src/test/checkMinRequiredVersion.spec.ts | 4 +- src/test/command.spec.ts | 2 +- .../deploy/functions/release/planner.spec.ts | 4 +- .../runtimes/discovery/parsing.spec.ts | 8 ++-- src/test/deploy/functions/validate.spec.ts | 40 ++++++++++++++++--- src/test/deploy/hosting/hashcache.spec.ts | 2 +- .../emulators/storage/persistence.spec.ts | 2 +- src/test/fsAsync.spec.ts | 2 +- src/test/functional.spec.ts | 2 +- src/test/functions/env.spec.ts | 2 +- src/test/gcp/cloudfunctions.spec.ts | 2 +- src/test/gcp/cloudfunctionsv2.spec.ts | 2 +- src/test/rulesDeploy.spec.ts | 2 +- 16 files changed, 90 insertions(+), 26 deletions(-) diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 928a4b67c82..01b6784cb38 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -44,6 +44,40 @@ export function endpointsAreValid(wantBackend: backend.Backend): void { tooSmallForConcurrency.join(","); throw new FirebaseError(msg); } + + const gcfV1WithCPU = endpoints + .filter((endpoint) => endpoint.platform === "gcfv1" && typeof endpoint["cpu"] !== "undefined") + .map((endpoint) => endpoint.id); + if (gcfV1WithCPU.length) { + const msg = `Cannot set CPU on the functions ${gcfV1WithCPU.join( + "," + )} because they are GCF gen 1`; + throw new FirebaseError(msg); + } + + const invalidCPU = endpoints + .filter((endpoint) => { + if (typeof endpoint.cpu === "undefined") { + return false; + } + if (endpoint.cpu === "gcf_gen1") { + return false; + } + const cpu: number = endpoint.cpu; + // All fractional CPU is allowed apparently? + if (cpu < 1) { + return false; + } + // But whole CPU is limited to fixed sizes + return ![1, 2, 4, 6, 8].includes(cpu); + }) + .map((endpoint) => endpoint.id); + if (invalidCPU.length) { + const msg = `The following functions have invalid CPU settings ${invalidCPU.join( + "," + )}. Valid CPU options are (0, 1], 2, 4, 6, 8, or "gcf_gen1"`; + throw new FirebaseError(msg); + } } /** Validate that all endpoints in the given set of backends are unique */ diff --git a/src/test/accountExporter.spec.ts b/src/test/accountExporter.spec.ts index 1d89e73e5fa..6392c3bbfdf 100644 --- a/src/test/accountExporter.spec.ts +++ b/src/test/accountExporter.spec.ts @@ -9,11 +9,11 @@ import { validateOptions, serialExportUsers } from "../accountExporter"; describe("accountExporter", () => { describe("validateOptions", () => { it("should reject when no format provided", () => { - expect(() => validateOptions({}, "output_file")).to.throw; + expect(() => validateOptions({}, "output_file")).to.throw(); }); it("should reject when format is not csv or json", () => { - expect(() => validateOptions({ format: "txt" }, "output_file")).to.throw; + expect(() => validateOptions({ format: "txt" }, "output_file")).to.throw(); }); it("should ignore format param when implicitly specified in file name", () => { diff --git a/src/test/accountImporter.spec.ts b/src/test/accountImporter.spec.ts index f2711280fde..a5c0945d4cb 100644 --- a/src/test/accountImporter.spec.ts +++ b/src/test/accountImporter.spec.ts @@ -33,11 +33,11 @@ describe("accountImporter", () => { describe("validateOptions", () => { it("should reject when unsupported hash algorithm provided", () => { - expect(() => validateOptions({ hashAlgo: "MD2" })).to.throw; + expect(() => validateOptions({ hashAlgo: "MD2" })).to.throw(); }); it("should reject when missing parameters", () => { - expect(() => validateOptions({ hashAlgo: "HMAC_SHA1" })).to.throw; + expect(() => validateOptions({ hashAlgo: "HMAC_SHA1" })).to.throw(); }); }); diff --git a/src/test/checkMinRequiredVersion.spec.ts b/src/test/checkMinRequiredVersion.spec.ts index 69cc83c440f..f467169792c 100644 --- a/src/test/checkMinRequiredVersion.spec.ts +++ b/src/test/checkMinRequiredVersion.spec.ts @@ -21,7 +21,7 @@ describe("checkMinRequiredVersion", () => { expect(() => { checkMinRequiredVersion({}, "key"); - }).to.throw; + }).to.throw(); }); it("should not error if installed version is above the min required version", () => { @@ -29,6 +29,6 @@ describe("checkMinRequiredVersion", () => { expect(() => { checkMinRequiredVersion({}, "key"); - }).not.to.throw; + }).not.to.throw(); }); }); diff --git a/src/test/command.spec.ts b/src/test/command.spec.ts index 844b82b5959..822144f188c 100644 --- a/src/test/command.spec.ts +++ b/src/test/command.spec.ts @@ -26,7 +26,7 @@ describe("Command", () => { command.action(() => { // do nothing }); - }).not.to.throw; + }).not.to.throw(); }); describe("runner", () => { diff --git a/src/test/deploy/functions/release/planner.spec.ts b/src/test/deploy/functions/release/planner.spec.ts index d3e05c0858f..6b8c48da2db 100644 --- a/src/test/deploy/functions/release/planner.spec.ts +++ b/src/test/deploy/functions/release/planner.spec.ts @@ -43,7 +43,7 @@ describe("planner", () => { it("throws on illegal updates", () => { const httpsFunc = func("a", "b", { httpsTrigger: {} }); const scheduleFunc = func("a", "b", { scheduleTrigger: {} }); - expect(() => planner.calculateUpdate(httpsFunc, scheduleFunc)).to.throw; + expect(() => planner.calculateUpdate(httpsFunc, scheduleFunc)).to.throw(); }); it("knows to delete & recreate for v2 topic changes", () => { @@ -316,7 +316,7 @@ describe("planner", () => { const have: backend.Endpoint = { ...func("id", "region"), platform: "gcfv1" }; const want: backend.Endpoint = { ...func("id", "region"), platform: "gcfv2" }; - expect(() => planner.checkForIllegalUpdate(want, have)).to.throw; + expect(() => planner.checkForIllegalUpdate(want, have)).to.throw(); }); it("should throw if a https function would be changed into an event triggered function", () => { diff --git a/src/test/deploy/functions/runtimes/discovery/parsing.spec.ts b/src/test/deploy/functions/runtimes/discovery/parsing.spec.ts index b6136246716..c0446f22b0e 100644 --- a/src/test/deploy/functions/runtimes/discovery/parsing.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/parsing.spec.ts @@ -51,7 +51,7 @@ describe("assertKeyTypes", () => { it(`handles a ${testType} when expecting a ${type}`, () => { const obj = { [type]: val }; if (type === testType) { - expect(() => parsing.assertKeyTypes("", obj, schema)).not.to.throw; + expect(() => parsing.assertKeyTypes("", obj, schema)).not.to.throw(); } else { expect(() => parsing.assertKeyTypes("", obj, schema)).to.throw(FirebaseError); } @@ -79,9 +79,9 @@ describe("assertKeyTypes", () => { const schema = { cpu: (val: hasCPU["cpu"]) => typeof val === "number" || val === "gcf_gen1", }; - expect(() => parsing.assertKeyTypes("", literalCPU, schema)).to.not.throw; - expect(() => parsing.assertKeyTypes("", symbolicCPU, schema)).to.not.throw; - expect(() => parsing.assertKeyTypes("", badCPU, schema)).to.throw; + expect(() => parsing.assertKeyTypes("", literalCPU, schema)).to.not.throw(); + expect(() => parsing.assertKeyTypes("", symbolicCPU, schema)).to.not.throw(); + expect(() => parsing.assertKeyTypes("", badCPU, schema)).to.throw(); }); it("Throws on superfluous keys", () => { diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index aba8af7d76d..4fca66311a1 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -153,7 +153,7 @@ describe("validate", () => { }); it("Allows endpoints with no mem and no concurrency", () => { - expect(() => validate.endpointsAreValid(backend.of(ENDPOINT_BASE))).to.not.throw; + expect(() => validate.endpointsAreValid(backend.of(ENDPOINT_BASE))).to.not.throw(); }); it("Allows endpoints with mem and no concurrency", () => { @@ -161,7 +161,7 @@ describe("validate", () => { ...ENDPOINT_BASE, availableMemoryMb: 256, }; - expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw(); }); it("Allows explicitly one concurrent", () => { @@ -169,7 +169,7 @@ describe("validate", () => { ...ENDPOINT_BASE, concurrency: 1, }; - expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; + expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw(); }); it("Allows endpoints with enough mem and no concurrency", () => { @@ -315,6 +315,36 @@ describe("validate", () => { expect(() => validate.endpointsAreValid(want)).to.not.throw(); }); + + for (const cpu of [undefined, "gcf_gen1", 0.1, 0.5, 1, 2, 4, 6, 8] as const) { + it(`does not throw for valid CPU ${cpu ?? "undefined"}`, () => { + const want = backend.of({ + ...ENDPOINT_BASE, + platform: "gcfv2", + cpu, + }); + expect(() => validate.endpointsAreValid(want)).to.not.throw(); + }); + } + + it("throws for gcfv1 with CPU", () => { + const want = backend.of({ + ...ENDPOINT_BASE, + platform: "gcfv1", + cpu: 1, + }); + expect(() => validate.endpointsAreValid(want)).to.throw(); + }); + + it("throws for invalid CPU", () => { + const want = backend.of({ + ...ENDPOINT_BASE, + platform: "gcfv2", + // Fractional CPU is only valid for <1 + cpu: 1.5, + }); + expect(() => validate.endpointsAreValid(want)).to.throw(); + }); }); describe("endpointsAreUnqiue", () => { @@ -337,7 +367,7 @@ describe("validate", () => { { ...ENDPOINT_BASE, id: "i3", region: "r2" }, { ...ENDPOINT_BASE, id: "i4", region: "r2" } ); - expect(() => validate.endpointsAreUnique({ b1, b2 })).to.not.throw; + expect(() => validate.endpointsAreUnique({ b1, b2 })).to.not.throw(); }); it("passes given unique id, region pairs", () => { @@ -349,7 +379,7 @@ describe("validate", () => { { ...ENDPOINT_BASE, id: "i1", region: "r2" }, { ...ENDPOINT_BASE, id: "i2", region: "r2" } ); - expect(() => validate.endpointsAreUnique({ b1, b2 })).to.not.throw; + expect(() => validate.endpointsAreUnique({ b1, b2 })).to.not.throw(); }); it("throws given non-unique id region pairs", () => { diff --git a/src/test/deploy/hosting/hashcache.spec.ts b/src/test/deploy/hosting/hashcache.spec.ts index 84e6cd92427..c7382ec0320 100644 --- a/src/test/deploy/hosting/hashcache.spec.ts +++ b/src/test/deploy/hosting/hashcache.spec.ts @@ -17,7 +17,7 @@ describe("hashcache", () => { const name = "testcache"; const data = new Map([["foo", { mtime: 0, hash: "deadbeef" }]]); - expect(dump(dir.name, name, data)).to.not.throw; + expect(() => dump(dir.name, name, data)).to.not.throw(); expect(existsSync(join(dir.name, ".firebase", `hosting.${name}.cache`))).to.be.true; expect(readFileSync(join(dir.name, ".firebase", `hosting.${name}.cache`), "utf8")).to.equal( diff --git a/src/test/emulators/storage/persistence.spec.ts b/src/test/emulators/storage/persistence.spec.ts index dded2ef5779..260054ba2d8 100644 --- a/src/test/emulators/storage/persistence.spec.ts +++ b/src/test/emulators/storage/persistence.spec.ts @@ -17,7 +17,7 @@ describe("Persistence", () => { _persistence.appendBytes(filename, Buffer.from("hello world")); _persistence.deleteFile(filename); - expect(() => _persistence.readBytes(filename, 10)).to.throw; + expect(() => _persistence.readBytes(filename, 10)).to.throw(); }); }); diff --git a/src/test/fsAsync.spec.ts b/src/test/fsAsync.spec.ts index d75d7b15d53..2d1c0f2eb37 100644 --- a/src/test/fsAsync.spec.ts +++ b/src/test/fsAsync.spec.ts @@ -46,7 +46,7 @@ describe("fsAsync", () => { rimraf(baseDir); expect(() => { fs.statSync(baseDir); - }).to.throw; + }).to.throw(); }); describe("readdirRecursive", () => { diff --git a/src/test/functional.spec.ts b/src/test/functional.spec.ts index 753b4ae5aa3..63d12f02327 100644 --- a/src/test/functional.spec.ts +++ b/src/test/functional.spec.ts @@ -86,7 +86,7 @@ describe("functional", () => { expect([...f.zip([1], ["a"])]).to.deep.equal([[1, "a"]]); }); it("throws on length mismatch", () => { - expect(() => f.zip([1], [])).to.throw; + expect(() => [...f.zip([1], [])]).to.throw(); }); }); diff --git a/src/test/functions/env.spec.ts b/src/test/functions/env.spec.ts index 49a95b71878..c38f75481c9 100644 --- a/src/test/functions/env.spec.ts +++ b/src/test/functions/env.spec.ts @@ -302,7 +302,7 @@ FOO=foo rimraf(tmpdir); expect(() => { fs.statSync(tmpdir); - }).to.throw; + }).to.throw(); }); it("loads nothing if .env files are missing", () => { diff --git a/src/test/gcp/cloudfunctions.spec.ts b/src/test/gcp/cloudfunctions.spec.ts index ca6ea473c81..bce9bb62e41 100644 --- a/src/test/gcp/cloudfunctions.spec.ts +++ b/src/test/gcp/cloudfunctions.spec.ts @@ -55,7 +55,7 @@ describe("cloudfunctions", () => { { ...ENDPOINT, platform: "gcfv2", httpsTrigger: {} }, UPLOAD_URL ); - }).to.throw; + }).to.throw(); }); it("should copy a minimal function", () => { diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 1c1606e0eb3..e9f73516684 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -82,7 +82,7 @@ describe("cloudfunctionsv2", () => { { ...ENDPOINT, httpsTrigger: {}, platform: "gcfv1" }, CLOUD_FUNCTION_V2_SOURCE ); - }).to.throw; + }).to.throw(); }); it("should copy a minimal function", () => { diff --git a/src/test/rulesDeploy.spec.ts b/src/test/rulesDeploy.spec.ts index 25d8a2f4695..7d589c40ccb 100644 --- a/src/test/rulesDeploy.spec.ts +++ b/src/test/rulesDeploy.spec.ts @@ -29,7 +29,7 @@ describe("RulesDeploy", () => { expect(() => { rd.addFile("firestore.rules"); - }).to.not.throw; + }).to.not.throw(); }); it("should throw an error if the file does not exist", () => { From 2f8f0cb2f1660c18ca888c37ef705cc558147114 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 5 May 2022 06:56:59 +0900 Subject: [PATCH 0299/1699] Update function label used to match the filter style. (#4518) Instead of outputting function name w/ in the following style: [codebase]function(region) We will instead format the function name as follows: codebase:function(region) This is nice since it matches the format expected by the --only filter. Thanks @inlined! --- src/deploy/functions/functionsDeployHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deploy/functions/functionsDeployHelper.ts b/src/deploy/functions/functionsDeployHelper.ts index b79a42b65dc..ee2914dcfe6 100644 --- a/src/deploy/functions/functionsDeployHelper.ts +++ b/src/deploy/functions/functionsDeployHelper.ts @@ -130,7 +130,7 @@ export function getEndpointFilters(options: { only?: string }): EndpointFilter[] export function getFunctionLabel(fn: backend.TargetIds & { codebase?: string }): string { let id = `${fn.id}(${fn.region})`; if (fn.codebase && fn.codebase !== DEFAULT_CODEBASE) { - id = `[${fn.codebase}]${id}`; + id = `${fn.codebase}:${id}`; } return id; } From d322ac0e6bb297ca1fd064608deafe59aa6a8922 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 4 May 2022 23:11:09 +0000 Subject: [PATCH 0300/1699] 10.8.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e3a17217bd9..edadccaa494 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.7.2", + "version": "10.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.7.2", + "version": "10.8.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 1f32a1a452c..d52686d01f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.7.2", + "version": "10.8.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 3522dd733756f83a0a0ed2a0d53f0a972f32b2ae Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 4 May 2022 23:11:32 +0000 Subject: [PATCH 0301/1699] [firebase-release] Removed change log and reset repo after 10.8.0 release --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07417b7e68e..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +0,0 @@ -- Add support for secrets to v2 functions (#4451). -- Fixes an issue where `ext:export` would write different param values than what it displayed in the prompt (#4515). -- Enhances the functions IAM permission process and updates the error messages (#4511). -- Removes logic for automatically pruning stale secrets after function deploys (#4519). -- Adds 'build:watch' npm script to `firebase init` when initializing a project with Typescript functions. From 9733b8489ef1be10afd6b11ab0ae0a8151cd1a79 Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Thu, 5 May 2022 11:59:10 -0700 Subject: [PATCH 0302/1699] fix canonicalizeRefInput --- src/commands/ext-install.ts | 2 +- src/extensions/extensionsHelper.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 4f1da3c5261..f2913f5e9a2 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -95,7 +95,7 @@ export default new Command("ext:install [extensionName]") void track("Extension Install", "Install by Source", options.interactive ? 1 : 0); } else { void track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); - extensionName = canonicalizeRefInput(extensionName); + extensionName = await canonicalizeRefInput(extensionName); extensionVersion = await extensionsApi.getExtensionVersion(extensionName); await infoExtensionVersion({ extensionName, diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 4074b0db1e1..cc7845e19cd 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -39,6 +39,7 @@ import { envOverride } from "../utils"; import { getLocalChangelog } from "./changelog"; import { getProjectNumber } from "../getProjectNumber"; import { Constants } from "../emulator/constants"; +import { resolveVersion } from "../deploy/extensions/planner"; /** * SpecParamType represents the exact strings that the extensions @@ -782,7 +783,7 @@ export async function diagnoseAndFixProject(options: any): Promise { * 1. Infer firebase publisher if not provided * 2. Infer "latest" as the version if not provided */ -export function canonicalizeRefInput(extensionName: string): string { +export async function canonicalizeRefInput(extensionName: string): Promise { // Infer firebase if publisher ID not provided. if (extensionName.split("/").length < 2) { const [extensionID, version] = extensionName.split("@"); @@ -790,8 +791,6 @@ export function canonicalizeRefInput(extensionName: string): string { } // Get the correct version for a given extension reference from the Registry API. const ref = refs.parse(extensionName); - if (!ref.version) { - extensionName = `${extensionName}@latest`; - } - return extensionName; + ref.version = await resolveVersion(ref); + return refs.toExtensionVersionName(ref); } From 80350e911d69706e8207cf2879284c6f30f0f619 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 5 May 2022 13:54:03 -0700 Subject: [PATCH 0303/1699] Un-hide CF3v2 (#4525) --- src/commands/functions-list.ts | 32 ++++----- src/deploy/functions/backend.ts | 5 -- src/deploy/functions/prepare.ts | 11 --- src/deploy/functions/runtimes/node/index.ts | 78 ++++++++++----------- src/functions/functionslog.ts | 17 ++--- src/previews.ts | 2 - src/test/deploy/functions/backend.spec.ts | 53 +++++++------- src/test/functions/functionsLog.spec.ts | 16 ----- 8 files changed, 79 insertions(+), 135 deletions(-) diff --git a/src/commands/functions-list.ts b/src/commands/functions-list.ts index a81e0995bda..58c15220f86 100644 --- a/src/commands/functions-list.ts +++ b/src/commands/functions-list.ts @@ -5,7 +5,6 @@ import { needProjectId } from "../projectUtils"; import { Options } from "../options"; import { requirePermissions } from "../requirePermissions"; import * as backend from "../deploy/functions/backend"; -import { previews } from "../previews"; import { logger } from "../logger"; import Table = require("cli-table"); @@ -19,28 +18,21 @@ export default new Command("functions:list") } as args.Context; const existing = await backend.existingBackend(context); const endpointsList = backend.allEndpoints(existing).sort(backend.compareFunctions); - const table = previews.functionsv2 - ? new Table({ - head: ["Function", "Version", "Trigger", "Location", "Memory", "Runtime"], - style: { head: ["yellow"] }, - }) - : new Table({ - head: ["Function", "Trigger", "Location", "Memory", "Runtime"], - style: { head: ["yellow"] }, - }); + const table = new Table({ + head: ["Function", "Version", "Trigger", "Location", "Memory", "Runtime"], + style: { head: ["yellow"] }, + }); for (const endpoint of endpointsList) { const trigger = backend.endpointTriggerType(endpoint); const availableMemoryMb = endpoint.availableMemoryMb || "---"; - const entry = previews.functionsv2 - ? [ - endpoint.id, - endpoint.platform === "gcfv2" ? "v2" : "v1", - trigger, - endpoint.region, - availableMemoryMb, - endpoint.runtime, - ] - : [endpoint.id, trigger, endpoint.region, availableMemoryMb, endpoint.runtime]; + const entry = [ + endpoint.id, + endpoint.platform === "gcfv2" ? "v2" : "v1", + trigger, + endpoint.region, + availableMemoryMb, + endpoint.runtime, + ]; table.push(entry); } logger.info(table.toString()); diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index ef6a3b5e334..186929ef0bc 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -6,7 +6,6 @@ import * as utils from "../../utils"; import * as runtimes from "./runtimes"; import { FirebaseError } from "../../error"; import { Context } from "./args"; -import { previews } from "../../previews"; import { flattenArray, zip } from "../../functional"; /** Retry settings for a ScheduleSpec. */ @@ -525,10 +524,6 @@ async function loadExistingBackend(ctx: Context & PrivateContextFields): Promise } ctx.unreachableRegions.gcfV1 = gcfV1Results.unreachable; - if (!previews.functionsv2) { - return; - } - let gcfV2Results; try { gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId); diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index f0ae4f0f1f8..57a2773a215 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -148,17 +148,6 @@ export async function prepare( ); } if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) { - if (!previews.functionsv2) { - throw new FirebaseError( - "This version of firebase-tools does not support Google Cloud " + - "Functions gen 2\n" + - "If Cloud Functions for Firebase gen 2 is still in alpha, sign up " + - "for the alpha program at " + - "https://services.google.com/fb/forms/firebasealphaprogram/\n" + - "If Cloud Functions for Firebase gen 2 is in beta, get the latest " + - "version of Firebse Tools with `npm i -g firebase-tools@latest`" - ); - } source.functionsSourceV2 = await prepareFunctionsUpload(sourceDir, config); } if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) { diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index b87f0fc0318..df5165da412 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -9,7 +9,6 @@ import fetch from "node-fetch"; import { FirebaseError } from "../../../../error"; import { getRuntimeChoice } from "./parseRuntimeAndValidateSDK"; import { logger } from "../../../../logger"; -import { previews } from "../../../../previews"; import { logLabeledWarning } from "../../../../utils"; import * as backend from "../../backend"; import * as build from "../../build"; @@ -128,48 +127,45 @@ export class Delegate { config: backend.RuntimeConfigValues, env: backend.EnvironmentVariables ): Promise { - if (previews.functionsv2) { - if (!semver.valid(this.sdkVersion)) { - logger.debug( - `Could not parse firebase-functions version '${this.sdkVersion}' into semver. Falling back to parseTriggers.` - ); - return parseTriggers.discoverBackend( - this.projectId, - this.sourceDir, - this.runtime, - config, - env - ); - } - if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) { - logLabeledWarning( - "functions", - `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` + - `Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}` - ); - return parseTriggers.discoverBackend( - this.projectId, - this.sourceDir, - this.runtime, - config, - env - ); - } - let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime); - if (!discovered) { - const getPort = promisify(portfinder.getPort) as () => Promise; - const port = await getPort(); - const kill = await this.serve(port, env); - try { - discovered = await discovery.detectFromPort(port, this.projectId, this.runtime); - } finally { - await kill(); - } + if (!semver.valid(this.sdkVersion)) { + logger.debug( + `Could not parse firebase-functions version '${this.sdkVersion}' into semver. Falling back to parseTriggers.` + ); + return parseTriggers.discoverBackend( + this.projectId, + this.sourceDir, + this.runtime, + config, + env + ); + } + if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) { + logLabeledWarning( + "functions", + `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` + + `Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}` + ); + return parseTriggers.discoverBackend( + this.projectId, + this.sourceDir, + this.runtime, + config, + env + ); + } + let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime); + if (!discovered) { + const getPort = promisify(portfinder.getPort) as () => Promise; + const port = await getPort(); + const kill = await this.serve(port, env); + try { + discovered = await discovery.detectFromPort(port, this.projectId, this.runtime); + } finally { + await kill(); } - discovered.environmentVariables = env; - return discovered; } - return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env); + discovered.environmentVariables = env; + return discovered; } // eslint-disable-next-line require-await diff --git a/src/functions/functionslog.ts b/src/functions/functionslog.ts index eb7809b60b2..102f76b47d4 100644 --- a/src/functions/functionslog.ts +++ b/src/functions/functionslog.ts @@ -1,25 +1,20 @@ import { logger } from "../logger"; import { LogEntry } from "../gcp/cloudlogging"; -import { previews } from "../previews"; /** * The correct API filter to use when GCFv2 is enabled and/or we want specific function logs - * @param v2Enabled check if the user has the preview v2 enabled * @param functionList list of functions seperated by comma - * @returns the correct filter for use when calling the list api + * @return the correct filter for use when calling the list api */ export function getApiFilter(functionList?: string) { - const baseFilter = previews.functionsv2 - ? 'resource.type="cloud_function" OR ' + - '(resource.type="cloud_run_revision" AND ' + - 'labels."goog-managed-by"="cloudfunctions")' - : 'resource.type="cloud_function"'; + const baseFilter = + 'resource.type="cloud_function" OR ' + + '(resource.type="cloud_run_revision" AND ' + + 'labels."goog-managed-by"="cloudfunctions")'; if (functionList) { const apiFuncFilters = functionList.split(",").map((fn) => { - return previews.functionsv2 - ? `resource.labels.function_name="${fn}" ` + `OR resource.labels.service_name="${fn}"` - : `resource.labels.function_name="${fn}"`; + return `resource.labels.function_name="${fn}" ` + `OR resource.labels.service_name="${fn}"`; }); return baseFilter + `\n(${apiFuncFilters.join(" OR ")})`; } diff --git a/src/previews.ts b/src/previews.ts index f98e4bde65c..a58b35a2b5a 100644 --- a/src/previews.ts +++ b/src/previews.ts @@ -7,7 +7,6 @@ interface PreviewFlags { extdev: boolean; extensionsemulator: boolean; rtdbmanagement: boolean; - functionsv2: boolean; golang: boolean; deletegcfartifacts: boolean; artifactregistry: boolean; @@ -23,7 +22,6 @@ export const previews: PreviewFlags = { extdev: false, extensionsemulator: false, rtdbmanagement: false, - functionsv2: false, golang: false, deletegcfartifacts: false, artifactregistry: false, diff --git a/src/test/deploy/functions/backend.spec.ts b/src/test/deploy/functions/backend.spec.ts index 276736d0739..c881eaaf441 100644 --- a/src/test/deploy/functions/backend.spec.ts +++ b/src/test/deploy/functions/backend.spec.ts @@ -2,7 +2,6 @@ import { expect } from "chai"; import * as sinon from "sinon"; import { FirebaseError } from "../../../error"; -import { previews } from "../../../previews"; import * as args from "../../../deploy/functions/args"; import * as backend from "../../../deploy/functions/backend"; import * as gcf from "../../../gcp/cloudfunctions"; @@ -165,7 +164,6 @@ describe("Backend", () => { let getService: sinon.SinonStub; beforeEach(() => { - previews.functionsv2 = false; listAllFunctions = sinon.stub(gcf, "listAllFunctions").rejects("Unexpected call"); listAllFunctionsV2 = sinon.stub(gcfV2, "listAllFunctions").rejects("Unexpected v2 call"); logLabeledWarning = sinon.spy(utils, "logLabeledWarning"); @@ -202,6 +200,10 @@ describe("Backend", () => { ], unreachable: ["region"], }); + listAllFunctionsV2.onFirstCall().resolves({ + functions: [], + unreachable: [], + }); const firstBackend = await backend.existingBackend(context); const secondBackend = await backend.existingBackend(context); @@ -209,7 +211,7 @@ describe("Backend", () => { expect(firstBackend).to.deep.equal(secondBackend); expect(listAllFunctions).to.be.calledOnce; - expect(listAllFunctionsV2).to.not.be.called; + expect(listAllFunctionsV2).to.be.calledOnce; }); it("should translate functions", async () => { @@ -222,13 +224,16 @@ describe("Backend", () => { ], unreachable: [], }); + listAllFunctionsV2.onFirstCall().resolves({ + functions: [], + unreachable: [], + }); const have = await backend.existingBackend(newContext()); expect(have).to.deep.equal(backend.of({ ...ENDPOINT, httpsTrigger: {} })); }); it("should throw an error if v2 list api throws an error", async () => { - previews.functionsv2 = true; listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: [], @@ -243,7 +248,6 @@ describe("Backend", () => { }); it("should read v1 functions only when user is not allowlisted for v2", async () => { - previews.functionsv2 = true; listAllFunctions.onFirstCall().resolves({ functions: [ { @@ -263,7 +267,6 @@ describe("Backend", () => { }); it("should throw an error if v2 list api throws an error", async () => { - previews.functionsv2 = true; listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: [], @@ -278,7 +281,6 @@ describe("Backend", () => { }); it("should read v1 functions only when user is not allowlisted for v2", async () => { - previews.functionsv2 = true; listAllFunctions.onFirstCall().resolves({ functions: [ { @@ -298,7 +300,6 @@ describe("Backend", () => { }); it("should read v2 functions when enabled", async () => { - previews.functionsv2 = true; getService .withArgs(HAVE_CLOUD_FUNCTION_V2.serviceConfig.service!) .resolves(CLOUD_RUN_SERVICE); @@ -341,6 +342,10 @@ describe("Backend", () => { ], unreachable: [], }); + listAllFunctionsV2.onFirstCall().resolves({ + functions: [], + unreachable: [], + }); const have = await backend.existingBackend(newContext()); const want = backend.of({ ...ENDPOINT, @@ -367,20 +372,6 @@ describe("Backend", () => { functions: [], unreachable: [], }); - - await backend.checkAvailability(newContext(), backend.empty()); - - expect(listAllFunctions).to.have.been.called; - expect(listAllFunctionsV2).to.not.have.been.called; - expect(logLabeledWarning).to.not.have.been.called; - }); - - it("should do nothing when all regions are available and GCFv2 is enabled", async () => { - previews.functionsv2 = true; - listAllFunctions.onFirstCall().resolves({ - functions: [], - unreachable: [], - }); listAllFunctionsV2.onFirstCall().resolves({ functions: [], unreachable: [], @@ -393,21 +384,24 @@ describe("Backend", () => { expect(logLabeledWarning).to.not.have.been.called; }); - it("should warn if an unused backend is unavailable", async () => { + it("should warn if an unused GCFv1 backend is unavailable", async () => { listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: ["region"], }); + listAllFunctionsV2.resolves({ + functions: [], + unreachable: [], + }); await backend.checkAvailability(newContext(), backend.empty()); expect(listAllFunctions).to.have.been.called; - expect(listAllFunctionsV2).to.not.have.been.called; + expect(listAllFunctionsV2).to.have.been.called; expect(logLabeledWarning).to.have.been.called; }); it("should warn if an unused GCFv2 backend is unavailable", async () => { - previews.functionsv2 = true; listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: [], @@ -424,11 +418,15 @@ describe("Backend", () => { expect(logLabeledWarning).to.have.been.called; }); - it("should throw if a needed region is unavailable", async () => { + it("should throw if a needed GCFv1 region is unavailable", async () => { listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: ["region"], }); + listAllFunctionsV2.resolves({ + functions: [], + unreachable: [], + }); const want = backend.of({ ...ENDPOINT, httpsTrigger: {} }); await expect(backend.checkAvailability(newContext(), want)).to.eventually.be.rejectedWith( FirebaseError, @@ -437,7 +435,6 @@ describe("Backend", () => { }); it("should throw if a GCFv2 needed region is unavailable", async () => { - previews.functionsv2 = true; listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: [], @@ -459,7 +456,6 @@ describe("Backend", () => { }); it("Should only warn when deploying GCFv1 and GCFv2 is unavailable.", async () => { - previews.functionsv2 = true; listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: [], @@ -478,7 +474,6 @@ describe("Backend", () => { }); it("Should only warn when deploying GCFv2 and GCFv1 is unavailable.", async () => { - previews.functionsv2 = true; listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: ["us-central1"], diff --git a/src/test/functions/functionsLog.spec.ts b/src/test/functions/functionsLog.spec.ts index 44ba38e593f..673a1fb9a1b 100644 --- a/src/test/functions/functionsLog.spec.ts +++ b/src/test/functions/functionsLog.spec.ts @@ -6,13 +6,7 @@ import { previews } from "../../previews"; describe("functionsLog", () => { describe("getApiFilter", () => { - it("should return base api filter for v1 functions", () => { - previews.functionsv2 = false; - expect(functionsLog.getApiFilter(undefined)).to.eq('resource.type="cloud_function"'); - }); - it("should return base api filter for v1&v2 functions", () => { - previews.functionsv2 = true; expect(functionsLog.getApiFilter(undefined)).to.eq( 'resource.type="cloud_function" OR ' + '(resource.type="cloud_run_revision" AND ' + @@ -20,17 +14,7 @@ describe("functionsLog", () => { ); }); - it("should return list api filter for v1 functions", () => { - previews.functionsv2 = false; - expect(functionsLog.getApiFilter("fn1,fn2")).to.eq( - 'resource.type="cloud_function"\n' + - '(resource.labels.function_name="fn1" OR ' + - 'resource.labels.function_name="fn2")' - ); - }); - it("should return list api filter for v1&v2 functions", () => { - previews.functionsv2 = true; expect(functionsLog.getApiFilter("fn1,fn2")).to.eq( 'resource.type="cloud_function" OR ' + '(resource.type="cloud_run_revision" AND ' + From 534caea3e745a6151d1e1a4fa2ec0c2f35b54a20 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 6 May 2022 09:26:28 -0700 Subject: [PATCH 0304/1699] Resolve relative paths for local extensions (#4529) --- src/emulator/extensionsEmulator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index a2d3a018b46..fb397479ea6 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -99,7 +99,7 @@ export class ExtensionsEmulator implements EmulatorInstance { `Tried to emulate local extension at ${instance.localPath}, but it was missing required files.` ); } - return instance.localPath; + return path.resolve(instance.localPath); } else if (instance.ref) { const ref = toExtensionVersionRef(instance.ref); const cacheDir = From 6b571edce6d4260622a0aa22f2ad2bcd2e943421 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sat, 7 May 2022 02:24:16 +0900 Subject: [PATCH 0305/1699] Improve error message when parsing function source fails. (#4527) Previously, an invalid function source would show error message like this: ```bash $ firebase deploy --only functions i deploying functions ... Error: Failed to parse backend specification: - YAMLException incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line at line 1, column 65: ... function source: ReferenceError: aa is not defined ``` This is surprising - a YAMLException? A backend specification? Often times, the full error message would curt short, and debugging the issue required the user to carefully inspect the debug log. We improve the error message to the following: ```bash $ firebase deploy --only functions i deploying functions ... Error: Failed to load function definition from source: Failed to generate manifest from function source: ReferenceError: aa is not defined ``` We hid the YAML portion of the error and make sure to relay the full error message returned from the underlying Functions Control API (i.e. the serve responsible for loading and advertising the `functions.yaml` baked into the Functions SDK) --- src/deploy/functions/runtimes/discovery/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/deploy/functions/runtimes/discovery/index.ts b/src/deploy/functions/runtimes/discovery/index.ts index b9e83292e84..a01653158a0 100644 --- a/src/deploy/functions/runtimes/discovery/index.ts +++ b/src/deploy/functions/runtimes/discovery/index.ts @@ -60,7 +60,7 @@ export async function detectFromPort( port: number, project: string, runtime: runtimes.Runtime, - timeout: number = 30_000 /* 30s to boot up */ + timeout = 30_000 /* 30s to boot up */ ): Promise { // The result type of fetch isn't exported let res: { text(): Promise }; @@ -90,7 +90,8 @@ export async function detectFromPort( try { parsed = yaml.load(text); } catch (err: any) { - throw new FirebaseError("Failed to parse backend specification", { children: [err] }); + logger.debug("Failed to parse functions.yaml", err); + throw new FirebaseError(`Failed to load function definition from source: ${text}`); } return yamlToBackend(parsed, project, api.functionsDefaultRegion, runtime); From 68ad6662d535af775c20952704796bcd7d048ce1 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sat, 7 May 2022 03:15:17 +0900 Subject: [PATCH 0306/1699] Set `enableCors` debug feature when emulating CF3 functions. (#4528) Accompanies https://github.com/firebase/firebase-functions/pull/1099. With this change, users using the supported version of the Firebase Functions SDK will be able to bypass existing/default cors settings to call the emulated HTTP/callable v2 functions. --- src/emulator/functionsEmulator.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index fe31ec730e4..c911760eeb0 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -1083,7 +1083,10 @@ export class FunctionsEmulator implements EmulatorInstance { envs.FUNCTIONS_EMULATOR = "true"; envs.TZ = "UTC"; // Fixes https://github.com/firebase/firebase-tools/issues/2253 envs.FIREBASE_DEBUG_MODE = "true"; - envs.FIREBASE_DEBUG_FEATURES = JSON.stringify({ skipTokenVerification: true }); + envs.FIREBASE_DEBUG_FEATURES = JSON.stringify({ + skipTokenVerification: true, + enableCors: true, + }); // TODO(danielylee): Support timeouts. Temporarily dropping the feature until we finish refactoring. // Make firebase-admin point at the Firestore emulator From aa06a279d4fc32479c3d9fc37cf2bea519747da1 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 6 May 2022 14:26:15 -0700 Subject: [PATCH 0307/1699] Removing extensions-emulator preview flag (#4484) * Removing extensions-emulator preview flag * fix test * Adding changelog entry * formats * Pin to emulator UI v1.7.0 --- CHANGELOG.md | 1 + scripts/extensions-emulator-tests/run.sh | 1 - src/emulator/controller.ts | 7 ++--- src/emulator/downloadableEmulators.ts | 33 ++++++------------------ src/previews.ts | 2 -- 5 files changed, 11 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..007aaa12506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Adds Firebase Extensions emulator. Learn more about how to test Extensions in the Emulator Suite here: https://firebase.google.com/docs/extensions/manifest. diff --git a/scripts/extensions-emulator-tests/run.sh b/scripts/extensions-emulator-tests/run.sh index 03311a27b14..b399bbc04d8 100755 --- a/scripts/extensions-emulator-tests/run.sh +++ b/scripts/extensions-emulator-tests/run.sh @@ -8,5 +8,4 @@ source scripts/set-default-credentials.sh npm install ) -firebase --open-sesame extensionsemulator mocha scripts/extensions-emulator-tests/tests.ts diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index d9a7253e9e9..d2f86809599 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -44,7 +44,6 @@ import { getProjectDefaultAccount } from "../auth"; import { Options } from "../options"; import { ParsedTriggerDefinition } from "./functionsEmulatorShared"; import { ExtensionsEmulator } from "./extensionsEmulator"; -import { previews } from "../previews"; import { normalizeAndValidate } from "../functions/projectConfig"; import { requiresJava } from "./downloadableEmulators"; @@ -193,9 +192,7 @@ export async function cleanShutdown(): Promise { */ export function filterEmulatorTargets(options: any): Emulators[] { let targets = [...ALL_SERVICE_EMULATORS]; - if (previews.extensionsemulator) { - targets.push(Emulators.EXTENSIONS); - } + targets.push(Emulators.EXTENSIONS); targets = targets.filter((e) => { return options.config.has(e) || options.config.has(`emulators.${e}`); @@ -460,7 +457,7 @@ export async function startAll( } } - if (shouldStart(options, Emulators.EXTENSIONS) && previews.extensionsemulator) { + if (shouldStart(options, Emulators.EXTENSIONS)) { const projectNumber = Constants.isDemoProject(projectId) ? Constants.FAKE_PROJECT_NUMBER : await needProjectNumber(options); diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index eb934ff8181..3ce92ff2fbf 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -62,24 +62,7 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe namePrefix: "cloud-storage-rules-emulator", }, }, - ui: previews.extensionsemulator - ? { - version: "EXTENSIONS", - downloadPath: path.join(CACHE_DIR, "ui-vEXTENSIONS.zip"), - unzipDir: path.join(CACHE_DIR, "ui-vEXTENSIONS"), - binaryPath: path.join(CACHE_DIR, "ui-vEXTENSIONS", "server.bundle.js"), - opts: { - cacheDir: CACHE_DIR, - remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vEXTENSIONS.zip", - expectedSize: -1, - expectedChecksum: "", - skipCache: true, - skipChecksumAndSize: true, - namePrefix: "ui", - }, - } - : previews.emulatoruisnapshot + ui: previews.emulatoruisnapshot ? { version: "SNAPSHOT", downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"), @@ -97,15 +80,15 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe }, } : { - version: "1.6.6", - downloadPath: path.join(CACHE_DIR, "ui-v1.6.6.zip"), - unzipDir: path.join(CACHE_DIR, "ui-v1.6.6"), - binaryPath: path.join(CACHE_DIR, "ui-v1.6.6", "server.bundle.js"), + version: "1.7.0", + downloadPath: path.join(CACHE_DIR, "ui-v1.7.0.zip"), + unzipDir: path.join(CACHE_DIR, "ui-v1.7.0"), + binaryPath: path.join(CACHE_DIR, "ui-v1.7.0", "server.bundle.js"), opts: { cacheDir: CACHE_DIR, - remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.6.zip", - expectedSize: 3817247, - expectedChecksum: "c80a3f0ae1e3f682ace0a18a9cdd2861", + remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.7.0.zip", + expectedSize: 4053708, + expectedChecksum: "aea9ae19091df5974a88a8847aaf127c", namePrefix: "ui", }, }, diff --git a/src/previews.ts b/src/previews.ts index a58b35a2b5a..4d07e7bfcb1 100644 --- a/src/previews.ts +++ b/src/previews.ts @@ -5,7 +5,6 @@ interface PreviewFlags { rtdbrules: boolean; ext: boolean; extdev: boolean; - extensionsemulator: boolean; rtdbmanagement: boolean; golang: boolean; deletegcfartifacts: boolean; @@ -20,7 +19,6 @@ export const previews: PreviewFlags = { rtdbrules: false, ext: false, extdev: false, - extensionsemulator: false, rtdbmanagement: false, golang: false, deletegcfartifacts: false, From ad99ec653a6e8486f28ef97bcc9471a83dc0268b Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 6 May 2022 14:56:57 -0700 Subject: [PATCH 0308/1699] Fix outdated package.json templates (#4531) * Remove firebase-functions-test dependency * Bump all the dependencies --- templates/extensions/javascript/package.lint.json | 10 +++++----- templates/extensions/javascript/package.nolint.json | 6 +++--- templates/extensions/typescript/package.lint.json | 12 ++++++------ .../init/functions/typescript/package.nolint.json | 9 ++++----- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/templates/extensions/javascript/package.lint.json b/templates/extensions/javascript/package.lint.json index b023cee30b0..c5e9758d394 100644 --- a/templates/extensions/javascript/package.lint.json +++ b/templates/extensions/javascript/package.lint.json @@ -3,15 +3,15 @@ "description": "Greet the world", "main": "index.js", "dependencies": { - "firebase-admin": "^9.8.0", - "firebase-functions": "^3.14.1" + "firebase-admin": "^10.2.0", + "firebase-functions": "^3.21.0" }, "devDependencies": { - "eslint": "^4.13.1", - "eslint-plugin-promise": "^3.6.0" + "eslint": "^8.15.1", + "eslint-plugin-promise": "^6.0.0" }, "scripts": { "lint": "./node_modules/.bin/eslint --max-warnings=0 .." }, "private": true -} +} \ No newline at end of file diff --git a/templates/extensions/javascript/package.nolint.json b/templates/extensions/javascript/package.nolint.json index 7624b2f57e3..75918fd45a7 100644 --- a/templates/extensions/javascript/package.nolint.json +++ b/templates/extensions/javascript/package.nolint.json @@ -3,8 +3,8 @@ "description": "Greet the world", "main": "index.js", "dependencies": { - "firebase-admin": "^9.8.0", - "firebase-functions": "^3.14.1" + "firebase-admin": "^10.2.0", + "firebase-functions": "^3.21.0" }, "private": true -} +} \ No newline at end of file diff --git a/templates/extensions/typescript/package.lint.json b/templates/extensions/typescript/package.lint.json index 1dcfbe14840..3bb412dbc11 100644 --- a/templates/extensions/typescript/package.lint.json +++ b/templates/extensions/typescript/package.lint.json @@ -7,13 +7,13 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^9.8.0", - "firebase-functions": "^3.14.1" + "firebase-admin": "^10.2.0", + "firebase-functions": "^3.21.0" }, "devDependencies": { - "eslint": "^7.6.0", - "eslint-plugin-import": "^2.22.0", - "typescript": "^3.8.0" + "eslint": "^8.15.1", + "eslint-plugin-import": "^2.26.0", + "typescript": "^4.6.4" }, "private": true -} +} \ No newline at end of file diff --git a/templates/init/functions/typescript/package.nolint.json b/templates/init/functions/typescript/package.nolint.json index 1b96c436bb1..0b6edcb5ebe 100644 --- a/templates/init/functions/typescript/package.nolint.json +++ b/templates/init/functions/typescript/package.nolint.json @@ -14,12 +14,11 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^10.0.2", - "firebase-functions": "^3.18.0" + "firebase-admin": "^10.2.0", + "firebase-functions": "^3.21.0" }, "devDependencies": { - "typescript": "^4.5.4", - "firebase-functions-test": "^0.2.0" + "typescript": "^4.6.4" }, "private": true -} +} \ No newline at end of file From f803e40cd16187deb796ebd86f5df836a7840b08 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 6 May 2022 15:37:24 -0700 Subject: [PATCH 0309/1699] Inlined.cpu fixes (#4530) * Add extra validation for CPU x memory constraints * Force default memory to avoid MB -> MiB bugs * Unbreak unit tests --- src/deploy/functions/validate.ts | 147 +++++++++++++----- src/gcp/cloudfunctionsv2.ts | 17 +- src/test/deploy/functions/validate.spec.ts | 171 +++++++++++++++++---- src/test/gcp/cloudfunctionsv2.spec.ts | 4 +- 4 files changed, 254 insertions(+), 85 deletions(-) diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 01b6784cb38..7267711cc06 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -10,6 +10,24 @@ import * as utils from "../../utils"; import * as secrets from "../../functions/secrets"; import { serviceForEndpoint } from "./services"; +function matchingIds( + endpoints: backend.Endpoint[], + filter: (endpoint: backend.Endpoint) => boolean +): string { + return endpoints + .filter(filter) + .map((endpoint) => endpoint.id) + .join(","); +} + +const mem = (endpoint: backend.Endpoint): backend.MemoryOptions => + endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; +const cpu = (endpoint: backend.Endpoint): number => { + return endpoint.cpu === "gcf_gen1" + ? backend.memoryToGen1Cpu(mem(endpoint)) + : endpoint.cpu ?? backend.memoryToGen2Cpu(mem(endpoint)); +}; + /** Validate that the configuration for endpoints are valid. */ export function endpointsAreValid(wantBackend: backend.Backend): void { const endpoints = backend.allEndpoints(wantBackend); @@ -19,63 +37,110 @@ export function endpointsAreValid(wantBackend: backend.Backend): void { } // Our SDK doesn't let people articulate this, but it's theoretically possible in the manifest syntax. - const gcfV1WithConcurrency = endpoints - .filter((endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1") - .map((endpoint) => endpoint.id); + const gcfV1WithConcurrency = matchingIds( + endpoints, + (endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1" + ); if (gcfV1WithConcurrency.length) { - const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency.join( - "," - )} because they are GCF gen 1`; + const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency} because they are GCF gen 1`; throw new FirebaseError(msg); } - const tooSmallForConcurrency = endpoints - .filter((endpoint) => { - if ((endpoint.concurrency || 1) === 1) { - return false; - } - return (endpoint.cpu as number) < backend.MIN_CPU_FOR_CONCURRENCY; - }) - .map((endpoint) => endpoint.id); + const tooSmallForConcurrency = matchingIds(endpoints, (endpoint) => { + if ((endpoint.concurrency || 1) === 1) { + return false; + } + return cpu(endpoint) < backend.MIN_CPU_FOR_CONCURRENCY; + }); if (tooSmallForConcurrency.length) { const msg = "The following functions are configured to allow concurrent " + "execution and less than one full CPU. This is not supported: " + - tooSmallForConcurrency.join(","); + tooSmallForConcurrency; throw new FirebaseError(msg); } + cpuConfigIsValid(endpoints); +} - const gcfV1WithCPU = endpoints - .filter((endpoint) => endpoint.platform === "gcfv1" && typeof endpoint["cpu"] !== "undefined") - .map((endpoint) => endpoint.id); +/** + * Validate that endpoints have valid CPU configuration. + * Enforces https://cloud.google.com/run/docs/configuring/cpu. + */ +export function cpuConfigIsValid(endpoints: backend.Endpoint[]): void { + const gcfV1WithCPU = matchingIds( + endpoints, + (endpoint) => endpoint.platform === "gcfv1" && typeof endpoint["cpu"] !== "undefined" + ); if (gcfV1WithCPU.length) { - const msg = `Cannot set CPU on the functions ${gcfV1WithCPU.join( - "," - )} because they are GCF gen 1`; + const msg = `Cannot set CPU on the functions ${gcfV1WithCPU} because they are GCF gen 1`; throw new FirebaseError(msg); } - const invalidCPU = endpoints - .filter((endpoint) => { - if (typeof endpoint.cpu === "undefined") { - return false; - } - if (endpoint.cpu === "gcf_gen1") { - return false; - } - const cpu: number = endpoint.cpu; - // All fractional CPU is allowed apparently? - if (cpu < 1) { - return false; - } - // But whole CPU is limited to fixed sizes - return ![1, 2, 4, 6, 8].includes(cpu); - }) - .map((endpoint) => endpoint.id); + const invalidCPU = matchingIds(endpoints, (endpoint) => { + const c: number = cpu(endpoint); + if (c < 0.08) { + return true; + } + if (c < 1) { + return false; + } + // But whole CPU is limited to fixed sizes + return ![1, 2, 4, 6, 8].includes(c); + }); if (invalidCPU.length) { - const msg = `The following functions have invalid CPU settings ${invalidCPU.join( - "," - )}. Valid CPU options are (0, 1], 2, 4, 6, 8, or "gcf_gen1"`; + const msg = `The following functions have invalid CPU settings ${invalidCPU}. Valid CPU options are (0.08, 1], 2, 4, 6, 8, or "gcf_gen1"`; + throw new FirebaseError(msg); + } + + const smallCPURegions = ["australia-southeast2", "asia-northeast3", "asia-south2"]; + const tooBigCPUForRegion = matchingIds( + endpoints, + (endpoint) => smallCPURegions.includes(endpoint.region) && cpu(endpoint) > 4 + ); + if (tooBigCPUForRegion) { + const msg = `The functions ${tooBigCPUForRegion} have > 4 CPU in a region that supports a maximum 4 CPU`; + throw new FirebaseError(msg); + } + + const tooSmallCPUSmall = matchingIds( + endpoints, + (endpoint) => mem(endpoint) > 512 && cpu(endpoint) < 0.5 + ); + if (tooSmallCPUSmall) { + const msg = `The functions ${tooSmallCPUSmall} have too little CPU for their memory allocation. A minimum of 0.5 CPU is needed to set a memory limit greater than 512MiB`; + throw new FirebaseError(msg); + } + + const tooSmallCPUBig = matchingIds( + endpoints, + (endpoint) => mem(endpoint) > 1024 && cpu(endpoint) < 1 + ); + if (tooSmallCPUBig) { + const msg = `The functions ${tooSmallCPUSmall} have too little CPU for their memory allocation. A minimum of 1 CPU is needed to set a memory limit greater than 1GiB`; + throw new FirebaseError(msg); + } + const tooSmallMemory4CPU = matchingIds( + endpoints, + (endpoint) => cpu(endpoint) === 4 && mem(endpoint) < 2 << 10 + ); + if (tooSmallMemory4CPU) { + const msg = `The functions ${tooSmallMemory4CPU} have too little memory for their CPU. Functions with 4 CPU require at least 2GiB`; + throw new FirebaseError(msg); + } + const tooSmallMemory6CPU = matchingIds( + endpoints, + (endpoint) => cpu(endpoint) === 6 && mem(endpoint) < 3 << 10 + ); + if (tooSmallMemory6CPU) { + const msg = `The functions ${tooSmallMemory6CPU} have too little memory for their CPU. Functions with 6 CPU require at least 3GiB`; + throw new FirebaseError(msg); + } + const tooSmallMemory8CPU = matchingIds( + endpoints, + (endpoint) => cpu(endpoint) === 8 && mem(endpoint) < 4 << 10 + ); + if (tooSmallMemory8CPU) { + const msg = `The functions ${tooSmallMemory8CPU} have too little memory for their CPU. Functions with 8 CPU require at least 4GiB`; throw new FirebaseError(msg); } } diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index a3addfaee85..4dd48f20740 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -454,19 +454,10 @@ export function functionFromEndpoint(endpoint: backend.Endpoint, source: Storage "ingressSettings", "timeoutSeconds" ); - proto.renameIfPresent( - gcfFunction.serviceConfig, - endpoint, - "availableMemory", - "availableMemoryMb", - (mb: number) => { - if (mb > 1024) { - return `${mb / 1024}Gi`; - } else { - return `${mb}Mi`; - } - } - ); + // Memory must be set because the default value of GCF gen 2 is Megabytes and + // we use mebibytes + const mem: number = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; + gcfFunction.serviceConfig.availableMemory = mem > 1024 ? `${mem / 1024}Gi` : `${mem}Mi`; proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances"); proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances"); diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index 4fca66311a1..b5a1c52b362 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -152,6 +152,147 @@ describe("validate", () => { ); }); + for (const [mem, cpu] of [ + [undefined, undefined], + [undefined, "gcf_gen1"], + [128, 0.1], + [512, 0.5], + [512, 1], + [512, 2], + [2048, 4], + [4096, 6], + [4096, 8], + ] as const) { + it(`does not throw for valid CPU ${cpu ?? "undefined"}`, () => { + const want = backend.of({ + ...ENDPOINT_BASE, + platform: "gcfv2", + cpu, + availableMemoryMb: mem, + }); + expect(() => validate.endpointsAreValid(want)).to.not.throw(); + }); + } + + it("throws for gcfv1 with CPU", () => { + const want = backend.of({ + ...ENDPOINT_BASE, + platform: "gcfv1", + cpu: 1, + }); + expect(() => validate.endpointsAreValid(want)).to.throw(); + }); + + for (const region of ["australia-southeast2", "asia-northeast3", "asia-south2"]) { + it("disallows large CPU in low-CPU region" + region, () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + platform: "gcfv2", + region, + cpu: 6, + availableMemoryMb: 2048, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( + /have > 4 CPU in a region that supports a maximum 4 CPU/ + ); + }); + } + + for (const [mem, cpu] of [ + [128, 0.08], + [512, 0.5], + [1024, 1], + [2048, 2], + [2048, 4], + [4096, 6], + [4096, 8], + [1024, "gcf_gen1"], + ] as const) { + it(`allows valid CPU size ${cpu}`, () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + platform: "gcfv2", + region: "us-west1", + cpu: cpu, + availableMemoryMb: mem, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw(); + }); + } + + for (const [mem, cpu] of [ + // < 0.08 + [128, 0.07], + // fractional > 1 + [512, 1.1], + // odd + [1024, 3], + [2048, 5], + [2048, 7], + // too large + [4096, 9], + ] as const) { + it(`disallows CPU size ${cpu}`, () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + platform: "gcfv2", + cpu, + availableMemoryMb: mem, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( + /Valid CPU options are \(0.08, 1], 2, 4, 6, 8, or "gcf_gen1"/ + ); + }); + } + + it("disallows tiny CPU with large memory", () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + platform: "gcfv2", + cpu: 0.49, + availableMemoryMb: 1024, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( + /A minimum of 0.5 CPU is needed to set a memory limit greater than 512MiB/ + ); + }); + + it("disallows small CPU with huge memory", () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + platform: "gcfv2", + cpu: 0.99, + availableMemoryMb: 2048, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( + /A minimum of 1 CPU is needed to set a memory limit greater than 1GiB/ + ); + }); + + for (const [mem, cpu] of [ + [1024, 4], + [2048, 6], + [2048, 8], + ] as const) { + it(`enforces minimum memory for ${cpu} CPU`, () => { + const ep: backend.Endpoint = { + ...ENDPOINT_BASE, + platform: "gcfv2", + cpu, + availableMemoryMb: mem, + }; + + expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( + /too little memory for their CPU/ + ); + }); + } + it("Allows endpoints with no mem and no concurrency", () => { expect(() => validate.endpointsAreValid(backend.of(ENDPOINT_BASE))).to.not.throw(); }); @@ -315,36 +456,6 @@ describe("validate", () => { expect(() => validate.endpointsAreValid(want)).to.not.throw(); }); - - for (const cpu of [undefined, "gcf_gen1", 0.1, 0.5, 1, 2, 4, 6, 8] as const) { - it(`does not throw for valid CPU ${cpu ?? "undefined"}`, () => { - const want = backend.of({ - ...ENDPOINT_BASE, - platform: "gcfv2", - cpu, - }); - expect(() => validate.endpointsAreValid(want)).to.not.throw(); - }); - } - - it("throws for gcfv1 with CPU", () => { - const want = backend.of({ - ...ENDPOINT_BASE, - platform: "gcfv1", - cpu: 1, - }); - expect(() => validate.endpointsAreValid(want)).to.throw(); - }); - - it("throws for invalid CPU", () => { - const want = backend.of({ - ...ENDPOINT_BASE, - platform: "gcfv2", - // Fractional CPU is only valid for <1 - cpu: 1.5, - }); - expect(() => validate.endpointsAreValid(want)).to.throw(); - }); }); describe("endpointsAreUnqiue", () => { diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index e9f73516684..eb706a7118f 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -38,7 +38,9 @@ describe("cloudfunctionsv2", () => { }, environmentVariables: {}, }, - serviceConfig: {}, + serviceConfig: { + availableMemory: `${backend.DEFAULT_MEMORY}Mi`, + }, }; const RUN_URI = "https://id-nonce-region-project.run.app"; From cca97ca3545af6be8f7dc35c68fa4c0ad86cf3db Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 9 May 2022 15:28:54 -0700 Subject: [PATCH 0310/1699] Fixing broken tests on next (#4535) --- src/extensions/extensionsHelper.ts | 2 +- src/test/extensions/extensionsHelper.spec.ts | 30 +++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index cc7845e19cd..e6ebeb5da50 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -792,5 +792,5 @@ export async function canonicalizeRefInput(extensionName: string): Promise { describe("substituteParams", () => { @@ -881,26 +882,35 @@ describe("extensionsHelper", () => { }); describe(`${canonicalizeRefInput.name}`, () => { - it("should do nothing to a valid ref", () => { - expect(canonicalizeRefInput("firebase/bigquery-export@10.1.1")).to.equal( + let resolveVersionStub: sinon.SinonStub; + beforeEach(() => { + resolveVersionStub = sinon.stub(planner, "resolveVersion").resolves("10.1.1"); + }); + afterEach(() => { + resolveVersionStub.restore(); + }); + it("should do nothing to a valid ref", async () => { + expect(await canonicalizeRefInput("firebase/bigquery-export@10.1.1")).to.equal( "firebase/bigquery-export@10.1.1" ); }); - it("should infer latest version", () => { - expect(canonicalizeRefInput("firebase/bigquery-export")).to.equal( - "firebase/bigquery-export@latest" + it("should infer latest version", async () => { + expect(await canonicalizeRefInput("firebase/bigquery-export")).to.equal( + "firebase/bigquery-export@10.1.1" ); }); - it("should infer publisher name as firebase", () => { - expect(canonicalizeRefInput("firebase/bigquery-export")).to.equal( - "firebase/bigquery-export@latest" + it("should infer publisher name as firebase", async () => { + expect(await canonicalizeRefInput("firebase/bigquery-export")).to.equal( + "firebase/bigquery-export@10.1.1" ); }); - it("should infer publisher name as firebase and also infer latest as version", () => { - expect(canonicalizeRefInput("bigquery-export")).to.equal("firebase/bigquery-export@latest"); + it("should infer publisher name as firebase and also infer latest as version", async () => { + expect(await canonicalizeRefInput("bigquery-export")).to.equal( + "firebase/bigquery-export@10.1.1" + ); }); }); }); From 5c768315e434dcaa1bf070a44fad8fba86b6d591 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 10 May 2022 10:51:15 -0700 Subject: [PATCH 0311/1699] Adding CHANGELOG entry for cf3v2 (#4537) * Adding CHANGELOG entry for cf3v2 * gen 2 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 007aaa12506..f0788e4642c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Adds Firebase Extensions emulator. Learn more about how to test Extensions in the Emulator Suite here: https://firebase.google.com/docs/extensions/manifest. +- Adds support for Cloud Functions for Firebase gen 2. From 745e16769f01a7f4da3d77f26cb77eb4a5cd4159 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 May 2022 16:30:41 -0400 Subject: [PATCH 0312/1699] More firebase-frameworks work (#4463) * Work with emulators:start * Add dev mode flag * Dev flag not actually needed * Move entry into firebase-tools * Cleanup * Bump dep * fix missing import Co-authored-by: Bryan Kendall --- npm-shrinkwrap.json | 108 +++++++++++++++++++-- package.json | 2 +- src/deploy/index.ts | 3 +- src/emulator/controller.ts | 9 ++ src/frameworks/index.ts | 123 ++++++++++++++++++++++++ src/hosting/normalizedHostingConfigs.ts | 8 ++ src/serve/index.ts | 7 +- 7 files changed, 246 insertions(+), 14 deletions(-) create mode 100644 src/frameworks/index.ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index edadccaa494..4ada9b81da8 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -30,7 +30,7 @@ "exit-code": "^1.0.2", "express": "^4.16.4", "filesize": "^6.1.0", - "firebase-frameworks": "^0.3.0", + "firebase-frameworks": "^0.4.0", "fs-extra": "^5.0.0", "glob": "^7.1.2", "google-auth-library": "^7.11.0", @@ -6059,18 +6059,67 @@ } }, "node_modules/firebase-frameworks": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.3.0.tgz", - "integrity": "sha512-Zxtx5WsD8ZZdItIeDjjpM+JgaIWDdwBujmLYLKf2Ou6onyRsd8bNRrnVsqrnq4S3FN9TcNYliXdwMu7AwYdW7Q==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.4.0.tgz", + "integrity": "sha512-Seu+1dKNo3AacMrOHb1V0F41DfCKiM6gW4Go/34z78WtuBkzKNSUOUI+w8XCH7A96QGZRbNbGwt33BiSXEb2xQ==", "dependencies": { + "fs-extra": "^10.1.0", + "jsonc-parser": "^3.0.0", + "semver": "^7.3.7", "tslib": "^2.3.1" } }, + "node_modules/firebase-frameworks/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/firebase-frameworks/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/firebase-frameworks/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/firebase-frameworks/node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, + "node_modules/firebase-frameworks/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/firebase-functions": { "version": "3.18.1", "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.18.1.tgz", @@ -7945,6 +7994,11 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -18400,17 +18454,52 @@ } }, "firebase-frameworks": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.3.0.tgz", - "integrity": "sha512-Zxtx5WsD8ZZdItIeDjjpM+JgaIWDdwBujmLYLKf2Ou6onyRsd8bNRrnVsqrnq4S3FN9TcNYliXdwMu7AwYdW7Q==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.4.0.tgz", + "integrity": "sha512-Seu+1dKNo3AacMrOHb1V0F41DfCKiM6gW4Go/34z78WtuBkzKNSUOUI+w8XCH7A96QGZRbNbGwt33BiSXEb2xQ==", "requires": { + "fs-extra": "^10.1.0", + "jsonc-parser": "^3.0.0", + "semver": "^7.3.7", "tslib": "^2.3.1" }, "dependencies": { + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" } } }, @@ -19883,6 +19972,11 @@ "minimist": "^1.2.5" } }, + "jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", diff --git a/package.json b/package.json index d52686d01f2..954f0c2373f 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "exit-code": "^1.0.2", "express": "^4.16.4", "filesize": "^6.1.0", - "firebase-frameworks": "^0.3.0", + "firebase-frameworks": "^0.4.0", "fs-extra": "^5.0.0", "glob": "^7.1.2", "google-auth-library": "^7.11.0", diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 24a9ceb969e..fc6a88b821c 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -15,6 +15,7 @@ import * as FunctionsTarget from "./functions"; import * as StorageTarget from "./storage"; import * as RemoteConfigTarget from "./remoteconfig"; import * as ExtensionsTarget from "./extensions"; +import { prepareFrameworks } from "../frameworks"; const TARGETS = { hosting: HostingTarget, @@ -58,7 +59,7 @@ export const deploy = async function ( if (previews.frameworkawareness && targetNames.includes("hosting")) { const config = options.config.get("hosting"); if (Array.isArray(config) ? config.some((it) => it.source) : config.source) { - await require("firebase-frameworks").prepare(targetNames, context, options); + await prepareFrameworks(targetNames, context, options); } } diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index d2f86809599..6b270e4b3be 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -46,6 +46,8 @@ import { ParsedTriggerDefinition } from "./functionsEmulatorShared"; import { ExtensionsEmulator } from "./extensionsEmulator"; import { normalizeAndValidate } from "../functions/projectConfig"; import { requiresJava } from "./downloadableEmulators"; +import { prepareFrameworks } from "../frameworks"; +import { previews } from "../previews"; const START_LOGGING_EMULATOR = utils.envOverride( "START_LOGGING_EMULATOR", @@ -402,6 +404,13 @@ export async function startAll( } } + if (previews.frameworkawareness) { + const config = options.config.get("hosting"); + if (Array.isArray(config) ? config.some((it) => it.source) : config.source) { + await prepareFrameworks(targets, options, options); + } + } + if (shouldStart(options, Emulators.HUB)) { const hubAddr = await getAndCheckAddress(Emulators.HUB, options); const hub = new EmulatorHub({ projectId, ...hubAddr }); diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts new file mode 100644 index 00000000000..814e06708c1 --- /dev/null +++ b/src/frameworks/index.ts @@ -0,0 +1,123 @@ +import { join } from "path"; +import { exit } from "process"; + +import { needProjectId } from "../projectUtils"; +import { normalizedHostingConfigs } from "../hosting/normalizedHostingConfigs"; +import { listSites, Site } from "../hosting/api"; +import { getAppConfig, AppPlatform } from "../management/apps"; +import { promises as fsPromises } from "fs"; +import { promptOnce } from "../prompt"; + +const { writeFile } = fsPromises; + +export const shortSiteName = (site?: Site) => site?.name && site.name.split("/").pop(); + +export const prepareFrameworks = async (targetNames: string[], context: any, options: any) => { + const project = needProjectId(context); + // options.site is not present when emulated. We could call requireHostingSite but IAM permissions haven't + // been booted up (at this point) and we may be offline, so just use projectId. Most of the time + // the default site is named the same as the project & for frameworks this is only used for naming the + // function... unless you're using authenticated server-context TODO explore the implication here. + const configs = normalizedHostingConfigs({ site: project, ...options }, { resolveTargets: true }); + options.normalizedHostingConfigs = configs; + if (configs.length === 0) return; + for (const config of configs) { + const { source, site, public: publicDir } = config; + if (!source) continue; + const dist = join(".firebase", site); + const hostingDist = join(dist, "hosting"); + const functionsDist = join(dist, "functions"); + if (publicDir) + throw new Error(`hosting.public and hosting.source cannot both be set in firebase.json`); + const getProjectPath = (...args: string[]) => join(process.cwd(), source, ...args); + const functionName = `ssr${site.replace(/-/g, "")}`; + const { build } = require("firebase-frameworks/tools"); + const { usingCloudFunctions, rewrites, redirects, headers, usesFirebaseConfig } = await build( + { + dist, + project, + site, + function: { + name: functionName, + region: "us-central1", + }, + }, + getProjectPath + ); + config.public = hostingDist; + if (usingCloudFunctions) { + if (context.hostingChannel) { + // TODO move to prompts + const message = + "Cannot preview changes to the backend, you will only see changes to the static content on this channel."; + if (!options.nonInteractive) { + const continueDeploy = await promptOnce({ + type: "confirm", + default: true, + message: `${message} Would you like to continue with the deploy?`, + }); + if (!continueDeploy) exit(1); + } else { + console.error(message); + } + } else { + const functionConfig = { + source: functionsDist, + codebase: `firebase-frameworks-${site}`, + }; + if (targetNames.includes("functions")) { + const combinedFunctionsConfig = [functionConfig].concat( + options.config.get("functions") || [] + ); + options.config.set("functions", combinedFunctionsConfig); + } else { + targetNames.unshift("functions"); + options.config.set("functions", functionConfig); + } + } + + config.rewrites = [ + ...(config.rewrites || []), + ...rewrites, + { + source: "**", + function: functionName, + }, + ]; + + let firebaseProjectConfig = null; + if (usesFirebaseConfig) { + const sites = await listSites(project); + const selectedSite = sites.find((it) => shortSiteName(it) === site); + if (selectedSite) { + const { appId } = selectedSite; + if (appId) { + firebaseProjectConfig = await getAppConfig(appId, AppPlatform.WEB); + } else { + console.warn( + `No Firebase app associated with site ${site}, unable to provide authenticated server context` + ); + } + } + } + writeFile( + join(functionsDist, ".env"), + `FRAMEWORKS_FIREBASE_PROJECT_CONFIG="${JSON.stringify(firebaseProjectConfig).replace( + /"/g, + '\\"' + )}"` + ); + } else { + config.rewrites = [ + ...(config.rewrites || []), + ...rewrites, + { + source: "**", + destination: "/index.html", + }, + ]; + } + config.redirects = [...(config.redirects || []), ...redirects]; + config.headers = [...(config.headers || []), ...headers]; + } +}; diff --git a/src/hosting/normalizedHostingConfigs.ts b/src/hosting/normalizedHostingConfigs.ts index f2833e06694..59d9a2ee701 100644 --- a/src/hosting/normalizedHostingConfigs.ts +++ b/src/hosting/normalizedHostingConfigs.ts @@ -4,8 +4,13 @@ import { cloneDeep } from "lodash"; import { FirebaseError } from "../error"; interface HostingConfig { + source?: string; + public?: string; site: string; target: string; + rewrites?: any[]; + redirects?: any[]; + headers?: any[]; } function filterOnly(configs: HostingConfig[], onlyString: string): HostingConfig[] { @@ -90,6 +95,9 @@ export function normalizedHostingConfigs( cmdOptions: any, // eslint-disable-line @typescript-eslint/no-explicit-any options: { resolveTargets?: boolean } = {} ): HostingConfig[] { + // First see if there's a momoized copy on the options, from frameworks + const normalizedHostingConfigs = cmdOptions.normalizedHostingConfigs; + if (normalizedHostingConfigs) return normalizedHostingConfigs; let configs = cloneDeep(cmdOptions.config.get("hosting")); if (!configs) { return []; diff --git a/src/serve/index.ts b/src/serve/index.ts index 9c853779735..703be9205ba 100644 --- a/src/serve/index.ts +++ b/src/serve/index.ts @@ -1,6 +1,7 @@ import { EmulatorServer } from "../emulator/emulatorServer"; import * as _ from "lodash"; import { logger } from "../logger"; +import { prepareFrameworks } from "../frameworks"; import { previews } from "../previews"; const { FunctionsServer } = require("./functions"); @@ -26,11 +27,7 @@ export async function serve(options: any): Promise { targetNames.includes("hosting") && [].concat(options.config.get("hosting")).some((it: any) => it.source) ) { - await require("firebase-frameworks").prepare( - targetNames, - { project: options.projectId }, - options - ); + await prepareFrameworks(targetNames, options, options); } await Promise.all( _.map(targetNames, (targetName: string) => { From ab96b28a44316b50514b94ad6c536d665d51170d Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 10 May 2022 21:39:53 +0000 Subject: [PATCH 0313/1699] 10.9.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 4ada9b81da8..2bed7140385 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.8.0", + "version": "10.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.8.0", + "version": "10.9.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 954f0c2373f..6eaf84ee1c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.8.0", + "version": "10.9.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 03072fd091a58e770a5bbe4ec66d371f78f62dd1 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 10 May 2022 21:40:16 +0000 Subject: [PATCH 0314/1699] [firebase-release] Removed change log and reset repo after 10.9.0 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0788e4642c..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Adds Firebase Extensions emulator. Learn more about how to test Extensions in the Emulator Suite here: https://firebase.google.com/docs/extensions/manifest. -- Adds support for Cloud Functions for Firebase gen 2. From 514bba3ad0fe95cfe3c0ee4f8617597fad08d6a8 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Wed, 11 May 2022 12:12:07 -0400 Subject: [PATCH 0315/1699] Bump firebase-frameworks (#4539) * Bump frameworks * schema * Adding source to array props of schema * Changelog and bump * Bump shrinkwrap * Fixing hosting schema * Revert for feedback * Formatting * Schema * Formatting * Bump to 0.4.2 which fixes a build issue for custom / express.js apps * Update CHANGELOG.md Co-authored-by: Bryan Kendall --- CHANGELOG.md | 2 ++ npm-shrinkwrap.json | 14 +++++++------- package.json | 2 +- schema/firebase-config.json | 9 +++++++++ src/firebaseConfig.ts | 1 + src/frameworks/index.ts | 11 ++++++++++- 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..c9fe90ececf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Updates [firebase-frameworks](https://github.com/FirebaseExtended/firebase-framework-tools) to 0.4.2 addressing several issues with the web frameworks integration. +- Adds `hosting.source` to configuration schema as an allowed property. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2bed7140385..7491584381e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -30,7 +30,7 @@ "exit-code": "^1.0.2", "express": "^4.16.4", "filesize": "^6.1.0", - "firebase-frameworks": "^0.4.0", + "firebase-frameworks": "^0.4.2", "fs-extra": "^5.0.0", "glob": "^7.1.2", "google-auth-library": "^7.11.0", @@ -6059,9 +6059,9 @@ } }, "node_modules/firebase-frameworks": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.4.0.tgz", - "integrity": "sha512-Seu+1dKNo3AacMrOHb1V0F41DfCKiM6gW4Go/34z78WtuBkzKNSUOUI+w8XCH7A96QGZRbNbGwt33BiSXEb2xQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.4.2.tgz", + "integrity": "sha512-a3xNE3wPh8JWq2WOgWlSypVS9O/y/3/3Im9EV7bNBF44wFV2oOAyFdVgDk6it81+lBRv7ci8PttgQZohtsFeVA==", "dependencies": { "fs-extra": "^10.1.0", "jsonc-parser": "^3.0.0", @@ -18454,9 +18454,9 @@ } }, "firebase-frameworks": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.4.0.tgz", - "integrity": "sha512-Seu+1dKNo3AacMrOHb1V0F41DfCKiM6gW4Go/34z78WtuBkzKNSUOUI+w8XCH7A96QGZRbNbGwt33BiSXEb2xQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/firebase-frameworks/-/firebase-frameworks-0.4.2.tgz", + "integrity": "sha512-a3xNE3wPh8JWq2WOgWlSypVS9O/y/3/3Im9EV7bNBF44wFV2oOAyFdVgDk6it81+lBRv7ci8PttgQZohtsFeVA==", "requires": { "fs-extra": "^10.1.0", "jsonc-parser": "^3.0.0", diff --git a/package.json b/package.json index 6eaf84ee1c7..612e37b50e9 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "exit-code": "^1.0.2", "express": "^4.16.4", "filesize": "^6.1.0", - "firebase-frameworks": "^0.4.0", + "firebase-frameworks": "^0.4.2", "fs-extra": "^5.0.0", "glob": "^7.1.2", "google-auth-library": "^7.11.0", diff --git a/schema/firebase-config.json b/schema/firebase-config.json index 9c82b456a94..69b213df88a 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -910,6 +910,9 @@ "site": { "type": "string" }, + "source": { + "type": "string" + }, "target": { "type": "string" }, @@ -1392,6 +1395,9 @@ "site": { "type": "string" }, + "source": { + "type": "string" + }, "target": { "type": "string" }, @@ -1874,6 +1880,9 @@ "site": { "type": "string" }, + "source": { + "type": "string" + }, "target": { "type": "string" }, diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index c6b5631fb47..48c725e2c1c 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -59,6 +59,7 @@ export type HostingHeaders = HostingSource & { type HostingBase = { public?: string; + source?: string; ignore?: string[]; appAssociation?: string; cleanUrls?: boolean; diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 814e06708c1..1b97bd437af 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -95,8 +95,17 @@ export const prepareFrameworks = async (targetNames: string[], context: any, opt firebaseProjectConfig = await getAppConfig(appId, AppPlatform.WEB); } else { console.warn( - `No Firebase app associated with site ${site}, unable to provide authenticated server context` + `No Firebase app associated with site ${site}, unable to provide authenticated server context. +You can link a Web app to a Hosting site here https://console.firebase.google.com/project/_/settings/general/web` ); + if (!options.nonInteractive) { + const continueDeploy = await promptOnce({ + type: "confirm", + default: true, + message: "Would you like to continue with the deploy?", + }); + if (!continueDeploy) exit(1); + } } } } From 4b2242e28b9a3d0c7fa16b0405b5a989b79ed893 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 11 May 2022 09:44:20 -0700 Subject: [PATCH 0316/1699] Provide runtime config in Node discovery (#4541) * Provide runtime config in Node discovery * Changelog --- CHANGELOG.md | 1 + src/deploy/functions/runtimes/node/index.ts | 26 ++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9fe90ececf..a3e68cf6045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ +- Fix bug where functions failed to deploy if Runtime Config is accessed at global scope (#4541) - Updates [firebase-frameworks](https://github.com/FirebaseExtended/firebase-framework-tools) to 0.4.2 addressing several issues with the web frameworks integration. - Adds `hosting.source` to configuration schema as an allowed property. diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index df5165da412..d6ccefdb753 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -92,15 +92,23 @@ export class Delegate { return Promise.resolve(() => Promise.resolve()); } - serve(port: number, envs: backend.EnvironmentVariables): Promise<() => Promise> { + serve( + port: number, + config: backend.RuntimeConfigValues, + envs: backend.EnvironmentVariables + ): Promise<() => Promise> { + const env: Record = { + ...envs, + PORT: port.toString(), + FUNCTIONS_CONTROL_API: "true", + HOME: process.env.HOME, + PATH: process.env.PATH, + }; + if (Object.keys(config || {}).length) { + env.CLOUD_RUNTIME_CONFIG = JSON.stringify(config); + } const childProcess = spawn("./node_modules/.bin/firebase-functions", [this.sourceDir], { - env: { - ...envs, - PORT: port.toString(), - FUNCTIONS_CONTROL_API: "true", - HOME: process.env.HOME, - PATH: process.env.PATH, - }, + env, cwd: this.sourceDir, stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "inherit"], }); @@ -157,7 +165,7 @@ export class Delegate { if (!discovered) { const getPort = promisify(portfinder.getPort) as () => Promise; const port = await getPort(); - const kill = await this.serve(port, env); + const kill = await this.serve(port, config, env); try { discovered = await discovery.detectFromPort(port, this.projectId, this.runtime); } finally { From 96488c5ce10ce9b956eacc0c0ef2c60766e5fbf3 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 11 May 2022 16:53:15 +0000 Subject: [PATCH 0317/1699] 10.9.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7491584381e..41f1615eb61 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.9.0", + "version": "10.9.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.9.0", + "version": "10.9.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 612e37b50e9..208774c7e1b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.9.0", + "version": "10.9.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 002bcc5c524d3c36e45e79faa20e7ed972936210 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 11 May 2022 16:53:38 +0000 Subject: [PATCH 0318/1699] [firebase-release] Removed change log and reset repo after 10.9.1 release --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e68cf6045..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +0,0 @@ -- Fix bug where functions failed to deploy if Runtime Config is accessed at global scope (#4541) -- Updates [firebase-frameworks](https://github.com/FirebaseExtended/firebase-framework-tools) to 0.4.2 addressing several issues with the web frameworks integration. -- Adds `hosting.source` to configuration schema as an allowed property. From e8ea6c91ed1ea2273d6118f8a9da1a42a2d0318c Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 11 May 2022 13:23:08 -0700 Subject: [PATCH 0319/1699] Correctly copy over secret environment variables (#4543) * Correctly copy over secret environment variables * Changelog * Run formatter * Add unit tests --- CHANGELOG.md | 1 + .../functions/runtimes/discovery/v1alpha1.ts | 29 ++++++++++++------- .../runtimes/discovery/v1alpha1.spec.ts | 19 +++++++++++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..6050b89fbf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes bug where secret environment variables were not set on functions (#4543) diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 681e7e72ad6..5324acce314 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -13,7 +13,14 @@ const CHANNEL_NAME_REGEX = new RegExp( "(?[A-Za-z\\d\\-_]+)" ); -export type ManifestEndpoint = backend.ServiceConfiguration & +export interface ManifestSecretEnv { + key: string; + secret?: string; + projectId: string; +} + +type Base = Omit; +export type ManifestEndpoint = Base & backend.Triggered & Partial & Partial & @@ -24,6 +31,7 @@ export type ManifestEndpoint = backend.ServiceConfiguration & region?: string[]; entryPoint: string; platform?: backend.FunctionsPlatform; + secretEnvironmentVariables?: Array; }; export interface Manifest { @@ -249,17 +257,16 @@ function parseEndpoints( ep, "secretEnvironmentVariables", "secretEnvironmentVariables", - (senvs: ManifestEndpoint["secretEnvironmentVariables"]) => { - if (senvs && senvs.length > 0) { - ep.secretEnvironmentVariables = []; - for (const { key, secret } of senvs) { - ep.secretEnvironmentVariables.push({ - key, - secret: secret || key, // if secret is undefined, assume env var key == secret name - projectId: project, - }); - } + (senvs: Array) => { + const secretEnvironmentVariables: backend.SecretEnvVar[] = []; + for (const { key, secret } of senvs) { + secretEnvironmentVariables.push({ + key, + secret: secret || key, // if secret is undefined, assume env var key == secret name + projectId: project, + }); } + return secretEnvironmentVariables; } ); allParsed.push(parsed); diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 8bea601adf9..c2569c80273 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -294,7 +294,10 @@ describe("backendFromV1Alpha1", () => { }); // Parser errors; describe("allows valid backends", () => { - const DEFAULTED_ENDPOINT: Omit = { + const DEFAULTED_ENDPOINT: Omit< + backend.Endpoint, + "httpsTrigger" | "secretEnvironmentVariables" + > = { ...MIN_ENDPOINT, platform: "gcfv2", id: "id", @@ -551,6 +554,13 @@ describe("backendFromV1Alpha1", () => { }, ingressSettings: "ALLOW_INTERNAL_ONLY", serviceAccountEmail: "sa@", + secretEnvironmentVariables: [ + { + key: "SECRET", + secret: "SECRET", + projectId: "project", + }, + ], }; const yaml: v1alpha1.Manifest = { @@ -560,6 +570,13 @@ describe("backendFromV1Alpha1", () => { ...MIN_ENDPOINT, httpsTrigger: {}, ...fields, + secretEnvironmentVariables: [ + { + key: "SECRET", + // Missing "secret" + projectId: "project", + }, + ], }, }, }; From 3b911de4cbeee081e42df47d6b7e8564c479377b Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 11 May 2022 21:36:47 +0000 Subject: [PATCH 0320/1699] 10.9.2 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 41f1615eb61..c902c66cbee 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "10.9.1", + "version": "10.9.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "10.9.1", + "version": "10.9.2", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^2.18.4", diff --git a/package.json b/package.json index 208774c7e1b..6327336383a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "10.9.1", + "version": "10.9.2", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 9cb6fc15e14fe64497dfd8bf19ef87f2c841ed02 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 11 May 2022 21:37:11 +0000 Subject: [PATCH 0321/1699] [firebase-release] Removed change log and reset repo after 10.9.2 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6050b89fbf6..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes bug where secret environment variables were not set on functions (#4543) From d90d4d738ed986c75db199490c1d955cb34f6008 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Mon, 16 May 2022 16:45:39 -0700 Subject: [PATCH 0322/1699] Release RTDB emulator v4.8.0 (#4500) --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..3a7ac3790ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fix missing Connection header in RTDB emulator REST streaming API (https://github.com/firebase/firebase-tools-ui/issues/3329). diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 3ce92ff2fbf..3df272aeb01 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -27,14 +27,14 @@ const CACHE_DIR = export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDetails } = { database: { - downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.7.3.jar"), - version: "4.7.3", + downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.8.0.jar"), + version: "4.8.0", opts: { cacheDir: CACHE_DIR, remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.7.3.jar", - expectedSize: 28862098, - expectedChecksum: "8f696f24ee89c937a789498a0c0e4899", + "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.8.0.jar", + expectedSize: 33676395, + expectedChecksum: "e5ae0085d9c88ed14b0bd9c25fe62916", namePrefix: "firebase-database-emulator", }, }, From e7d26756cdfd5913b7cd4a92b4abd071e85a3b94 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 16 May 2022 17:55:31 -0700 Subject: [PATCH 0323/1699] error out on old java versions (#4548) * error out on old java versions * changelog --- CHANGELOG.md | 3 ++- src/emulator/commandUtils.ts | 2 +- src/emulator/controller.ts | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6d40d5aac..d6b9bb925f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ -- Remove `params` flag from ext:install, ext:update, ext:configure commands as they are replaced by the Extensions Manifest. See https://firebase.google.com/docs/extensions/manifest for more details. +- Removes support for running the emulators with Java versions prior to 11. +- Removes `params` flag from ext:install, ext:update, ext:configure commands as they are replaced by the Extensions Manifest. See https://firebase.google.com/docs/extensions/manifest for more details. diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index f1ba2ede4d4..a81b1d7c532 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -530,5 +530,5 @@ export async function checkJavaSupported(): Promise { } export const JAVA_DEPRECATION_WARNING = - "Support for Java version <= 10 will be dropped soon in firebase-tools@11. " + + "firebase-tools no longer supports Java version before 11. " + "Please upgrade to Java version 11 or above to continue using the emulators."; diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 6b270e4b3be..4f6165005ef 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -356,11 +356,11 @@ export async function startAll( `No emulators to start, run ${clc.bold("firebase init emulators")} to get started.` ); } - const deprecationNotices = []; + const deprecationNotices: string[] = []; if (targets.some(requiresJava)) { if (!(await commandUtils.checkJavaSupported())) { - utils.logLabeledWarning("emulators", JAVA_DEPRECATION_WARNING, "warn"); - deprecationNotices.push(JAVA_DEPRECATION_WARNING); + utils.logLabeledError("emulators", JAVA_DEPRECATION_WARNING, "warn"); + throw new FirebaseError(JAVA_DEPRECATION_WARNING); } } const hubLogger = EmulatorLogger.forEmulator(Emulators.HUB); From 295a5095f12db7ab30bd58e08c9818c9a6d7f91a Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 17 May 2022 09:06:13 -0700 Subject: [PATCH 0324/1699] format and update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6b9bb925f8..06493f169f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,6 @@ +## Breaking + +- Drops support for Node 12. +- Tooling moves to Node 16, firepit (standalone) builds move to Node 16, testing moves to 14, 16, and 18. - Removes support for running the emulators with Java versions prior to 11. - Removes `params` flag from ext:install, ext:update, ext:configure commands as they are replaced by the Extensions Manifest. See https://firebase.google.com/docs/extensions/manifest for more details. From ae1966e6ea0acb1cd6c12ec822d164801790465b Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 17 May 2022 09:53:51 -0700 Subject: [PATCH 0325/1699] remove unused dotenv dependency (#4551) --- CHANGELOG.md | 1 + npm-shrinkwrap.json | 33 --------------------------------- package.json | 2 -- 3 files changed, 1 insertion(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37316e83d28..27cf9d68749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,4 @@ ## Not-so-breaking - Fix missing Connection header in RTDB emulator REST streaming API (https://github.com/firebase/firebase-tools-ui/issues/3329). +- Removes unused `dotenv` dependency. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7b8e5b1090b..af310192c3e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -24,7 +24,6 @@ "cross-env": "^5.1.3", "cross-spawn": "^7.0.1", "csv-parse": "^5.0.4", - "dotenv": "^6.1.0", "exegesis": "^4.1.0", "exegesis-express": "^4.0.0", "exit-code": "^1.0.2", @@ -86,7 +85,6 @@ "@types/configstore": "^4.0.0", "@types/cors": "^2.8.10", "@types/cross-spawn": "^6.0.1", - "@types/dotenv": "^6.1.0", "@types/express": "^4.17.0", "@types/express-serve-static-core": "^4.17.8", "@types/fs-extra": "^5.0.5", @@ -2076,15 +2074,6 @@ "@types/node": "*" } }, - "node_modules/@types/dotenv": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", - "integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", @@ -4800,14 +4789,6 @@ "node": "*" } }, - "node_modules/dotenv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", - "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==", - "engines": { - "node": ">=6" - } - }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -15467,15 +15448,6 @@ "@types/node": "*" } }, - "@types/dotenv": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", - "integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", @@ -17627,11 +17599,6 @@ "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==", "dev": true }, - "dotenv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", - "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" - }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", diff --git a/package.json b/package.json index f9ba29a0553..2e7913630cd 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ "cross-env": "^5.1.3", "cross-spawn": "^7.0.1", "csv-parse": "^5.0.4", - "dotenv": "^6.1.0", "exegesis": "^4.1.0", "exegesis-express": "^4.0.0", "exit-code": "^1.0.2", @@ -160,7 +159,6 @@ "@types/configstore": "^4.0.0", "@types/cors": "^2.8.10", "@types/cross-spawn": "^6.0.1", - "@types/dotenv": "^6.1.0", "@types/express": "^4.17.0", "@types/express-serve-static-core": "^4.17.8", "@types/fs-extra": "^5.0.5", From cf1dc819384a018e9ca4ec6b46568f1e1aa79d58 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 17 May 2022 10:15:13 -0700 Subject: [PATCH 0326/1699] Removing ext:dev:emulators:* commands (#4552) * Removing ext:dev:emulators:* commands * instead throw errors * PR fixes --- CHANGELOG.md | 1 + package.json | 1 - .../greet-the-world/.gitignore | 1 - .../greet-the-world/extension.yaml | 67 ------------------- .../greet-the-world/functions/index.js | 25 ------- .../greet-the-world/package.json | 12 ---- .../greet-the-world/test-firebase.json | 11 --- .../greet-the-world/test-params.env | 2 - scripts/ext-dev-emulators-tests/run.sh | 15 ----- scripts/ext-dev-emulators-tests/tests.ts | 62 ----------------- src/commands/ext-dev-emulators-exec.ts | 22 ++++-- src/commands/ext-dev-emulators-start.ts | 42 ++++-------- 12 files changed, 29 insertions(+), 232 deletions(-) delete mode 100644 scripts/ext-dev-emulators-tests/greet-the-world/.gitignore delete mode 100644 scripts/ext-dev-emulators-tests/greet-the-world/extension.yaml delete mode 100644 scripts/ext-dev-emulators-tests/greet-the-world/functions/index.js delete mode 100644 scripts/ext-dev-emulators-tests/greet-the-world/package.json delete mode 100644 scripts/ext-dev-emulators-tests/greet-the-world/test-firebase.json delete mode 100644 scripts/ext-dev-emulators-tests/greet-the-world/test-params.env delete mode 100755 scripts/ext-dev-emulators-tests/run.sh delete mode 100644 scripts/ext-dev-emulators-tests/tests.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 27cf9d68749..52f553b7533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Tooling moves to Node 16, firepit (standalone) builds move to Node 16, testing moves to 14, 16, and 18. - Removes support for running the emulators with Java versions prior to 11. - Removes `params` flag from ext:install, ext:update, ext:configure commands as they are replaced by the Extensions Manifest. See https://firebase.google.com/docs/extensions/manifest for more details. +- Removes `ext:dev:emulators:start` and `ext:dev:emulators:exec` preview commands. ## Not-so-breaking diff --git a/package.json b/package.json index 2e7913630cd..f57dfcd14bc 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "test:emulator": "./scripts/emulator-tests/run.sh", "test:extensions-deploy": "./scripts/extensions-deploy-tests/run.sh", "test:extensions-emulator": "./scripts/extensions-emulator-tests/run.sh", - "test:ext-dev-emulator": "./scripts/ext-dev-emulators-tests/run.sh", "test:hosting": "./scripts/hosting-tests/run.sh", "test:triggers-end-to-end": "./scripts/triggers-end-to-end-tests/run.sh", "test:storage-deploy": "./scripts/storage-deploy-tests/run.sh", diff --git a/scripts/ext-dev-emulators-tests/greet-the-world/.gitignore b/scripts/ext-dev-emulators-tests/greet-the-world/.gitignore deleted file mode 100644 index d8b83df9cdb..00000000000 --- a/scripts/ext-dev-emulators-tests/greet-the-world/.gitignore +++ /dev/null @@ -1 +0,0 @@ -package-lock.json diff --git a/scripts/ext-dev-emulators-tests/greet-the-world/extension.yaml b/scripts/ext-dev-emulators-tests/greet-the-world/extension.yaml deleted file mode 100644 index 988b31339ff..00000000000 --- a/scripts/ext-dev-emulators-tests/greet-the-world/extension.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# Learn detailed information about the fields of an extension.yaml file in the docs - -name: greet-the-world # Identifier for the extension -specVersion: v1beta # Version of the Firebase Extensions specification -version: 0.0.1 # Follow semver versioning -license: Apache-2.0 # https://spdx.org/licenses/ - -# Friendly display name for your extension (~3-5 words) -displayName: Greet the world - -# Brief description of the task your extension performs (~1 sentence) -description: >- - Sends the world a specified greeting. - -billingRequired: false # Learn more in the docs - -# For your extension to interact with other Google APIs (like Firestore, Cloud Storage, or Cloud Translation), -# set the `apis` field. In addition, set the `roles` field to grant appropriate IAM access to interact with these products. -# Learn about these fields in the docs - -# Learn about the `resources` field in the docs -resources: - - name: greetTheWorld - type: firebaseextensions.v1beta.function - description: >- - HTTPS-triggered function that responds with a specified greeting message - properties: - sourceDirectory: . - location: ${LOCATION} - httpsTrigger: {} - -# Learn about the `params` field in the docs -params: - - param: GREETING - type: string - label: Greeting for the world - description: >- - What do you want to say to the world? For example, Hello world? or What's up, world? - default: Hello - required: true - immutable: false - - - param: LOCATION - type: select - label: Cloud Functions location - description: >- - Where do you want to deploy the functions created for this extension? For help selecting a - location, refer to the [location selection - guide](https://firebase.google.com/docs/functions/locations). - options: - - label: Iowa (us-central1) - value: us-central1 - - label: South Carolina (us-east1) - value: us-east1 - - label: Northern Virginia (us-east4) - value: us-east4 - - label: Belgium (europe-west1) - value: europe-west1 - - label: London (europe-west2) - value: europe-west2 - - label: Hong Kong (asia-east2) - value: asia-east2 - - label: Tokyo (asia-northeast1) - value: asia-northeast1 - default: us-central1 - required: true - immutable: true diff --git a/scripts/ext-dev-emulators-tests/greet-the-world/functions/index.js b/scripts/ext-dev-emulators-tests/greet-the-world/functions/index.js deleted file mode 100644 index 6203bf1ad69..00000000000 --- a/scripts/ext-dev-emulators-tests/greet-the-world/functions/index.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * This template contains a HTTP function that responds with a greeting when called - * - * Always use the FUNCTIONS HANDLER NAMESPACE - * when writing Cloud Functions for extensions. - * Learn more about the handler namespace in the docs - * - * Reference PARAMETERS in your functions code with: - * `process.env.` - * Learn more about parameters in the docs - */ - -const functions = require("firebase-functions"); - -exports.greetTheWorld = functions.handler.https.onRequest((req, res) => { - // Here we reference a user-provided parameter (its value is provided by the user during installation) - const consumerProvidedGreeting = process.env.GREETING; - - // And here we reference an auto-populated parameter (its value is provided by Firebase after installation) - const instanceId = process.env.EXT_INSTANCE_ID; - - const greeting = `${consumerProvidedGreeting} World from ${instanceId}`; - - res.send(greeting); -}); diff --git a/scripts/ext-dev-emulators-tests/greet-the-world/package.json b/scripts/ext-dev-emulators-tests/greet-the-world/package.json deleted file mode 100644 index 03f47e0b627..00000000000 --- a/scripts/ext-dev-emulators-tests/greet-the-world/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "greet-the-world", - "version": "1.0.0", - "description": "", - "main": "functions/index.js", - "dependencies": { - "firebase-admin": "^9.4.2", - "firebase-functions": "^3.15.1" - }, - "author": "", - "license": "MIT" -} diff --git a/scripts/ext-dev-emulators-tests/greet-the-world/test-firebase.json b/scripts/ext-dev-emulators-tests/greet-the-world/test-firebase.json deleted file mode 100644 index 92607c48562..00000000000 --- a/scripts/ext-dev-emulators-tests/greet-the-world/test-firebase.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "functions": {}, - "emulators": { - "hub": { - "port": 4000 - }, - "functions": { - "port": 9002 - } - } -} diff --git a/scripts/ext-dev-emulators-tests/greet-the-world/test-params.env b/scripts/ext-dev-emulators-tests/greet-the-world/test-params.env deleted file mode 100644 index 428f8e508ce..00000000000 --- a/scripts/ext-dev-emulators-tests/greet-the-world/test-params.env +++ /dev/null @@ -1,2 +0,0 @@ -GREETING=Hello -LOCATION=us-east1 diff --git a/scripts/ext-dev-emulators-tests/run.sh b/scripts/ext-dev-emulators-tests/run.sh deleted file mode 100755 index 53d63d49af3..00000000000 --- a/scripts/ext-dev-emulators-tests/run.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -e # Immediately exit on failure - -# Globally link the CLI for the testing framework -./scripts/npm-link.sh - -cd scripts/ext-dev-emulators-tests/greet-the-world -npm i -cd - # Return to root so that we don't need a relative path for mocha - -mocha \ - --require ts-node/register \ - --require source-map-support/register \ - --require src/test/helpers/mocha-bootstrap.ts \ - scripts/ext-dev-emulators-tests/tests.ts diff --git a/scripts/ext-dev-emulators-tests/tests.ts b/scripts/ext-dev-emulators-tests/tests.ts deleted file mode 100644 index c1c5b8783b0..00000000000 --- a/scripts/ext-dev-emulators-tests/tests.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { expect } from "chai"; -import * as fs from "fs"; -import * as path from "path"; -import * as subprocess from "child_process"; - -import { FrameworkOptions, TriggerEndToEndTest } from "../integration-helpers/framework"; - -const EXTENSION_ROOT = path.dirname(__filename) + "/greet-the-world"; - -const FIREBASE_PROJECT = process.env.FBTOOLS_TARGET_PROJECT || ""; -const FIREBASE_PROJECT_ZONE = "us-east1"; -const TEST_CONFIG_FILE = "test-firebase.json"; -const TEST_FUNCTION_NAME = "greetTheWorld"; - -/* - * Various delays that are needed because this test spawns - * parallel emulator subprocesses. - */ -const TEST_SETUP_TIMEOUT = 60000; -const EMULATORS_SHUTDOWN_DELAY_MS = 5000; - -function readConfig(): FrameworkOptions { - const filename = path.join(EXTENSION_ROOT, "test-firebase.json"); - const data = fs.readFileSync(filename, "utf8"); - return JSON.parse(data); -} - -describe("extension emulator", () => { - let test: TriggerEndToEndTest; - - before(async function (this) { - this.timeout(TEST_SETUP_TIMEOUT); - - expect(FIREBASE_PROJECT).to.exist.and.not.be.empty; - - // TODO(joehan): Delete the --open-sesame call when extdev flag is removed. - const p = subprocess.spawnSync("firebase", ["--open-sesame", "extdev"], { cwd: __dirname }); - console.log("open-sesame output:", p.stdout.toString()); - - test = new TriggerEndToEndTest(FIREBASE_PROJECT, EXTENSION_ROOT, readConfig()); - await test.startExtEmulators([ - "--test-params", - "test-params.env", - "--test-config", - TEST_CONFIG_FILE, - ]); - }); - - after(async function (this) { - this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); - await test.stopEmulators(); - }); - - it("should execute an HTTP function", async function (this) { - this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); - - const res = await test.invokeHttpFunction(TEST_FUNCTION_NAME, FIREBASE_PROJECT_ZONE); - - expect(res.status).to.equal(200); - await expect(res.text()).to.eventually.equal("Hello World from greet-the-world"); - }); -}); diff --git a/src/commands/ext-dev-emulators-exec.ts b/src/commands/ext-dev-emulators-exec.ts index 52790217a4d..9449f52413e 100644 --- a/src/commands/ext-dev-emulators-exec.ts +++ b/src/commands/ext-dev-emulators-exec.ts @@ -1,11 +1,13 @@ +// TODO(joehanley): Remove this entire command in v12. +import * as clc from "cli-color"; + import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; - +import { FirebaseError } from "../error"; import * as commandUtils from "../emulator/commandUtils"; -import * as optionsHelper from "../extensions/emulator/optionsHelper"; module.exports = new Command("ext:dev:emulators:exec + + diff --git a/scripts/frameworks-tests/vite-project/javascript.svg b/scripts/frameworks-tests/vite-project/javascript.svg new file mode 100644 index 00000000000..f9abb2b728d --- /dev/null +++ b/scripts/frameworks-tests/vite-project/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/frameworks-tests/vite-project/main.js b/scripts/frameworks-tests/vite-project/main.js new file mode 100644 index 00000000000..727b4ea209e --- /dev/null +++ b/scripts/frameworks-tests/vite-project/main.js @@ -0,0 +1,23 @@ +import './style.css' +import javascriptLogo from './javascript.svg' +import { setupCounter } from './counter.js' + +document.querySelector('#app').innerHTML = ` + +` + +setupCounter(document.querySelector('#counter')) diff --git a/scripts/frameworks-tests/vite-project/package-lock.json b/scripts/frameworks-tests/vite-project/package-lock.json new file mode 100644 index 00000000000..a33faf3c34c --- /dev/null +++ b/scripts/frameworks-tests/vite-project/package-lock.json @@ -0,0 +1,881 @@ +{ + "name": "vite-project", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "vite-project", + "version": "0.0.0", + "devDependencies": { + "vite": "^3.1.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.10.tgz", + "integrity": "sha512-FNONeQPy/ox+5NBkcSbYJxoXj9GWu8gVGJTVmUyoOCKQFDTrHVKgNSzChdNt0I8Aj/iKcsDf2r9BFwv+FSNUXg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.10.tgz", + "integrity": "sha512-w0Ou3Z83LOYEkwaui2M8VwIp+nLi/NA60lBLMvaJ+vXVMcsARYdEzLNE7RSm4+lSg4zq4d7fAVuzk7PNQ5JFgg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.10.tgz", + "integrity": "sha512-N7wBhfJ/E5fzn/SpNgX+oW2RLRjwaL8Y0ezqNqhjD6w0H2p0rDuEz2FKZqpqLnO8DCaWumKe8dsC/ljvVSSxng==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.10", + "@esbuild/linux-loong64": "0.15.10", + "esbuild-android-64": "0.15.10", + "esbuild-android-arm64": "0.15.10", + "esbuild-darwin-64": "0.15.10", + "esbuild-darwin-arm64": "0.15.10", + "esbuild-freebsd-64": "0.15.10", + "esbuild-freebsd-arm64": "0.15.10", + "esbuild-linux-32": "0.15.10", + "esbuild-linux-64": "0.15.10", + "esbuild-linux-arm": "0.15.10", + "esbuild-linux-arm64": "0.15.10", + "esbuild-linux-mips64le": "0.15.10", + "esbuild-linux-ppc64le": "0.15.10", + "esbuild-linux-riscv64": "0.15.10", + "esbuild-linux-s390x": "0.15.10", + "esbuild-netbsd-64": "0.15.10", + "esbuild-openbsd-64": "0.15.10", + "esbuild-sunos-64": "0.15.10", + "esbuild-windows-32": "0.15.10", + "esbuild-windows-64": "0.15.10", + "esbuild-windows-arm64": "0.15.10" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.10.tgz", + "integrity": "sha512-UI7krF8OYO1N7JYTgLT9ML5j4+45ra3amLZKx7LO3lmLt1Ibn8t3aZbX5Pu4BjWiqDuJ3m/hsvhPhK/5Y/YpnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.10.tgz", + "integrity": "sha512-EOt55D6xBk5O05AK8brXUbZmoFj4chM8u3riGflLa6ziEoVvNjRdD7Cnp82NHQGfSHgYR06XsPI8/sMuA/cUwg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.10.tgz", + "integrity": "sha512-hbDJugTicqIm+WKZgp208d7FcXcaK8j2c0l+fqSJ3d2AzQAfjEYDRM3Z2oMeqSJ9uFxyj/muSACLdix7oTstRA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.10.tgz", + "integrity": "sha512-M1t5+Kj4IgSbYmunf2BB6EKLkWUq+XlqaFRiGOk8bmBapu9bCDrxjf4kUnWn59Dka3I27EiuHBKd1rSO4osLFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.10.tgz", + "integrity": "sha512-KMBFMa7C8oc97nqDdoZwtDBX7gfpolkk6Bcmj6YFMrtCMVgoU/x2DI1p74DmYl7CSS6Ppa3xgemrLrr5IjIn0w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.10.tgz", + "integrity": "sha512-m2KNbuCX13yQqLlbSojFMHpewbn8wW5uDS6DxRpmaZKzyq8Dbsku6hHvh2U+BcLwWY4mpgXzFUoENEf7IcioGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.10.tgz", + "integrity": "sha512-guXrwSYFAvNkuQ39FNeV4sNkNms1bLlA5vF1H0cazZBOLdLFIny6BhT+TUbK/hdByMQhtWQ5jI9VAmPKbVPu1w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.10.tgz", + "integrity": "sha512-jd8XfaSJeucMpD63YNMO1JCrdJhckHWcMv6O233bL4l6ogQKQOxBYSRP/XLWP+6kVTu0obXovuckJDcA0DKtQA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.10.tgz", + "integrity": "sha512-6N8vThLL/Lysy9y4Ex8XoLQAlbZKUyExCWyayGi2KgTBelKpPgj6RZnUaKri0dHNPGgReJriKVU6+KDGQwn10A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.10.tgz", + "integrity": "sha512-GByBi4fgkvZFTHFDYNftu1DQ1GzR23jws0oWyCfhnI7eMOe+wgwWrc78dbNk709Ivdr/evefm2PJiUBMiusS1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.10.tgz", + "integrity": "sha512-BxP+LbaGVGIdQNJUNF7qpYjEGWb0YyHVSKqYKrn+pTwH/SiHUxFyJYSP3pqkku61olQiSBnSmWZ+YUpj78Tw7Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.10.tgz", + "integrity": "sha512-LoSQCd6498PmninNgqd/BR7z3Bsk/mabImBWuQ4wQgmQEeanzWd5BQU2aNi9mBURCLgyheuZS6Xhrw5luw3OkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.10.tgz", + "integrity": "sha512-Lrl9Cr2YROvPV4wmZ1/g48httE8z/5SCiXIyebiB5N8VT7pX3t6meI7TQVHw/wQpqP/AF4SksDuFImPTM7Z32Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.10.tgz", + "integrity": "sha512-ReP+6q3eLVVP2lpRrvl5EodKX7EZ1bS1/z5j6hsluAlZP5aHhk6ghT6Cq3IANvvDdscMMCB4QEbI+AjtvoOFpA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.10.tgz", + "integrity": "sha512-iGDYtJCMCqldMskQ4eIV+QSS/CuT7xyy9i2/FjpKvxAuCzrESZXiA1L64YNj6/afuzfBe9i8m/uDkFHy257hTw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.10.tgz", + "integrity": "sha512-ftMMIwHWrnrYnvuJQRJs/Smlcb28F9ICGde/P3FUTCgDDM0N7WA0o9uOR38f5Xe2/OhNCgkjNeb7QeaE3cyWkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.10.tgz", + "integrity": "sha512-mf7hBL9Uo2gcy2r3rUFMjVpTaGpFJJE5QTDDqUFf1632FxteYANffDZmKbqX0PfeQ2XjUDE604IcE7OJeoHiyg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.10.tgz", + "integrity": "sha512-ttFVo+Cg8b5+qHmZHbEc8Vl17kCleHhLzgT8X04y8zudEApo0PxPg9Mz8Z2cKH1bCYlve1XL8LkyXGFjtUYeGg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.10.tgz", + "integrity": "sha512-2H0gdsyHi5x+8lbng3hLbxDWR7mKHWh5BXZGKVG830KUmXOOWFE2YKJ4tHRkejRduOGDrBvHBriYsGtmTv3ntA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.10.tgz", + "integrity": "sha512-S+th4F+F8VLsHLR0zrUcG+Et4hx0RKgK1eyHc08kztmLOES8BWwMiaGdoW9hiXuzznXQ0I/Fg904MNbr11Nktw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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 + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", + "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.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/rollup": { + "version": "2.78.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", + "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/vite": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", + "integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==", + "dev": true, + "dependencies": { + "esbuild": "^0.15.9", + "postcss": "^8.4.16", + "resolve": "^1.22.1", + "rollup": "~2.78.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "less": "*", + "sass": "*", + "stylus": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.10.tgz", + "integrity": "sha512-FNONeQPy/ox+5NBkcSbYJxoXj9GWu8gVGJTVmUyoOCKQFDTrHVKgNSzChdNt0I8Aj/iKcsDf2r9BFwv+FSNUXg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.10.tgz", + "integrity": "sha512-w0Ou3Z83LOYEkwaui2M8VwIp+nLi/NA60lBLMvaJ+vXVMcsARYdEzLNE7RSm4+lSg4zq4d7fAVuzk7PNQ5JFgg==", + "dev": true, + "optional": true + }, + "esbuild": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.10.tgz", + "integrity": "sha512-N7wBhfJ/E5fzn/SpNgX+oW2RLRjwaL8Y0ezqNqhjD6w0H2p0rDuEz2FKZqpqLnO8DCaWumKe8dsC/ljvVSSxng==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.10", + "@esbuild/linux-loong64": "0.15.10", + "esbuild-android-64": "0.15.10", + "esbuild-android-arm64": "0.15.10", + "esbuild-darwin-64": "0.15.10", + "esbuild-darwin-arm64": "0.15.10", + "esbuild-freebsd-64": "0.15.10", + "esbuild-freebsd-arm64": "0.15.10", + "esbuild-linux-32": "0.15.10", + "esbuild-linux-64": "0.15.10", + "esbuild-linux-arm": "0.15.10", + "esbuild-linux-arm64": "0.15.10", + "esbuild-linux-mips64le": "0.15.10", + "esbuild-linux-ppc64le": "0.15.10", + "esbuild-linux-riscv64": "0.15.10", + "esbuild-linux-s390x": "0.15.10", + "esbuild-netbsd-64": "0.15.10", + "esbuild-openbsd-64": "0.15.10", + "esbuild-sunos-64": "0.15.10", + "esbuild-windows-32": "0.15.10", + "esbuild-windows-64": "0.15.10", + "esbuild-windows-arm64": "0.15.10" + } + }, + "esbuild-android-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.10.tgz", + "integrity": "sha512-UI7krF8OYO1N7JYTgLT9ML5j4+45ra3amLZKx7LO3lmLt1Ibn8t3aZbX5Pu4BjWiqDuJ3m/hsvhPhK/5Y/YpnA==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.10.tgz", + "integrity": "sha512-EOt55D6xBk5O05AK8brXUbZmoFj4chM8u3riGflLa6ziEoVvNjRdD7Cnp82NHQGfSHgYR06XsPI8/sMuA/cUwg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.10.tgz", + "integrity": "sha512-hbDJugTicqIm+WKZgp208d7FcXcaK8j2c0l+fqSJ3d2AzQAfjEYDRM3Z2oMeqSJ9uFxyj/muSACLdix7oTstRA==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.10.tgz", + "integrity": "sha512-M1t5+Kj4IgSbYmunf2BB6EKLkWUq+XlqaFRiGOk8bmBapu9bCDrxjf4kUnWn59Dka3I27EiuHBKd1rSO4osLFQ==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.10.tgz", + "integrity": "sha512-KMBFMa7C8oc97nqDdoZwtDBX7gfpolkk6Bcmj6YFMrtCMVgoU/x2DI1p74DmYl7CSS6Ppa3xgemrLrr5IjIn0w==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.10.tgz", + "integrity": "sha512-m2KNbuCX13yQqLlbSojFMHpewbn8wW5uDS6DxRpmaZKzyq8Dbsku6hHvh2U+BcLwWY4mpgXzFUoENEf7IcioGg==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.10.tgz", + "integrity": "sha512-guXrwSYFAvNkuQ39FNeV4sNkNms1bLlA5vF1H0cazZBOLdLFIny6BhT+TUbK/hdByMQhtWQ5jI9VAmPKbVPu1w==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.10.tgz", + "integrity": "sha512-jd8XfaSJeucMpD63YNMO1JCrdJhckHWcMv6O233bL4l6ogQKQOxBYSRP/XLWP+6kVTu0obXovuckJDcA0DKtQA==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.10.tgz", + "integrity": "sha512-6N8vThLL/Lysy9y4Ex8XoLQAlbZKUyExCWyayGi2KgTBelKpPgj6RZnUaKri0dHNPGgReJriKVU6+KDGQwn10A==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.10.tgz", + "integrity": "sha512-GByBi4fgkvZFTHFDYNftu1DQ1GzR23jws0oWyCfhnI7eMOe+wgwWrc78dbNk709Ivdr/evefm2PJiUBMiusS1A==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.10.tgz", + "integrity": "sha512-BxP+LbaGVGIdQNJUNF7qpYjEGWb0YyHVSKqYKrn+pTwH/SiHUxFyJYSP3pqkku61olQiSBnSmWZ+YUpj78Tw7Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.10.tgz", + "integrity": "sha512-LoSQCd6498PmninNgqd/BR7z3Bsk/mabImBWuQ4wQgmQEeanzWd5BQU2aNi9mBURCLgyheuZS6Xhrw5luw3OkQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.10.tgz", + "integrity": "sha512-Lrl9Cr2YROvPV4wmZ1/g48httE8z/5SCiXIyebiB5N8VT7pX3t6meI7TQVHw/wQpqP/AF4SksDuFImPTM7Z32Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.10.tgz", + "integrity": "sha512-ReP+6q3eLVVP2lpRrvl5EodKX7EZ1bS1/z5j6hsluAlZP5aHhk6ghT6Cq3IANvvDdscMMCB4QEbI+AjtvoOFpA==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.10.tgz", + "integrity": "sha512-iGDYtJCMCqldMskQ4eIV+QSS/CuT7xyy9i2/FjpKvxAuCzrESZXiA1L64YNj6/afuzfBe9i8m/uDkFHy257hTw==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.10.tgz", + "integrity": "sha512-ftMMIwHWrnrYnvuJQRJs/Smlcb28F9ICGde/P3FUTCgDDM0N7WA0o9uOR38f5Xe2/OhNCgkjNeb7QeaE3cyWkQ==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.10.tgz", + "integrity": "sha512-mf7hBL9Uo2gcy2r3rUFMjVpTaGpFJJE5QTDDqUFf1632FxteYANffDZmKbqX0PfeQ2XjUDE604IcE7OJeoHiyg==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.10.tgz", + "integrity": "sha512-ttFVo+Cg8b5+qHmZHbEc8Vl17kCleHhLzgT8X04y8zudEApo0PxPg9Mz8Z2cKH1bCYlve1XL8LkyXGFjtUYeGg==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.10.tgz", + "integrity": "sha512-2H0gdsyHi5x+8lbng3hLbxDWR7mKHWh5BXZGKVG830KUmXOOWFE2YKJ4tHRkejRduOGDrBvHBriYsGtmTv3ntA==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.10", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.10.tgz", + "integrity": "sha512-S+th4F+F8VLsHLR0zrUcG+Et4hx0RKgK1eyHc08kztmLOES8BWwMiaGdoW9hiXuzznXQ0I/Fg904MNbr11Nktw==", + "dev": true, + "optional": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "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 + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "postcss": { + "version": "8.4.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", + "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "rollup": { + "version": "2.78.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", + "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "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 + }, + "vite": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", + "integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==", + "dev": true, + "requires": { + "esbuild": "^0.15.9", + "fsevents": "~2.3.2", + "postcss": "^8.4.16", + "resolve": "^1.22.1", + "rollup": "~2.78.0" + } + } + } +} diff --git a/scripts/frameworks-tests/vite-project/package.json b/scripts/frameworks-tests/vite-project/package.json new file mode 100644 index 00000000000..4e55fa3ed80 --- /dev/null +++ b/scripts/frameworks-tests/vite-project/package.json @@ -0,0 +1,14 @@ +{ + "name": "vite-project", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^3.1.0" + } +} \ No newline at end of file diff --git a/scripts/frameworks-tests/vite-project/public/vite.svg b/scripts/frameworks-tests/vite-project/public/vite.svg new file mode 100644 index 00000000000..e7b8dfb1b2a --- /dev/null +++ b/scripts/frameworks-tests/vite-project/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/frameworks-tests/vite-project/style.css b/scripts/frameworks-tests/vite-project/style.css new file mode 100644 index 00000000000..12320801d36 --- /dev/null +++ b/scripts/frameworks-tests/vite-project/style.css @@ -0,0 +1,97 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index f232d6fb662..ad0df66f597 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -222,6 +222,8 @@ function scanDependencyTree(searchingFor: string, dependencies = {}): any { */ export function findDependency(name: string, options: Partial = {}) { const { cwd, depth, omitDev } = { ...DEFAULT_FIND_DEP_OPTIONS, ...options }; + const env: any = Object.assign({}, process.env); + delete env.NODE_ENV; const result = spawnSync( NPM_COMMAND, [ @@ -231,7 +233,7 @@ export function findDependency(name: string, options: Partial = ...(omitDev ? ["--omit", "dev"] : []), ...(depth === undefined ? [] : ["--depth", depth.toString(10)]), ], - { cwd } + { cwd, env } ); if (!result.stdout) return; const json = JSON.parse(result.stdout.toString()); From 12a8887117b25b92db03e47fb444961507396a22 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Thu, 13 Oct 2022 15:20:00 -0700 Subject: [PATCH 0651/1699] Unbreak default ints in text input (#5118) * Unbreak default ints in text input * add a comment explaining this * the ternary is not needed * add release note * format:other --- CHANGELOG.md | 1 + src/deploy/functions/params.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..83a2716fb82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes a crash in integer params when a default value is selected in the prompt. (#5118) diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 5ea90edb081..9059607ba41 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -605,7 +605,10 @@ async function promptText( return promptText(prompt, input, resolvedDefault, converter); } } - const converted = converter(res); + // TODO(vsfan): the toString() is because PromptOnce()'s return type of string + // is wrong--it will return the type of the default if selected. Remove this + // hack once we fix the prompt.ts metaprogramming. + const converted = converter(res.toString()); if (typeof converted === "object") { logger.error(converted.message); return promptText(prompt, input, resolvedDefault, converter); From b0637b98c534b9e4c8c552e1e4245a71ae25253c Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Fri, 14 Oct 2022 08:59:01 -0700 Subject: [PATCH 0652/1699] Update error handling for fetchBlockingFunction() (#5120) * Update error handling for fetchBlockingFunction() * Fix spacing and error message * Move .text() call to try block * Fix nits * Add changelog Co-authored-by: Bryan Kendall --- CHANGELOG.md | 1 + src/emulator/auth/operations.ts | 46 ++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83a2716fb82..5348ea55f82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Fixes a crash in integer params when a default value is selected in the prompt. (#5118) +- Fixes error handling for auth blocking functions. diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index e590ceb67dd..d72d6faca3d 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -859,7 +859,7 @@ function sendOobCode( if (reqBody.continueUrl) { assert( parseAbsoluteUri(reqBody.continueUrl), - "INVALID_CONTINUE_URI: ((expected an absolute URI with valid scheme and host))" + "INVALID_CONTINUE_URI : ((expected an absolute URI with valid scheme and host))" ); } @@ -2193,11 +2193,11 @@ function updateConfig( if (Object.prototype.hasOwnProperty.call(reqBody.blockingFunctions!.triggers, event)) { assert( Object.values(BlockingFunctionEvents).includes(event as BlockingFunctionEvents), - "INVALID_BLOCKING_FUNCTION: ((Event type is invalid.))" + "INVALID_BLOCKING_FUNCTION : ((Event type is invalid.))" ); assert( parseAbsoluteUri(reqBody.blockingFunctions!.triggers[event].functionUri!), - "INVALID_BLOCKING_FUNCTION: ((Expected an absolute URI with valid scheme and host.))" + "INVALID_BLOCKING_FUNCTION : ((Expected an absolute URI with valid scheme and host.))" ); } } @@ -2907,7 +2907,7 @@ function createTenant( reqBody: Schemas["GoogleCloudIdentitytoolkitAdminV2Tenant"] ): Schemas["GoogleCloudIdentitytoolkitAdminV2Tenant"] { if (!(state instanceof AgentProjectState)) { - throw new InternalError("INTERNAL_ERROR: Can only create tenant in agent project", "INTERNAL"); + throw new InternalError("INTERNAL_ERROR : Can only create tenant in agent project", "INTERNAL"); } const mfaConfig = reqBody.mfaConfig ?? {}; @@ -3024,7 +3024,10 @@ async function fetchBlockingFunction( controller.abort(); }, timeoutMs); - let response; + let response: BlockingFunctionResponsePayload; + let ok: boolean; + let status: number; + let text: string; try { const res = await fetch(url, { method: "POST", @@ -3032,29 +3035,42 @@ async function fetchBlockingFunction( body: JSON.stringify(reqBody), signal: controller.signal, }); - const text = await res.text(); - assert( - res.ok, - `BLOCKING_FUNCTION_ERROR_RESPONSE: ((HTTP request to ${url} returned HTTP error${res.status}: ${text}))` - ); - response = JSON.parse(text) as BlockingFunctionResponsePayload; + ok = res.ok; + status = res.status; + text = await res.text(); } catch (thrown: any) { const err = thrown instanceof Error ? thrown : new Error(thrown); const isAbortError = err.name.includes("AbortError"); if (isAbortError) { throw new InternalError( - `BLOCKING_FUNCTION_ERROR_RESPONSE: ((Deadline exceeded making request to ${url}.))`, + `BLOCKING_FUNCTION_ERROR_RESPONSE : ((Deadline exceeded making request to ${url}.))`, err.message ); } + // All other server errors throw new InternalError( - `BLOCKING_FUNCTION_ERROR_RESPONSE: ((Failed to make request to ${url}.))`, + `BLOCKING_FUNCTION_ERROR_RESPONSE : ((Failed to make request to ${url}.))`, err.message ); } finally { clearTimeout(timeout); } + assert( + ok, + `BLOCKING_FUNCTION_ERROR_RESPONSE : ((HTTP request to ${url} returned HTTP error ${status}: ${text}))` + ); + + try { + response = JSON.parse(text) as BlockingFunctionResponsePayload; + } catch (thrown: any) { + const err = thrown instanceof Error ? thrown : new Error(thrown); + throw new InternalError( + `BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response body is not valid JSON.))`, + err.message + ); + } + return processBlockingFunctionResponse(event, response); } @@ -3072,7 +3088,7 @@ function processBlockingFunctionResponse( const userRecord = response.userRecord; assert( userRecord.updateMask, - "BLOCKING_FUNCTION_ERROR_RESPONSE: ((Response UserRecord is missing updateMask.))" + "BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response UserRecord is missing updateMask.))" ); const mask = userRecord.updateMask; const fields = mask.split(","); @@ -3103,7 +3119,7 @@ function processBlockingFunctionResponse( extraClaims = userRecord.sessionClaims; } catch { throw new BadRequestError( - "BLOCKING_FUNCTION_ERROR_RESPONSE: ((Response has malformed session claims.))" + "BLOCKING_FUNCTION_ERROR_RESPONSE : ((Response has malformed session claims.))" ); } break; From 59f914367af799fbe0413707abf7646435ca0984 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 14 Oct 2022 11:26:07 -0700 Subject: [PATCH 0653/1699] Fix Storage rules emulator runtime issue (#5126) * Fix Storage rules emulator runtime issue * changelog * More specific * Formatting --- CHANGELOG.md | 1 + .../conformance/firebase-js-sdk.test.ts | 4 ++-- src/emulator/storage/rules/runtime.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5348ea55f82..03daa5adcd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Fixes a crash in integer params when a default value is selected in the prompt. (#5118) - Fixes error handling for auth blocking functions. +- Fixes bug preventing Storage Rules from updating when ruleset compilation completed successfully but with warnings diff --git a/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts b/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts index df4accef337..791e5ac57a5 100644 --- a/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts +++ b/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts @@ -544,7 +544,7 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { await testBucket.upload(emptyFilePath, { destination: TEST_FILE_NAME }); await signInToFirebaseAuth(page); - const metadata = await page.evaluate(async (filename) => { + const metadata = await page.evaluate((filename) => { return firebase .storage() .ref(filename) @@ -574,7 +574,7 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { }); await signInToFirebaseAuth(page); - const updatedMetadata = await page.evaluate(async (filename) => { + const updatedMetadata = await page.evaluate((filename) => { return firebase.storage().ref(filename).updateMetadata({ cacheControl: null, contentDisposition: null, diff --git a/src/emulator/storage/rules/runtime.ts b/src/emulator/storage/rules/runtime.ts index 1addf2f1b5f..1f53eb05bfd 100644 --- a/src/emulator/storage/rules/runtime.ts +++ b/src/emulator/storage/rules/runtime.ts @@ -285,7 +285,7 @@ export class StorageRulesRuntime { runtimeActionRequest )) as RuntimeActionLoadRulesetResponse; - if (response.errors.length || response.warnings.length) { + if (response.errors.length) { return { issues: StorageRulesIssues.fromResponse(response), }; From 5708902683a477cd1e06232c5af8d415597fc53d Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 14 Oct 2022 20:48:11 +0000 Subject: [PATCH 0654/1699] 11.14.4 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 42b5a04c564..209c58121fa 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.14.3", + "version": "11.14.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.14.3", + "version": "11.14.4", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index d93e282a361..86aae2dc1d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.14.3", + "version": "11.14.4", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 31f9b00eb09b09fa51e26faefd35234127029807 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 14 Oct 2022 20:48:24 +0000 Subject: [PATCH 0655/1699] [firebase-release] Removed change log and reset repo after 11.14.4 release --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03daa5adcd5..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +0,0 @@ -- Fixes a crash in integer params when a default value is selected in the prompt. (#5118) -- Fixes error handling for auth blocking functions. -- Fixes bug preventing Storage Rules from updating when ruleset compilation completed successfully but with warnings From 8079f7c785fcfb701aa83927ef8c8fd0343c50c5 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 17 Oct 2022 21:32:56 -0700 Subject: [PATCH 0656/1699] Various fixes and improvements. (#5139) * Make web frameworks public (#5136) * We don't need no public dir (#5142) * We don't need no public dir * Add docs * Add changelog Co-authored-by: Thomas Bouldin --- CHANGELOG.md | 1 + src/experiments.ts | 4 +++- src/frameworks/next/index.ts | 29 ++++++++++++++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..8574c68ed0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +Fix a bug where next.js applications would fail to deploy if they did not have a public dir (#5142) diff --git a/src/experiments.ts b/src/experiments.ts index f515afe761b..f1898b1ed6b 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -81,10 +81,12 @@ export const ALL_EXPERIMENTS = experiments({ shortDescription: "Native support for popular web frameworks", fullDescription: "Adds support for popular web frameworks such as Next.js " + - "Nuxt, Netlify, Angular, and Vite-compatible frameworks. Firebase is " + + "Angular, React, Svelte, and Vite-compatible frameworks. Firebase is " + "committed to support these platforms long-term, but a manual migration " + "may be required when the non-experimental support for these frameworks " + "is released", + docsUri: "https://firebase.google.com/docs/hosting/frameworks-overview", + public: true, }, pintags: { shortDescription: "Adds the pinTag option to Run and Functions rewrites", diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index a24d30db5b5..b79c5a0510f 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -51,6 +51,9 @@ function getNextVersion(cwd: string) { return findDependency("next", { cwd, depth: 0, omitDev: false })?.version; } +/** + * Returns whether this codebase is a Next.js backend. + */ export async function discover(dir: string) { if (!(await pathExists(join(dir, "package.json")))) return; if (!(await pathExists("next.config.js")) && !getNextVersion(dir)) return; @@ -58,6 +61,9 @@ export async function discover(dir: string) { return { mayWantBackend: true, publicDirectory: join(dir, "public") }; } +/** + * Build a next.js application. + */ export async function build(dir: string): Promise { const { default: nextBuild } = relativeRequire(dir, "next/dist/build"); @@ -133,6 +139,9 @@ export async function build(dir: string): Promise { return { wantsBackend, headers, redirects, rewrites }; } +/** + * Utility method used during project initialization. + */ export async function init(setup: any) { const language = await promptOnce({ type: "list", @@ -148,6 +157,9 @@ export async function init(setup: any) { ); } +/** + * Create a directory for SSG content. + */ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: string) { const { distDir } = await getConfig(sourceDir); const exportDetailPath = join(sourceDir, distDir, "export-detail.json"); @@ -157,8 +169,11 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin if (exportDetailJson?.success) { copy(exportDetailJson.outDirectory, destDir); } else { + const publicPath = join(sourceDir, "public"); await mkdir(join(destDir, "_next", "static"), { recursive: true }); - await copy(join(sourceDir, "public"), destDir); + if (await pathExists(publicPath)) { + await copy(publicPath, destDir); + } await copy(join(sourceDir, distDir, "static"), join(destDir, "_next", "static")); const serverPagesDir = join(sourceDir, distDir, "server", "pages"); @@ -202,6 +217,9 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin } } +/** + * Create a directory for SSR content. + */ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) { const { distDir } = await getConfig(sourceDir); const packageJsonBuffer = await readFile(join(sourceDir, "package.json")); @@ -227,13 +245,18 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: st platform: "node", }); } - await mkdir(join(destDir, "public")); + if (await pathExists(join(sourceDir, "public"))) { + await mkdir(join(destDir, "public")); + await copy(join(sourceDir, "public"), join(destDir, "public")); + } await mkdirp(join(destDir, distDir)); - await copy(join(sourceDir, "public"), join(destDir, "public")); await copy(join(sourceDir, distDir), join(destDir, distDir)); return { packageJson, frameworksEntry: "next.js" }; } +/** + * Create a dev server. + */ export async function getDevModeHandle(dir: string) { const { default: next } = relativeRequire(dir, "next"); const nextApp = next({ From dad66d436bbc2bb9db9bb3d94dcb54073c51a4e5 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 18 Oct 2022 13:10:24 +0000 Subject: [PATCH 0657/1699] 11.15.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 209c58121fa..e699a4cf8cb 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.14.4", + "version": "11.15.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.14.4", + "version": "11.15.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 86aae2dc1d5..24b9b06986f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.14.4", + "version": "11.15.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 99b9d36ff9946941a230f81cdec254de935bc318 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 18 Oct 2022 13:10:37 +0000 Subject: [PATCH 0658/1699] [firebase-release] Removed change log and reset repo after 11.15.0 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8574c68ed0c..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -Fix a bug where next.js applications would fail to deploy if they did not have a public dir (#5142) From 60ddb12c8fd1c4107e94d5a4790685660aa36883 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:13:56 -0400 Subject: [PATCH 0659/1699] Update RTDB emulator version to 4.11.0 (#5150) --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..bdac0018789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +Release RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index a1b1aed3597..bbedb48a868 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -27,14 +27,14 @@ const CACHE_DIR = export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDetails } = { database: { - downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.10.0.jar"), + downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.11.0.jar"), version: "4.10.0", opts: { cacheDir: CACHE_DIR, remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.10.0.jar", - expectedSize: 34230230, - expectedChecksum: "e99b23f0e723813de4f4ea0e879b46b0", + "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.11.0.jar", + expectedSize: 34318940, + expectedChecksum: "311609538bd65666eb724ef47c2e6466", namePrefix: "firebase-database-emulator", }, }, From f29a7202e1a17e272f9f1617357d11367f7ce805 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:59:23 -0400 Subject: [PATCH 0660/1699] Update RTDB emulator version to 4.11.0 (2) (#5151) --- src/emulator/downloadableEmulators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index bbedb48a868..8732fb716e5 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -28,7 +28,7 @@ const CACHE_DIR = export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDetails } = { database: { downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.11.0.jar"), - version: "4.10.0", + version: "4.11.0", opts: { cacheDir: CACHE_DIR, remoteUrl: From 7775f4828c64c72bfb0760cac9c68db4e193ea0a Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Tue, 25 Oct 2022 10:04:21 -0700 Subject: [PATCH 0661/1699] Update the flag name in the warning message for singleProjectMode to match the actual flag (#5168) * Update the flag name in the warning message for singleProjectMode to match the actual flag --- src/emulator/auth/server.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/emulator/auth/server.ts b/src/emulator/auth/server.ts index 8ee6d0b2080..e63eef89086 100644 --- a/src/emulator/auth/server.ts +++ b/src/emulator/auth/server.ts @@ -373,9 +373,8 @@ export async function createApp( const errorString = `Multiple projectIds are not recommended in single project mode. ` + `Requested project ID ${projectId}, but the emulator is configured for ` + - `${defaultProjectId}. This warning will become an error in the future. To opt-out of ` + - `single project mode add/set the \'"single_project_mode"\' false' property in the` + - ` firebase.json emulators config.`; + `${defaultProjectId}. To opt-out of single project mode add/set the ` + + `\'"singleProjectMode"\' false' property in the firebase.json emulators config.`; EmulatorLogger.forEmulator(Emulators.AUTH).log("WARN", errorString); if (singleProjectMode === SingleProjectMode.ERROR) { throw new BadRequestError(errorString); From 7ea2400786e32a510485e6e3299356da978b9164 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:09:23 -0400 Subject: [PATCH 0662/1699] Use template for emulator update details (#5166) * Use template for emulator versions * Factor out all emulator update details --- src/emulator/downloadableEmulators.ts | 141 ++++++++++++++++---------- src/emulator/types.ts | 6 ++ 2 files changed, 91 insertions(+), 56 deletions(-) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 8732fb716e5..95821f8b304 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -4,6 +4,7 @@ import { DownloadableEmulatorCommand, DownloadableEmulatorDetails, EmulatorDownloadDetails, + EmulatorUpdateDetails, } from "./types"; import { Constants } from "./constants"; @@ -25,88 +26,116 @@ const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000; /* ms */ const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators"); -export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDetails } = { +const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDetails } = { database: { - downloadPath: path.join(CACHE_DIR, "firebase-database-emulator-v4.11.0.jar"), version: "4.11.0", + expectedSize: 34318940, + expectedChecksum: "311609538bd65666eb724ef47c2e6466", + }, + firestore: { + version: "1.15.1", + expectedSize: 61475851, + expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee", + }, + storage: { + version: "1.1.1", + expectedSize: 46448285, + expectedChecksum: "691982db4019d49d345a97151bdea7e2", + }, + ui: experiments.isEnabled("emulatoruisnapshot") + ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" } + : { + version: "1.11.1", + expectedSize: 3061713, + expectedChecksum: "a4944414518be206280b495f526f18bf", + }, + pubsub: { + version: "0.1.0", + expectedSize: 36623622, + expectedChecksum: "81704b24737d4968734d3e175f4cde71", + }, +}; + +export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDetails } = { + database: { + downloadPath: path.join( + CACHE_DIR, + `firebase-database-emulator-v${EMULATOR_UPDATE_DETAILS.database.version}.jar` + ), + version: EMULATOR_UPDATE_DETAILS.database.version, opts: { cacheDir: CACHE_DIR, - remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.11.0.jar", - expectedSize: 34318940, - expectedChecksum: "311609538bd65666eb724ef47c2e6466", + remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v${EMULATOR_UPDATE_DETAILS.database.version}.jar`, + expectedSize: EMULATOR_UPDATE_DETAILS.database.expectedSize, + expectedChecksum: EMULATOR_UPDATE_DETAILS.database.expectedChecksum, namePrefix: "firebase-database-emulator", }, }, firestore: { - downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.15.1.jar"), - version: "1.15.1", + downloadPath: path.join( + CACHE_DIR, + `cloud-firestore-emulator-v${EMULATOR_UPDATE_DETAILS.firestore.version}.jar` + ), + version: EMULATOR_UPDATE_DETAILS.firestore.version, opts: { cacheDir: CACHE_DIR, - remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.15.1.jar", - expectedSize: 61475851, - expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee", + remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v${EMULATOR_UPDATE_DETAILS.firestore.version}.jar`, + expectedSize: EMULATOR_UPDATE_DETAILS.firestore.expectedSize, + expectedChecksum: EMULATOR_UPDATE_DETAILS.firestore.expectedChecksum, namePrefix: "cloud-firestore-emulator", }, }, storage: { - downloadPath: path.join(CACHE_DIR, "cloud-storage-rules-runtime-v1.1.1.jar"), - version: "1.1.1", + downloadPath: path.join( + CACHE_DIR, + `cloud-storage-rules-runtime-v${EMULATOR_UPDATE_DETAILS.storage.version}.jar` + ), + version: EMULATOR_UPDATE_DETAILS.storage.version, opts: { cacheDir: CACHE_DIR, - remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-storage-rules-runtime-v1.1.1.jar", - expectedSize: 46448285, - expectedChecksum: "691982db4019d49d345a97151bdea7e2", + remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-storage-rules-runtime-v${EMULATOR_UPDATE_DETAILS.storage.version}.jar`, + expectedSize: EMULATOR_UPDATE_DETAILS.storage.expectedSize, + expectedChecksum: EMULATOR_UPDATE_DETAILS.storage.expectedChecksum, namePrefix: "cloud-storage-rules-emulator", }, }, - ui: experiments.isEnabled("emulatoruisnapshot") - ? { - version: "SNAPSHOT", - downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"), - unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"), - binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server", "server.js"), - opts: { - cacheDir: CACHE_DIR, - remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip", - expectedSize: -1, - expectedChecksum: "", - skipCache: true, - skipChecksumAndSize: true, - namePrefix: "ui", - }, - } - : { - version: "1.11.1", - downloadPath: path.join(CACHE_DIR, "ui-v1.11.1.zip"), - unzipDir: path.join(CACHE_DIR, "ui-v1.11.1"), - binaryPath: path.join(CACHE_DIR, "ui-v1.11.1", "server", "server.js"), - opts: { - cacheDir: CACHE_DIR, - remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.11.1.zip", - expectedSize: 3061713, - expectedChecksum: "a4944414518be206280b495f526f18bf", - namePrefix: "ui", - }, - }, + ui: { + version: EMULATOR_UPDATE_DETAILS.ui.version, + downloadPath: path.join(CACHE_DIR, `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}.zip`), + unzipDir: path.join(CACHE_DIR, `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}`), + binaryPath: path.join( + CACHE_DIR, + `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}`, + "server", + "server.js" + ), + opts: { + cacheDir: CACHE_DIR, + remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v${EMULATOR_UPDATE_DETAILS.ui.version}.zip`, + expectedSize: EMULATOR_UPDATE_DETAILS.ui.expectedSize, + expectedChecksum: EMULATOR_UPDATE_DETAILS.ui.expectedChecksum, + skipCache: experiments.isEnabled("emulatoruisnapshot"), + skipChecksumAndSize: experiments.isEnabled("emulatoruisnapshot"), + namePrefix: "ui", + }, + }, pubsub: { - downloadPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0.zip"), - version: "0.1.0", - unzipDir: path.join(CACHE_DIR, "pubsub-emulator-0.1.0"), + downloadPath: path.join( + CACHE_DIR, + `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}.zip` + ), + version: EMULATOR_UPDATE_DETAILS.pubsub.version, + unzipDir: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`), binaryPath: path.join( CACHE_DIR, - "pubsub-emulator-0.1.0", + `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`, `pubsub-emulator/bin/cloud-pubsub-emulator${process.platform === "win32" ? ".bat" : ""}` ), opts: { cacheDir: CACHE_DIR, - remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.1.0.zip", - expectedSize: 36623622, - expectedChecksum: "81704b24737d4968734d3e175f4cde71", + remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}.zip`, + expectedSize: EMULATOR_UPDATE_DETAILS.pubsub.expectedSize, + expectedChecksum: EMULATOR_UPDATE_DETAILS.pubsub.expectedChecksum, namePrefix: "pubsub-emulator", }, }, diff --git a/src/emulator/types.ts b/src/emulator/types.ts index 78d5c7475aa..97d19fa9703 100644 --- a/src/emulator/types.ts +++ b/src/emulator/types.ts @@ -159,6 +159,12 @@ export interface EmulatorDownloadOptions { skipCache?: boolean; } +export interface EmulatorUpdateDetails { + version: string; + expectedSize: number; + expectedChecksum: string; +} + export interface EmulatorDownloadDetails { opts: EmulatorDownloadOptions; From a81ad120ba7f5a7152bcfc7696a229c6f35c4e46 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 25 Oct 2022 12:45:29 -0700 Subject: [PATCH 0663/1699] Track which web framework is deployed (#5140) --- src/deploy/index.ts | 7 ++++++- src/frameworks/index.ts | 20 +++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/deploy/index.ts b/src/deploy/index.ts index e1e885f8e9d..fc07e21d43d 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -61,10 +61,11 @@ export const deploy = async function ( if (targetNames.includes("hosting")) { const config = options.config.get("hosting"); + let deployedFrameworks: string[] = []; if (Array.isArray(config) ? config.some((it) => it.source) : config.source) { experiments.assertEnabled("webframeworks", "deploy a web framework to hosting"); const usedToTargetFunctions = targetNames.includes("functions"); - await prepareFrameworks(targetNames, context, options); + deployedFrameworks = await prepareFrameworks(targetNames, context, options); const nowTargetsFunctions = targetNames.includes("functions"); if (nowTargetsFunctions && !usedToTargetFunctions) { if (context.hostingChannel && !experiments.isEnabled("pintags")) { @@ -74,7 +75,11 @@ export const deploy = async function ( } await requirePermissions(TARGET_PERMISSIONS["functions"]); } + } else { + const count = Array.isArray(config) ? config.length : 1; + deployedFrameworks = Array(count).fill("classic"); } + await Promise.all(deployedFrameworks.map((framework) => track("hosting_deploy", framework))); } for (const targetName of targetNames) { diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index ad0df66f597..3b4bcd2ccdd 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -248,7 +248,7 @@ export async function prepareFrameworks( context: any, options: any, emulators: EmulatorInfo[] = [] -) { +): Promise { // `firebase-frameworks` requires Node >= 16. We must check for this to avoid horrible errors. const nodeVersion = process.version; if (!semver.satisfies(nodeVersion, ">=16.0.0")) { @@ -257,6 +257,7 @@ export async function prepareFrameworks( ); } + const deployedFrameworks: string[] = []; const project = needProjectId(context); const { projectRoot } = options; const account = getProjectDefaultAccount(projectRoot); @@ -282,10 +283,15 @@ export async function prepareFrameworks( } const configs = hostingConfig(options); let firebaseDefaults: FirebaseDefaults | undefined = undefined; - if (configs.length === 0) return; + if (configs.length === 0) { + return deployedFrameworks; + } for (const config of configs) { const { source, site, public: publicDir } = config; - if (!source) continue; + if (!source) { + deployedFrameworks.push("classic"); + continue; + } config.rewrites ||= []; config.redirects ||= []; config.headers ||= []; @@ -293,8 +299,9 @@ export async function prepareFrameworks( const dist = join(projectRoot, ".firebase", site); const hostingDist = join(dist, "hosting"); const functionsDist = join(dist, "functions"); - if (publicDir) + if (publicDir) { throw new Error(`hosting.public and hosting.source cannot both be set in firebase.json`); + } const getProjectPath = (...args: string[]) => join(projectRoot, source, ...args); const functionName = `ssr${site.toLowerCase().replace(/-/g, "")}`; const usesFirebaseAdminSdk = !!findDependency("firebase-admin", { cwd: getProjectPath() }); @@ -385,8 +392,9 @@ export async function prepareFrameworks( // Attach the handle to options, it will be used when spinning up superstatic options.frameworksDevModeHandle = devModeHandle; // null is the dev-mode entry for firebase-framework-tools - if (mayWantBackend && firebaseDefaults) + if (mayWantBackend && firebaseDefaults) { codegenFunctionsDirectory = codegenDevModeFunctionsDirectory; + } } else { const { wantsBackend = false, @@ -403,6 +411,7 @@ export async function prepareFrameworks( config.public = relative(projectRoot, hostingDist); if (wantsBackend) codegenFunctionsDirectory = codegenProdModeFunctionsDirectory; } + deployedFrameworks.push(`${framework}${codegenFunctionsDirectory ? "_ssr" : ""}`); if (codegenFunctionsDirectory) { if (firebaseDefaults) firebaseDefaults._authTokenSyncURL = "/__session"; @@ -546,6 +555,7 @@ exports.ssr = onRequest((req, res) => server.then(it => it.handle(req, res))); }); } } + return deployedFrameworks; } function codegenDevModeFunctionsDirectory() { From fd7cc7653c5dd891aca13ca316ed226752a66dec Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 25 Oct 2022 14:39:37 -0700 Subject: [PATCH 0664/1699] downgrade superstatic to v8 (#5172) * downgrade superstatic to v8 * fix superstatic importing --- CHANGELOG.md | 3 +- npm-shrinkwrap.json | 488 ++++++++++++++++++++++++++++++++++++------- package.json | 2 +- src/serve/hosting.ts | 10 +- 4 files changed, 418 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdac0018789..513ddd4e434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ -Release RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`. +- Releases RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`. +- Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues in the Hosting emulator. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e699a4cf8cb..a8c9e68dfb4 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -57,7 +57,7 @@ "stream-chain": "^2.2.4", "stream-json": "^1.7.3", "strip-ansi": "^6.0.1", - "superstatic": "^9.0.0", + "superstatic": "^8.0.0", "tar": "^6.1.11", "tcp-port-used": "^1.0.2", "tmp": "^0.2.1", @@ -2531,8 +2531,7 @@ "node_modules/@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "node_modules/@types/configstore": { "version": "4.0.0", @@ -3612,6 +3611,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -4052,7 +4059,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, "dependencies": { "ansi-align": "^3.0.0", "camelcase": "^5.3.1", @@ -4074,7 +4080,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, "dependencies": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -4090,7 +4095,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4103,7 +4107,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4114,14 +4117,12 @@ "node_modules/boxen/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==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/boxen/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, "engines": { "node": ">=8" } @@ -4130,7 +4131,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4454,7 +4454,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, "engines": { "node": ">=6" } @@ -4846,6 +4845,14 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "node_modules/compare-semver": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/compare-semver/-/compare-semver-1.1.0.tgz", + "integrity": "sha512-AENcdfhxsMCzzl+QRdOwMQeA8tZBEEacAmA4pGPoyco27G9sIaM98WNYkcToC9O0wIx1vE+1ErmaM4t0/fXhMw==", + "dependencies": { + "semver": "^5.0.1" + } + }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -7432,6 +7439,25 @@ "toxic": "^1.0.0" } }, + "node_modules/global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "dependencies": { + "ini": "1.3.7" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -7982,6 +8008,17 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -8518,6 +8555,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dependencies": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -8532,6 +8584,14 @@ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "optional": true }, + "node_modules/is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -13029,6 +13089,28 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", + "integrity": "sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==", + "dependencies": { + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -13193,47 +13275,81 @@ } }, "node_modules/superstatic": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-9.0.0.tgz", - "integrity": "sha512-4rvzTZdqBPtCjeo/V4YkbBeDnHxI2+3jP1FHGzvTeDswq+HQFB7l3JTjq31BfyJFTogn8JmbDW9sKOeBUGDAhg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-8.0.0.tgz", + "integrity": "sha512-PqlA2xuEwOlRZsknl58A/rZEmgCUcfWIFec0bn10wYE5/tbMhEbMXGHCYDppiXLXcuhGHyOp1IimM2hLqkLLuw==", "dependencies": { "basic-auth-connect": "^1.0.0", - "commander": "^9.4.0", + "chalk": "^1.1.3", + "commander": "^9.2.0", + "compare-semver": "^1.0.0", "compression": "^1.7.0", - "connect": "^3.7.0", + "connect": "^3.6.2", "destroy": "^1.0.4", "fast-url-parser": "^1.1.3", "glob-slasher": "^1.0.1", "is-url": "^1.2.2", "join-path": "^1.1.1", "lodash": "^4.17.19", - "mime-types": "^2.1.35", - "minimatch": "^5.1.0", + "mime-types": "^2.1.16", + "minimatch": "^3.0.4", "morgan": "^1.8.2", "on-finished": "^2.2.0", "on-headers": "^1.0.0", "path-to-regexp": "^1.8.0", "router": "^1.3.1", - "update-notifier": "^5.1.0" + "string-length": "^1.0.0", + "update-notifier": "^4.1.1" }, "bin": { - "superstatic": "lib/bin/server.js" + "superstatic": "bin/server" }, "engines": { - "node": "^14.18.0 || >=16.4.0" + "node": ">= 12.20" }, "optionalDependencies": { - "re2": "^1.17.7" + "re2": "^1.15.8" } }, - "node_modules/superstatic/node_modules/brace-expansion": { + "node_modules/superstatic/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superstatic/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superstatic/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dependencies": { - "balanced-match": "^1.0.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, + "node_modules/superstatic/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==" + }, "node_modules/superstatic/node_modules/commander": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", @@ -13242,22 +13358,19 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/superstatic/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==", + "engines": { + "node": ">=8" + } + }, "node_modules/superstatic/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "node_modules/superstatic/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/superstatic/node_modules/path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -13266,6 +13379,88 @@ "isarray": "0.0.1" } }, + "node_modules/superstatic/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superstatic/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/superstatic/node_modules/update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "dependencies": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "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", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/superstatic/node_modules/update-notifier/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/superstatic/node_modules/update-notifier/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/superstatic/node_modules/update-notifier/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supertest": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.2.3.tgz", @@ -13605,7 +13800,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", - "dev": true, "engines": { "node": ">=8" }, @@ -13882,7 +14076,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, "engines": { "node": ">=8" } @@ -16870,8 +17063,7 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/configstore": { "version": "4.0.0", @@ -17761,6 +17953,11 @@ } } }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -18128,7 +18325,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, "requires": { "ansi-align": "^3.0.0", "camelcase": "^5.3.1", @@ -18144,7 +18340,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -18154,7 +18349,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -18164,7 +18358,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -18172,20 +18365,17 @@ "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==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "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 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -18418,8 +18608,7 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "camelcase-keys": { "version": "6.2.2", @@ -18706,6 +18895,14 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compare-semver": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/compare-semver/-/compare-semver-1.1.0.tgz", + "integrity": "sha512-AENcdfhxsMCzzl+QRdOwMQeA8tZBEEacAmA4pGPoyco27G9sIaM98WNYkcToC9O0wIx1vE+1ErmaM4t0/fXhMw==", + "requires": { + "semver": "^5.0.1" + } + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -20719,6 +20916,21 @@ "toxic": "^1.0.0" } }, + "global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "requires": { + "ini": "1.3.7" + }, + "dependencies": { + "ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" + } + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -21165,6 +21377,14 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "requires": { + "ansi-regex": "^2.0.0" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -21566,6 +21786,15 @@ "is-extglob": "^2.1.1" } }, + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -21577,6 +21806,11 @@ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "optional": true }, + "is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -25104,6 +25338,24 @@ "safe-buffer": "~5.1.0" } }, + "string-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", + "integrity": "sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==", + "requires": { + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -25218,57 +25470,79 @@ } }, "superstatic": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-9.0.0.tgz", - "integrity": "sha512-4rvzTZdqBPtCjeo/V4YkbBeDnHxI2+3jP1FHGzvTeDswq+HQFB7l3JTjq31BfyJFTogn8JmbDW9sKOeBUGDAhg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-8.0.0.tgz", + "integrity": "sha512-PqlA2xuEwOlRZsknl58A/rZEmgCUcfWIFec0bn10wYE5/tbMhEbMXGHCYDppiXLXcuhGHyOp1IimM2hLqkLLuw==", "requires": { "basic-auth-connect": "^1.0.0", - "commander": "^9.4.0", + "chalk": "^1.1.3", + "commander": "^9.2.0", + "compare-semver": "^1.0.0", "compression": "^1.7.0", - "connect": "^3.7.0", + "connect": "^3.6.2", "destroy": "^1.0.4", "fast-url-parser": "^1.1.3", "glob-slasher": "^1.0.1", "is-url": "^1.2.2", "join-path": "^1.1.1", "lodash": "^4.17.19", - "mime-types": "^2.1.35", - "minimatch": "^5.1.0", + "mime-types": "^2.1.16", + "minimatch": "^3.0.4", "morgan": "^1.8.2", "on-finished": "^2.2.0", "on-headers": "^1.0.0", "path-to-regexp": "^1.8.0", - "re2": "^1.17.7", + "re2": "^1.15.8", "router": "^1.3.1", - "update-notifier": "^5.1.0" + "string-length": "^1.0.0", + "update-notifier": "^4.1.1" }, "dependencies": { - "brace-expansion": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "requires": { + "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" + } + }, + "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { - "balanced-match": "^1.0.0" + "color-name": "~1.1.4" } }, + "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==" + }, "commander": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==" }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "requires": { - "brace-expansion": "^2.0.1" - } - }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -25276,6 +25550,66 @@ "requires": { "isarray": "0.0.1" } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==" + }, + "update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "requires": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "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", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } } } }, @@ -25543,8 +25877,7 @@ "term-size": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", - "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", - "dev": true + "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==" }, "terser": { "version": "5.15.0", @@ -25746,8 +26079,7 @@ "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" }, "type-is": { "version": "1.6.18", diff --git a/package.json b/package.json index 24b9b06986f..35c4537e46b 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "stream-chain": "^2.2.4", "stream-json": "^1.7.3", "strip-ansi": "^6.0.1", - "superstatic": "^9.0.0", + "superstatic": "^8.0.0", "tar": "^6.1.11", "tcp-port-used": "^1.0.2", "tmp": "^0.2.1", diff --git a/src/serve/hosting.ts b/src/serve/hosting.ts index 8b75cbcea6a..e2bb51cbdd0 100644 --- a/src/serve/hosting.ts +++ b/src/serve/hosting.ts @@ -1,8 +1,8 @@ const morgan = require("morgan"); -import { IncomingMessage, ServerResponse } from "http"; -import { server as superstatic } from "superstatic"; +const { server: superstatic } = require("superstatic"); // eslint-disable-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment import * as clc from "colorette"; import { isIPv4 } from "net"; +import { NextFunction, Request, Response } from "express"; import { detectProjectRoot } from "../detectProjectRoot"; import { FirebaseError } from "../error"; @@ -52,13 +52,13 @@ function startServer(options: any, config: any, port: number, init: TemplateServ const server = superstatic({ debug: false, port: port, - hostname: options.host, + host: options.host, config: config, compression: true, - cwd: detectProjectRoot(options) || undefined, + cwd: detectProjectRoot(options), stack: "strict", before: { - files: (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => { + files: (req: Request, res: Response, next: NextFunction) => { // We do these in a single method to ensure order of operations morganMiddleware(req, res, () => null); firebaseMiddleware(req, res, next); From d67881de4848b52f821d993227ab62458791215f Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 25 Oct 2022 14:52:39 -0700 Subject: [PATCH 0665/1699] build:publish should also run copyfiles (#5173) * build:publish should also run copyfiles * changelog * formatting is hard * Update CHANGELOG.md --- CHANGELOG.md | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 513ddd4e434..df80ce2f267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Releases RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`. -- Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues in the Hosting emulator. +- Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues on Windows. +- Fixes internal library that was not being correctly published. diff --git a/package.json b/package.json index 35c4537e46b..93cb8e3fa86 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "scripts": { "build": "tsc && npm run copyfiles", - "build:publish": "tsc --build tsconfig.publish.json", + "build:publish": "tsc --build tsconfig.publish.json && npm run copyfiles", "build:watch": "npm run build && tsc --watch", "clean": "rimraf lib dev", "copyfiles": "node -e \"const fs = require('fs'); fs.mkdirSync('./lib', {recursive:true}); fs.copyFileSync('./src/dynamicImport.js', './lib/dynamicImport.js')\"", From 2b4261ef801a5e95ef62d05297a2d2cfddee20f3 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 26 Oct 2022 09:29:57 -0700 Subject: [PATCH 0666/1699] Provisioning checks should be best effort (#5163) --- CHANGELOG.md | 1 + src/extensions/provisioningHelper.ts | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df80ce2f267..7c58e867110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixes an issue where an error during product provisioning check would block `firebase deploy --only extensions` (#5074). - Releases RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`. - Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues on Windows. - Fixes internal library that was not being correctly published. diff --git a/src/extensions/provisioningHelper.ts b/src/extensions/provisioningHelper.ts index 3d71c9b4c95..ac4f91e3184 100644 --- a/src/extensions/provisioningHelper.ts +++ b/src/extensions/provisioningHelper.ts @@ -7,6 +7,7 @@ import { Client } from "../apiv2"; import { flattenArray } from "../functional"; import { FirebaseError } from "../error"; import { getExtensionSpec, InstanceSpec } from "../deploy/extensions/planner"; +import { logger } from "../logger"; /** Product for which provisioning can be (or is) deferred */ export enum DeferredProduct { @@ -55,12 +56,16 @@ async function checkProducts(projectId: string, usedProducts: DeferredProduct[]) if (usedProducts.includes(DeferredProduct.AUTH)) { isAuthProvisionedPromise = isAuthProvisioned(projectId); } - - if (isStorageProvisionedPromise && !(await isStorageProvisionedPromise)) { - needProvisioning.push(DeferredProduct.STORAGE); - } - if (isAuthProvisionedPromise && !(await isAuthProvisionedPromise)) { - needProvisioning.push(DeferredProduct.AUTH); + try { + if (isStorageProvisionedPromise && !(await isStorageProvisionedPromise)) { + needProvisioning.push(DeferredProduct.STORAGE); + } + if (isAuthProvisionedPromise && !(await isAuthProvisionedPromise)) { + needProvisioning.push(DeferredProduct.AUTH); + } + } catch (err: any) { + // If a provisioning check throws, we should fail open since this is best effort. + logger.debug(`Error while checking product provisioning, failing open: ${err}`); } if (needProvisioning.length > 0) { From 793253ff4516e1d78ea14d8ef162a9540abc32cc Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Wed, 26 Oct 2022 15:50:16 -0400 Subject: [PATCH 0667/1699] Add `--disable-triggers` flag to database write commands (#5179) * Add --disable-triggers flag to database write commands * Update unit tests * Add changelog * Address PR feedback: add test, make constructor arg required --- CHANGELOG.md | 1 + src/commands/database-push.ts | 5 +++++ src/commands/database-remove.ts | 3 ++- src/commands/database-set.ts | 5 +++++ src/commands/database-update.ts | 5 +++++ src/database/remove.ts | 5 +++-- src/database/removeRemote.ts | 10 ++++++++-- src/test/database/remove.spec.ts | 18 ++++++++++++++---- src/test/database/removeRemote.spec.ts | 23 ++++++++++++++++++----- 9 files changed, 61 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c58e867110..0a147d85e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Releases RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`. - Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues on Windows. - Fixes internal library that was not being correctly published. +- Adds `--disable-triggers` flag to RTDB write commands. diff --git a/src/commands/database-push.ts b/src/commands/database-push.ts index 9f1e3ed234c..7777f36dbfa 100644 --- a/src/commands/database-push.ts +++ b/src/commands/database-push.ts @@ -21,6 +21,7 @@ export const command = new Command("database:push [infile]") "--instance ", "use the database .firebaseio.com (if omitted, use default database instance)" ) + .option("--disable-triggers", "suppress any Cloud functions triggered by this operation") .before(requirePermissions, ["firebasedatabase.instances.update"]) .before(requireDatabaseInstance) .before(populateInstanceDetails) @@ -33,6 +34,9 @@ export const command = new Command("database:push [infile]") utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin); const origin = realtimeOriginOrEmulatorOrCustomUrl(options.instanceDetails.databaseUrl); const u = new URL(utils.getDatabaseUrl(origin, options.instance, path + ".json")); + if (options.disableTriggers) { + u.searchParams.set("disableTriggers", "true"); + } if (!infile && !options.data) { utils.explainStdin(); @@ -46,6 +50,7 @@ export const command = new Command("database:push [infile]") method: "POST", path: u.pathname, body: inStream, + queryParams: u.searchParams, }); } catch (err: any) { logger.debug(err); diff --git a/src/commands/database-remove.ts b/src/commands/database-remove.ts index 6b0012090c9..04a38e46146 100644 --- a/src/commands/database-remove.ts +++ b/src/commands/database-remove.ts @@ -17,6 +17,7 @@ export const command = new Command("database:remove ") "--instance ", "use the database .firebaseio.com (if omitted, use default database instance)" ) + .option("--disable-triggers", "suppress any Cloud functions triggered by this operation") .before(requirePermissions, ["firebasedatabase.instances.update"]) .before(requireDatabaseInstance) .before(populateInstanceDetails) @@ -40,7 +41,7 @@ export const command = new Command("database:remove ") return utils.reject("Command aborted.", { exit: 1 }); } - const removeOps = new DatabaseRemove(options.instance, path, origin); + const removeOps = new DatabaseRemove(options.instance, path, origin, !!options.disableTriggers); await removeOps.execute(); utils.logSuccess("Data removed successfully"); }); diff --git a/src/commands/database-set.ts b/src/commands/database-set.ts index 6c9d788e144..67304569c0f 100644 --- a/src/commands/database-set.ts +++ b/src/commands/database-set.ts @@ -23,6 +23,7 @@ export const command = new Command("database:set [infile]") "--instance ", "use the database .firebaseio.com (if omitted, use default database instance)" ) + .option("--disable-triggers", "suppress any Cloud functions triggered by this operation") .before(requirePermissions, ["firebasedatabase.instances.update"]) .before(requireDatabaseInstance) .before(populateInstanceDetails) @@ -34,6 +35,9 @@ export const command = new Command("database:set [infile]") const origin = realtimeOriginOrEmulatorOrCustomUrl(options.instanceDetails.databaseUrl); const dbPath = utils.getDatabaseUrl(origin, options.instance, path); const dbJsonURL = new URL(utils.getDatabaseUrl(origin, options.instance, path + ".json")); + if (options.disableTriggers) { + dbJsonURL.searchParams.set("disableTriggers", "true"); + } const confirm = await promptOnce( { @@ -61,6 +65,7 @@ export const command = new Command("database:set [infile]") method: "PUT", path: dbJsonURL.pathname, body: inStream, + queryParams: dbJsonURL.searchParams, }); } catch (err: any) { logger.debug(err); diff --git a/src/commands/database-update.ts b/src/commands/database-update.ts index 01f8dc21623..f1b6d494c9c 100644 --- a/src/commands/database-update.ts +++ b/src/commands/database-update.ts @@ -23,6 +23,7 @@ export const command = new Command("database:update [infile]") "--instance ", "use the database .firebaseio.com (if omitted, use default database instance)" ) + .option("--disable-triggers", "suppress any Cloud functions triggered by this operation") .before(requirePermissions, ["firebasedatabase.instances.update"]) .before(requireDatabaseInstance) .before(populateInstanceDetails) @@ -51,6 +52,9 @@ export const command = new Command("database:update [infile]") (infile && fs.createReadStream(infile)) || process.stdin; const jsonUrl = new URL(utils.getDatabaseUrl(origin, options.instance, path + ".json")); + if (options.disableTriggers) { + jsonUrl.searchParams.set("disableTriggers", "true"); + } if (!infile && !options.data) { utils.explainStdin(); @@ -62,6 +66,7 @@ export const command = new Command("database:update [infile]") method: "PATCH", path: jsonUrl.pathname, body: inStream, + queryParams: jsonUrl.searchParams, }); } catch (err: any) { throw new FirebaseError("Unexpected error while setting data"); diff --git a/src/database/remove.ts b/src/database/remove.ts index 9f5297d8019..2d0d863731c 100644 --- a/src/database/remove.ts +++ b/src/database/remove.ts @@ -29,10 +29,11 @@ export default class DatabaseRemove { * @param instance RTBD instance ID. * @param path path to delete. * @param host db host. + * @param disableTriggers if true, suppresses any Cloud functions that would be triggered by this operation. */ - constructor(instance: string, path: string, host: string) { + constructor(instance: string, path: string, host: string, disableTriggers: boolean) { this.path = path; - this.remote = new RTDBRemoveRemote(instance, host); + this.remote = new RTDBRemoveRemote(instance, host, disableTriggers); this.deleteJobStack = new Stack({ name: "delete stack", concurrency: 1, diff --git a/src/database/removeRemote.ts b/src/database/removeRemote.ts index 2c655651e5c..60885f41151 100644 --- a/src/database/removeRemote.ts +++ b/src/database/removeRemote.ts @@ -22,10 +22,12 @@ export class RTDBRemoveRemote implements RemoveRemote { private instance: string; private host: string; private apiClient: Client; + private disableTriggers: boolean; - constructor(instance: string, host: string) { + constructor(instance: string, host: string, disableTriggers: boolean) { this.instance = instance; this.host = host; + this.disableTriggers = disableTriggers; const url = new URL(utils.getDatabaseUrl(this.host, this.instance, "/")); this.apiClient = new Client({ urlPrefix: url.origin, auth: true }); @@ -46,7 +48,11 @@ export class RTDBRemoveRemote implements RemoveRemote { private async patch(path: string, body: any, note: string): Promise { const t0 = Date.now(); const url = new URL(utils.getDatabaseUrl(this.host, this.instance, path + ".json")); - const queryParams = { print: "silent", writeSizeLimit: "tiny" }; + const queryParams = { + print: "silent", + writeSizeLimit: "tiny", + disableTriggers: this.disableTriggers.toString(), + }; const res = await this.apiClient.request({ method: "PATCH", path: url.pathname, diff --git a/src/test/database/remove.spec.ts b/src/test/database/remove.spec.ts index 4a8c4998fc7..9938a38be03 100644 --- a/src/test/database/remove.spec.ts +++ b/src/test/database/remove.spec.ts @@ -8,7 +8,7 @@ const HOST = "https://firebaseio.com"; describe("DatabaseRemove", () => { it("should remove tiny tree", async () => { const fakeDb = new FakeRemoveRemote({ c: 1 }); - const removeOps = new DatabaseRemove("test-tiny-tree", "/", HOST); + const removeOps = new DatabaseRemove("test-tiny-tree", "/", HOST, /* disableTriggers= */ false); removeOps.remote = fakeDb; await removeOps.execute(); expect(fakeDb.data).to.eql(null); @@ -29,7 +29,7 @@ describe("DatabaseRemove", () => { const fakeList = new FakeListRemote(data); const fakeDb = new FakeRemoveRemote(data); - const removeOps = new DatabaseRemove("test-sub-path", "/a", HOST); + const removeOps = new DatabaseRemove("test-sub-path", "/a", HOST, /* disableTriggers= */ false); removeOps.remote = fakeDb; removeOps.listRemote = fakeList; await removeOps.execute(); @@ -57,7 +57,12 @@ describe("DatabaseRemove", () => { const data = buildData(3, 5); const fakeDb = new FakeRemoveRemote(data, threshold); const fakeLister = new FakeListRemote(data); - const removeOps = new DatabaseRemove("test-nested-tree", "/", HOST); + const removeOps = new DatabaseRemove( + "test-nested-tree", + "/", + HOST, + /* disableTriggers= */ false + ); removeOps.remote = fakeDb; removeOps.listRemote = fakeLister; await removeOps.execute(); @@ -68,7 +73,12 @@ describe("DatabaseRemove", () => { const data = buildData(1232, 1); const fakeDb = new FakeRemoveRemote(data, threshold); const fakeList = new FakeListRemote(data); - const removeOps = new DatabaseRemove("test-remover", "/", HOST); + const removeOps = new DatabaseRemove( + "test-remover", + "/", + HOST, + /* disableTriggers= */ false + ); removeOps.remote = fakeDb; removeOps.listRemote = fakeList; await removeOps.execute(); diff --git a/src/test/database/removeRemote.spec.ts b/src/test/database/removeRemote.spec.ts index 869c6a741dd..3f8357df059 100644 --- a/src/test/database/removeRemote.spec.ts +++ b/src/test/database/removeRemote.spec.ts @@ -7,7 +7,7 @@ import { RTDBRemoveRemote } from "../../database/removeRemote"; describe("RemoveRemote", () => { const instance = "fake-db"; const host = "https://firebaseio.com"; - const remote = new RTDBRemoveRemote(instance, host); + const remote = new RTDBRemoveRemote(instance, host, /* disableTriggers= */ false); const serverUrl = utils.getDatabaseUrl(host, instance, ""); afterEach(() => { @@ -17,7 +17,7 @@ describe("RemoveRemote", () => { it("should return true when patch is small", () => { nock(serverUrl) .patch("/a/b.json") - .query({ print: "silent", writeSizeLimit: "tiny" }) + .query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "false" }) .reply(200, {}); return expect(remote.deletePath("/a/b")).to.eventually.eql(true); }); @@ -25,7 +25,7 @@ describe("RemoveRemote", () => { it("should return false whem patch is large", () => { nock(serverUrl) .patch("/a/b.json") - .query({ print: "silent", writeSizeLimit: "tiny" }) + .query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "false" }) .reply(400, { error: "Data requested exceeds the maximum size that can be accessed with a single request.", @@ -36,7 +36,7 @@ describe("RemoveRemote", () => { it("should return true when multi-path patch is small", () => { nock(serverUrl) .patch("/a/b.json") - .query({ print: "silent", writeSizeLimit: "tiny" }) + .query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "false" }) .reply(200, {}); return expect(remote.deleteSubPath("/a/b", ["1", "2", "3"])).to.eventually.eql(true); }); @@ -44,11 +44,24 @@ describe("RemoveRemote", () => { it("should return false when multi-path patch is large", () => { nock(serverUrl) .patch("/a/b.json") - .query({ print: "silent", writeSizeLimit: "tiny" }) + .query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "false" }) .reply(400, { error: "Data requested exceeds the maximum size that can be accessed with a single request.", }); return expect(remote.deleteSubPath("/a/b", ["1", "2", "3"])).to.eventually.eql(false); }); + + it("should send disableTriggers param", () => { + const remoteWithDisableTriggers = new RTDBRemoveRemote( + instance, + host, + /* disableTriggers= */ true + ); + nock(serverUrl) + .patch("/a/b.json") + .query({ print: "silent", writeSizeLimit: "tiny", disableTriggers: "true" }) + .reply(200, {}); + return expect(remoteWithDisableTriggers.deletePath("/a/b")).to.eventually.eql(true); + }); }); From 74c1b19bfb8b6daedc4b9aacc6aeec1daa53134d Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 27 Oct 2022 14:29:46 -0700 Subject: [PATCH 0668/1699] Inlined.web frameworks label (#5176) * Move HostingResolved out of firebaseConfig * Move metric to deploy code and add label * Add some smoke tests to the hosting pepare library --- src/deploy/hosting/context.ts | 2 +- src/deploy/hosting/prepare.ts | 23 ++++- src/deploy/index.ts | 7 +- src/firebaseConfig.ts | 11 +-- src/frameworks/index.ts | 9 +- src/hosting/config.ts | 11 ++- src/hosting/options.ts | 3 +- src/test/deploy/hosting/prepare.spec.ts | 118 ++++++++++++++++++++++++ 8 files changed, 155 insertions(+), 29 deletions(-) create mode 100644 src/test/deploy/hosting/prepare.spec.ts diff --git a/src/deploy/hosting/context.ts b/src/deploy/hosting/context.ts index 05a69b07633..889df7b03b9 100644 --- a/src/deploy/hosting/context.ts +++ b/src/deploy/hosting/context.ts @@ -1,4 +1,4 @@ -import { HostingResolved } from "../../firebaseConfig"; +import { HostingResolved } from "../../hosting/config"; import { Context as FunctionsContext } from "../functions/args"; export interface HostingDeploy { diff --git a/src/deploy/hosting/prepare.ts b/src/deploy/hosting/prepare.ts index effbf065b9d..639ae6c84aa 100644 --- a/src/deploy/hosting/prepare.ts +++ b/src/deploy/hosting/prepare.ts @@ -6,6 +6,7 @@ import { Context } from "./context"; import { Options } from "../../options"; import { HostingOptions } from "../../hosting/options"; import { zipIn } from "../../functional"; +import { track } from "../../track"; /** * Prepare creates versions for each Hosting site to be deployed. @@ -25,12 +26,24 @@ export async function prepare(context: Context, options: HostingOptions & Option return Promise.resolve(); } - const version: Omit = { - status: "CREATED", - labels: deploymentTool.labels(), - }; const versions = await Promise.all( - configs.map((config) => api.createVersion(config.site, version)) + configs.map(async (config) => { + const labels: Record = { + ...deploymentTool.labels(), + }; + if (config.webFramework) { + labels["firebase-web-framework"] = config.webFramework; + } + const version: Omit = { + status: "CREATED", + labels, + }; + const [, versionName] = await Promise.all([ + track("hosting_deploy", config.webFramework || "classic"), + api.createVersion(config.site, version), + ]); + return versionName; + }) ); context.hosting = { deploys: [], diff --git a/src/deploy/index.ts b/src/deploy/index.ts index fc07e21d43d..e1e885f8e9d 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -61,11 +61,10 @@ export const deploy = async function ( if (targetNames.includes("hosting")) { const config = options.config.get("hosting"); - let deployedFrameworks: string[] = []; if (Array.isArray(config) ? config.some((it) => it.source) : config.source) { experiments.assertEnabled("webframeworks", "deploy a web framework to hosting"); const usedToTargetFunctions = targetNames.includes("functions"); - deployedFrameworks = await prepareFrameworks(targetNames, context, options); + await prepareFrameworks(targetNames, context, options); const nowTargetsFunctions = targetNames.includes("functions"); if (nowTargetsFunctions && !usedToTargetFunctions) { if (context.hostingChannel && !experiments.isEnabled("pintags")) { @@ -75,11 +74,7 @@ export const deploy = async function ( } await requirePermissions(TARGET_PERMISSIONS["functions"]); } - } else { - const count = Array.isArray(config) ? config.length : 1; - deployedFrameworks = Array(count).fill("classic"); } - await Promise.all(deployedFrameworks.map((framework) => track("hosting_deploy", framework))); } for (const targetName of targetNames) { diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index 58c9879d396..9a56e09a081 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -10,7 +10,7 @@ import { RequireAtLeastOne } from "./metaprogramming"; // should be sourced from - https://github.com/firebase/firebase-tools/blob/master/src/deploy/functions/runtimes/index.ts#L15 type CloudFunctionRuntimes = "nodejs10" | "nodejs12" | "nodejs14" | "nodejs16"; -type Deployable = { +export type Deployable = { predeploy?: string | string[]; postdeploy?: string | string[]; }; @@ -67,7 +67,7 @@ export type HostingHeaders = HostingSource & { }[]; }; -type HostingBase = { +export type HostingBase = { public?: string; source?: string; ignore?: string[]; @@ -101,13 +101,6 @@ export type HostingMultiple = (HostingBase & }> & Deployable)[]; -// After validating a HostingMultiple and resolving targets, we will instead -// have a HostingResolved. -export type HostingResolved = HostingBase & { - site: string; - target?: string; -} & Deployable; - type StorageSingle = { rules: string; target?: string; diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 3b4bcd2ccdd..a3158d8e0db 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -248,7 +248,7 @@ export async function prepareFrameworks( context: any, options: any, emulators: EmulatorInfo[] = [] -): Promise { +): Promise { // `firebase-frameworks` requires Node >= 16. We must check for this to avoid horrible errors. const nodeVersion = process.version; if (!semver.satisfies(nodeVersion, ">=16.0.0")) { @@ -257,7 +257,6 @@ export async function prepareFrameworks( ); } - const deployedFrameworks: string[] = []; const project = needProjectId(context); const { projectRoot } = options; const account = getProjectDefaultAccount(projectRoot); @@ -284,12 +283,11 @@ export async function prepareFrameworks( const configs = hostingConfig(options); let firebaseDefaults: FirebaseDefaults | undefined = undefined; if (configs.length === 0) { - return deployedFrameworks; + return; } for (const config of configs) { const { source, site, public: publicDir } = config; if (!source) { - deployedFrameworks.push("classic"); continue; } config.rewrites ||= []; @@ -411,7 +409,7 @@ export async function prepareFrameworks( config.public = relative(projectRoot, hostingDist); if (wantsBackend) codegenFunctionsDirectory = codegenProdModeFunctionsDirectory; } - deployedFrameworks.push(`${framework}${codegenFunctionsDirectory ? "_ssr" : ""}`); + config.webFramework = `${framework}${codegenFunctionsDirectory ? "_ssr" : ""}`; if (codegenFunctionsDirectory) { if (firebaseDefaults) firebaseDefaults._authTokenSyncURL = "/__session"; @@ -555,7 +553,6 @@ exports.ssr = onRequest((req, res) => server.then(it => it.handle(req, res))); }); } } - return deployedFrameworks; } function codegenDevModeFunctionsDirectory() { diff --git a/src/hosting/config.ts b/src/hosting/config.ts index 6552cc04a08..c2707f4b0d8 100644 --- a/src/hosting/config.ts +++ b/src/hosting/config.ts @@ -5,7 +5,8 @@ import { FirebaseError } from "../error"; import { HostingMultiple, HostingSingle, - HostingResolved, + HostingBase, + Deployable, HostingRewrites, FunctionsRewrite, LegacyFunctionsRewrite, @@ -20,6 +21,14 @@ import * as path from "node:path"; import * as experiments from "../experiments"; import { logger } from "../logger"; +// After validating a HostingMultiple and resolving targets, we will instead +// have a HostingResolved. +export type HostingResolved = HostingBase & { + site: string; + target?: string; + webFramework?: string; +} & Deployable; + // assertMatches allows us to throw when an --only flag doesn't match a target // but an --except flag doesn't. Is this desirable behavior? function matchingConfigs( diff --git a/src/hosting/options.ts b/src/hosting/options.ts index 9cee6436858..2b54bf82f80 100644 --- a/src/hosting/options.ts +++ b/src/hosting/options.ts @@ -1,6 +1,7 @@ -import { FirebaseConfig, HostingResolved } from "../firebaseConfig"; +import { FirebaseConfig } from "../firebaseConfig"; import { Implements } from "../metaprogramming"; import { Options } from "../options"; +import { HostingResolved } from "./config"; /** * The set of fields that the Hosting codebase needs from Options. diff --git a/src/test/deploy/hosting/prepare.spec.ts b/src/test/deploy/hosting/prepare.spec.ts new file mode 100644 index 00000000000..8a72f814c77 --- /dev/null +++ b/src/test/deploy/hosting/prepare.spec.ts @@ -0,0 +1,118 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; + +import { FirebaseConfig } from "../../../firebaseConfig"; +import { HostingOptions } from "../../../hosting/options"; +import { Context } from "../../../deploy/hosting/context"; +import { Options } from "../../../options"; +import * as hostingApi from "../../../hosting/api"; +import * as tracking from "../../../track"; +import * as deploymentTool from "../../../deploymentTool"; +import * as config from "../../../hosting/config"; +import { prepare } from "../../../deploy/hosting"; + +describe("hosting prepare", () => { + let hostingStub: sinon.SinonStubbedInstance; + let trackingStub: sinon.SinonStubbedInstance; + let siteConfig: config.HostingResolved; + let firebaseJson: FirebaseConfig; + let options: HostingOptions & Options; + + beforeEach(() => { + hostingStub = sinon.stub(hostingApi); + trackingStub = sinon.stub(tracking); + + // We're intentionally using pointer references so that editing site + // edits the results of hostingConfig() and changes firebase.json + siteConfig = { + site: "site", + public: ".", + }; + firebaseJson = { + hosting: siteConfig, + }; + options = { + cwd: ".", + configPath: ".", + only: "", + except: "", + filteredTargets: ["HOSTING"], + force: false, + json: false, + nonInteractive: false, + interactive: true, + debug: false, + config: { + src: firebaseJson, + } as any, + rc: null as any, + + // Forces caching behavior of hostingConfig call + normalizedHostingConfig: [siteConfig], + }; + }); + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("passes a smoke test with web framework", async () => { + siteConfig.webFramework = "fake-framework"; + + // Edit the in-memory config to add a web framework + hostingStub.createVersion.callsFake((siteId, version) => { + expect(siteId).to.equal(siteConfig.site); + expect(version.status).to.equal("CREATED"); + expect(version.labels).to.deep.equal({ + ...deploymentTool.labels(), + "firebase-web-framework": "fake-framework", + }); + return Promise.resolve("version"); + }); + + const context: Context = { + projectId: "project", + }; + await prepare(context, options); + + expect(trackingStub.track).to.have.been.calledOnceWith("hosting_deploy", "fake-framework"); + expect(hostingStub.createVersion).to.have.been.calledOnce; + expect(context.hosting).to.deep.equal({ + deploys: [ + { + config: siteConfig, + version: "version", + }, + ], + }); + }); + + it("passes a smoke test without web framework", async () => { + // Do not set a web framework on siteConfig + + // Edit the in-memory config to add a web framework + hostingStub.createVersion.callsFake((siteId, version) => { + expect(siteId).to.equal(siteConfig.site); + expect(version.status).to.equal("CREATED"); + // Note: we're missing the web framework label + expect(version.labels).to.deep.equal(deploymentTool.labels()); + return Promise.resolve("version"); + }); + + const context: Context = { + projectId: "project", + }; + await prepare(context, options); + + expect(trackingStub.track).to.have.been.calledOnceWith("hosting_deploy", "classic"); + expect(hostingStub.createVersion).to.have.been.calledOnce; + expect(context.hosting).to.deep.equal({ + deploys: [ + { + config: siteConfig, + version: "version", + }, + ], + }); + }); +}); From 818ea6c82cde6fccdb277eccaab8f7396811fac7 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 28 Oct 2022 13:10:27 -0700 Subject: [PATCH 0669/1699] Set environment variable necessary to be a custom events source (#5078) * Set environment variable necessary to be a custom events source * Set label in emulator --- src/deploy/functions/prepare.ts | 16 +++++++++++++++- src/emulator/functionsEmulatorShared.ts | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 60801d85158..ad8d373e367 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -31,7 +31,9 @@ import { AUTH_BLOCKING_EVENTS } from "../../functions/events/v1"; import { generateServiceIdentity } from "../../gcp/serviceusage"; import { applyBackendHashToBackends } from "./cache/applyHash"; import { allEndpoints, Backend } from "./backend"; +import { assertExhaustive } from "../../functional"; +export const EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE"; function hasUserConfig(config: Record): boolean { // "firebase" key is always going to exist in runtime config. // If any other key exists, we can assume that user is using runtime config. @@ -138,7 +140,19 @@ export async function prepare( } for (const endpoint of backend.allEndpoints(wantBackend)) { - endpoint.environmentVariables = wantBackend.environmentVariables; + endpoint.environmentVariables = wantBackend.environmentVariables || {}; + let resource: string; + if (endpoint.platform === "gcfv1") { + resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`; + } else if (endpoint.platform === "gcfv2") { + // N.B. If GCF starts allowing v1's allowable characters in IDs they're + // going to need to have a transform to create a service ID (which has a + // more restrictive cahracter set). We'll need to reimplement that here. + resource = `projects/${endpoint.project}/locations/${endpoint.region}/services/${endpoint.id}`; + } else { + assertExhaustive(endpoint.platform); + } + endpoint.environmentVariables[EVENTARC_SOURCE_ENV] = resource; endpoint.codebase = codebase; } wantBackends[codebase] = wantBackend; diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 77155871e4f..d6414a09d45 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -25,6 +25,13 @@ const V2_EVENTS = [ ...events.v2.DATABASE_EVENTS, ]; +/** + * Label for eventarc event sources. + * TODO: Consider DRYing from functions/prepare.ts + * A nice place would be to put it in functionsv2.ts once we get rid of functions.ts + */ +export const EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE"; + export type SignatureType = "http" | "event" | "cloudevent"; export interface ParsedTriggerDefinition { @@ -171,6 +178,19 @@ export function emulatedFunctionsFromEndpoints( }; def.availableMemoryMb = endpoint.availableMemoryMb || 256; def.labels = endpoint.labels || {}; + if (endpoint.platform === "gcfv1") { + def.labels[EVENTARC_SOURCE_ENV] = + "cloudfunctions-emulated.googleapis.com" + + `/projects/${endpoint.project || "project"}/locations/${endpoint.region}/functions/${ + endpoint.id + }`; + } else if (endpoint.platform === "gcfv2") { + def.labels[EVENTARC_SOURCE_ENV] = + "run-emulated.googleapis.com" + + `/projects/${endpoint.project || "project"}/locations/${endpoint.region}/services/${ + endpoint.id + }`; + } def.timeoutSeconds = endpoint.timeoutSeconds || 60; def.secretEnvironmentVariables = endpoint.secretEnvironmentVariables || []; def.platform = endpoint.platform; From 87e8f0c4159179cda5540dd36c035c5d0c43b9d3 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 31 Oct 2022 11:14:01 -0700 Subject: [PATCH 0670/1699] Enable experiments that should have gone out with the 4.0 functions sdk (#5192) * Enable experiments that should have gone out with the 4.0 functions sdk * Add changelog --- CHANGELOG.md | 2 ++ src/experiments.ts | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a147d85e0e..234f90edba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,5 @@ - Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues on Windows. - Fixes internal library that was not being correctly published. - Adds `--disable-triggers` flag to RTDB write commands. +- Default enables experiment to skip deploying unmodified functions (#5192) +- Default enables experiment to allow parameterized functions codebases (#5192) diff --git a/src/experiments.ts b/src/experiments.ts index f1898b1ed6b..21014746d44 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -66,9 +66,7 @@ export const ALL_EXPERIMENTS = experiments({ }, functionsparams: { shortDescription: "Adds support for paramaterizing functions deployments", - }, - skipdeployingnoopfunctions: { - shortDescription: "Detect that there have been no changes to a function and skip deployment", + default: true, }, // Emulator experiments From 2a675d85dcadffa3e5289978cb4ddb45faf8a934 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Mon, 31 Oct 2022 16:13:26 -0400 Subject: [PATCH 0671/1699] Next 13 fixes (#5175) --- CHANGELOG.md | 2 + .../hosting/.gitignore | 2 + .../hosting/app/bar/page.tsx | 5 + .../hosting/app/foo/page.tsx | 3 + .../hosting/app/layout.tsx | 8 + .../hosting/next.config.js | 3 + .../hosting/package-lock.json | 277 +++++++++--------- .../hosting/package.json | 2 +- .../hosting/tsconfig.json | 24 +- scripts/webframeworks-deploy-tests/tests.ts | 2 +- src/frameworks/next/index.ts | 97 ++++-- 11 files changed, 261 insertions(+), 164 deletions(-) create mode 100644 scripts/webframeworks-deploy-tests/hosting/app/bar/page.tsx create mode 100644 scripts/webframeworks-deploy-tests/hosting/app/foo/page.tsx create mode 100644 scripts/webframeworks-deploy-tests/hosting/app/layout.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 234f90edba0..9b0d5784d68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ - Releases RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`. - Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues on Windows. - Fixes internal library that was not being correctly published. +- Add support for Next.js 13 in firebase deploy. +- Next.js routes with revalidate are now handled by the a backing Cloud Function. - Adds `--disable-triggers` flag to RTDB write commands. - Default enables experiment to skip deploying unmodified functions (#5192) - Default enables experiment to allow parameterized functions codebases (#5192) diff --git a/scripts/webframeworks-deploy-tests/hosting/.gitignore b/scripts/webframeworks-deploy-tests/hosting/.gitignore index c87c9b392c0..4f360c89d2a 100644 --- a/scripts/webframeworks-deploy-tests/hosting/.gitignore +++ b/scripts/webframeworks-deploy-tests/hosting/.gitignore @@ -34,3 +34,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +.vscode diff --git a/scripts/webframeworks-deploy-tests/hosting/app/bar/page.tsx b/scripts/webframeworks-deploy-tests/hosting/app/bar/page.tsx new file mode 100644 index 00000000000..2db606988f1 --- /dev/null +++ b/scripts/webframeworks-deploy-tests/hosting/app/bar/page.tsx @@ -0,0 +1,5 @@ +export const revalidate = 60; + +export default function Bar() { + return <>Bar; +} diff --git a/scripts/webframeworks-deploy-tests/hosting/app/foo/page.tsx b/scripts/webframeworks-deploy-tests/hosting/app/foo/page.tsx new file mode 100644 index 00000000000..3fb4cf4e4dc --- /dev/null +++ b/scripts/webframeworks-deploy-tests/hosting/app/foo/page.tsx @@ -0,0 +1,3 @@ +export default function Foo() { + return <>Foo; +} diff --git a/scripts/webframeworks-deploy-tests/hosting/app/layout.tsx b/scripts/webframeworks-deploy-tests/hosting/app/layout.tsx new file mode 100644 index 00000000000..7b221173feb --- /dev/null +++ b/scripts/webframeworks-deploy-tests/hosting/app/layout.tsx @@ -0,0 +1,8 @@ +export default function RootLayout({ children }: any) { + return ( + + + {children} + + ) +} diff --git a/scripts/webframeworks-deploy-tests/hosting/next.config.js b/scripts/webframeworks-deploy-tests/hosting/next.config.js index ae887958d3c..d3ef77accdd 100644 --- a/scripts/webframeworks-deploy-tests/hosting/next.config.js +++ b/scripts/webframeworks-deploy-tests/hosting/next.config.js @@ -2,6 +2,9 @@ const nextConfig = { reactStrictMode: true, swcMinify: true, + experimental: { + appDir: true + }, } module.exports = nextConfig diff --git a/scripts/webframeworks-deploy-tests/hosting/package-lock.json b/scripts/webframeworks-deploy-tests/hosting/package-lock.json index 5b23704afab..69330694bbc 100644 --- a/scripts/webframeworks-deploy-tests/hosting/package-lock.json +++ b/scripts/webframeworks-deploy-tests/hosting/package-lock.json @@ -8,7 +8,7 @@ "name": "hosting", "version": "0.1.0", "dependencies": { - "next": "12.3.1", + "next": "13.0.0", "react": "18.2.0", "react-dom": "18.2.0" }, @@ -113,9 +113,9 @@ "dev": true }, "node_modules/@next/env": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.1.tgz", - "integrity": "sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==" + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.0.tgz", + "integrity": "sha512-65v9BVuah2Mplohm4+efsKEnoEuhmlGm8B2w6vD1geeEP2wXtlSJCvR/cCRJ3fD8wzCQBV41VcMBQeYET6MRkg==" }, "node_modules/@next/eslint-plugin-next": { "version": "12.3.1", @@ -127,9 +127,9 @@ } }, "node_modules/@next/swc-android-arm-eabi": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.1.tgz", - "integrity": "sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.0.tgz", + "integrity": "sha512-+DUQkYF93gxFjWY+CYWE1QDX6gTgnUiWf+W4UqZjM1Jcef8U97fS6xYh+i+8rH4MM0AXHm7OSakvfOMzmjU6VA==", "cpu": [ "arm" ], @@ -142,9 +142,9 @@ } }, "node_modules/@next/swc-android-arm64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.1.tgz", - "integrity": "sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.0.tgz", + "integrity": "sha512-RW9Uy3bMSc0zVGCa11klFuwfP/jdcdkhdruqnrJ7v+7XHm6OFKkSRzX6ee7yGR1rdDZvTnP4GZSRSpzjLv/N0g==", "cpu": [ "arm64" ], @@ -157,9 +157,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.1.tgz", - "integrity": "sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.0.tgz", + "integrity": "sha512-APA26nps1j4qyhOIzkclW/OmgotVHj1jBxebSpMCPw2rXfiNvKNY9FA0TcuwPmUCNqaTnm703h6oW4dvp73A4Q==", "cpu": [ "arm64" ], @@ -172,9 +172,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz", - "integrity": "sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.0.tgz", + "integrity": "sha512-qsUhUdoFuRJiaJ7LnvTQ6GZv1QnMDcRXCIjxaN0FNVXwrjkq++U7KjBUaxXkRzLV4C7u0NHLNOp0iZwNNE7ypw==", "cpu": [ "x64" ], @@ -187,9 +187,9 @@ } }, "node_modules/@next/swc-freebsd-x64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.1.tgz", - "integrity": "sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.0.tgz", + "integrity": "sha512-sCdyCbboS7CwdnevKH9J6hkJI76LUw1jVWt4eV7kISuLiPba3JmehZSWm80oa4ADChRVAwzhLAo2zJaYRrInbg==", "cpu": [ "x64" ], @@ -202,9 +202,9 @@ } }, "node_modules/@next/swc-linux-arm-gnueabihf": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.1.tgz", - "integrity": "sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.0.tgz", + "integrity": "sha512-/X/VxfFA41C9jrEv+sUsPLQ5vbDPVIgG0CJrzKvrcc+b+4zIgPgtfsaWq9ockjHFQi3ycvlZK4TALOXO8ovQ6Q==", "cpu": [ "arm" ], @@ -217,9 +217,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.1.tgz", - "integrity": "sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.0.tgz", + "integrity": "sha512-x6Oxr1GIi0ZtNiT6jbw+JVcbEi3UQgF7mMmkrgfL4mfchOwXtWSHKTSSPnwoJWJfXYa0Vy1n8NElWNTGAqoWFw==", "cpu": [ "arm64" ], @@ -232,9 +232,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.1.tgz", - "integrity": "sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.0.tgz", + "integrity": "sha512-SnMH9ngI+ipGh3kqQ8+mDtWunirwmhQnQeZkEq9e/9Xsgjf04OetqrqRHKM1HmJtG2qMUJbyXFJ0F81TPuT+3g==", "cpu": [ "arm64" ], @@ -247,9 +247,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.1.tgz", - "integrity": "sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.0.tgz", + "integrity": "sha512-VSQwTX9EmdbotArtA1J67X8964oQfe0xHb32x4tu+JqTR+wOHyG6wGzPMdXH2oKAp6rdd7BzqxUXXf0J+ypHlw==", "cpu": [ "x64" ], @@ -262,9 +262,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.1.tgz", - "integrity": "sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.0.tgz", + "integrity": "sha512-xBCP0nnpO0q4tsytXkvIwWFINtbFRyVY5gxa1zB0vlFtqYR9lNhrOwH3CBrks3kkeaePOXd611+8sjdUtrLnXA==", "cpu": [ "x64" ], @@ -277,9 +277,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.1.tgz", - "integrity": "sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.0.tgz", + "integrity": "sha512-NutwDafqhGxqPj/eiUixJq9ImS/0sgx6gqlD7jRndCvQ2Q8AvDdu1+xKcGWGNnhcDsNM/n1avf1e62OG1GaqJg==", "cpu": [ "arm64" ], @@ -292,9 +292,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.1.tgz", - "integrity": "sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.0.tgz", + "integrity": "sha512-zNaxaO+Kl/xNz02E9QlcVz0pT4MjkXGDLb25qxtAzyJL15aU0+VjjbIZAYWctG59dvggNIUNDWgoBeVTKB9xLg==", "cpu": [ "ia32" ], @@ -307,9 +307,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz", - "integrity": "sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.0.tgz", + "integrity": "sha512-FFOGGWwTCRMu9W7MF496Urefxtuo2lttxF1vwS+1rIRsKvuLrWhVaVTj3T8sf2EBL6gtJbmh4TYlizS+obnGKA==", "cpu": [ "x64" ], @@ -761,6 +761,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2195,43 +2200,43 @@ "dev": true }, "node_modules/next": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", - "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/next/-/next-13.0.0.tgz", + "integrity": "sha512-puH1WGM6rGeFOoFdXXYfUxN9Sgi4LMytCV5HkQJvVUOhHfC1DoVqOfvzaEteyp6P04IW+gbtK2Q9pInVSrltPA==", "dependencies": { - "@next/env": "12.3.1", + "@next/env": "13.0.0", "@swc/helpers": "0.4.11", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", - "styled-jsx": "5.0.7", + "styled-jsx": "5.1.0", "use-sync-external-store": "1.2.0" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=12.22.0" + "node": ">=14.6.0" }, "optionalDependencies": { - "@next/swc-android-arm-eabi": "12.3.1", - "@next/swc-android-arm64": "12.3.1", - "@next/swc-darwin-arm64": "12.3.1", - "@next/swc-darwin-x64": "12.3.1", - "@next/swc-freebsd-x64": "12.3.1", - "@next/swc-linux-arm-gnueabihf": "12.3.1", - "@next/swc-linux-arm64-gnu": "12.3.1", - "@next/swc-linux-arm64-musl": "12.3.1", - "@next/swc-linux-x64-gnu": "12.3.1", - "@next/swc-linux-x64-musl": "12.3.1", - "@next/swc-win32-arm64-msvc": "12.3.1", - "@next/swc-win32-ia32-msvc": "12.3.1", - "@next/swc-win32-x64-msvc": "12.3.1" + "@next/swc-android-arm-eabi": "13.0.0", + "@next/swc-android-arm64": "13.0.0", + "@next/swc-darwin-arm64": "13.0.0", + "@next/swc-darwin-x64": "13.0.0", + "@next/swc-freebsd-x64": "13.0.0", + "@next/swc-linux-arm-gnueabihf": "13.0.0", + "@next/swc-linux-arm64-gnu": "13.0.0", + "@next/swc-linux-arm64-musl": "13.0.0", + "@next/swc-linux-x64-gnu": "13.0.0", + "@next/swc-linux-x64-musl": "13.0.0", + "@next/swc-win32-arm64-msvc": "13.0.0", + "@next/swc-win32-ia32-msvc": "13.0.0", + "@next/swc-win32-x64-msvc": "13.0.0" }, "peerDependencies": { "fibers": ">= 3.1.0", "node-sass": "^6.0.0 || ^7.0.0", - "react": "^17.0.2 || ^18.0.0-0", - "react-dom": "^17.0.2 || ^18.0.0-0", + "react": "^18.0.0-0", + "react-dom": "^18.0.0-0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -2859,9 +2864,12 @@ } }, "node_modules/styled-jsx": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz", - "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.0.tgz", + "integrity": "sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==", + "dependencies": { + "client-only": "0.0.1" + }, "engines": { "node": ">= 12.0.0" }, @@ -3158,9 +3166,9 @@ "dev": true }, "@next/env": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.1.tgz", - "integrity": "sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==" + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.0.tgz", + "integrity": "sha512-65v9BVuah2Mplohm4+efsKEnoEuhmlGm8B2w6vD1geeEP2wXtlSJCvR/cCRJ3fD8wzCQBV41VcMBQeYET6MRkg==" }, "@next/eslint-plugin-next": { "version": "12.3.1", @@ -3172,81 +3180,81 @@ } }, "@next/swc-android-arm-eabi": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.1.tgz", - "integrity": "sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.0.tgz", + "integrity": "sha512-+DUQkYF93gxFjWY+CYWE1QDX6gTgnUiWf+W4UqZjM1Jcef8U97fS6xYh+i+8rH4MM0AXHm7OSakvfOMzmjU6VA==", "optional": true }, "@next/swc-android-arm64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.1.tgz", - "integrity": "sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.0.tgz", + "integrity": "sha512-RW9Uy3bMSc0zVGCa11klFuwfP/jdcdkhdruqnrJ7v+7XHm6OFKkSRzX6ee7yGR1rdDZvTnP4GZSRSpzjLv/N0g==", "optional": true }, "@next/swc-darwin-arm64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.1.tgz", - "integrity": "sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.0.tgz", + "integrity": "sha512-APA26nps1j4qyhOIzkclW/OmgotVHj1jBxebSpMCPw2rXfiNvKNY9FA0TcuwPmUCNqaTnm703h6oW4dvp73A4Q==", "optional": true }, "@next/swc-darwin-x64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz", - "integrity": "sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.0.tgz", + "integrity": "sha512-qsUhUdoFuRJiaJ7LnvTQ6GZv1QnMDcRXCIjxaN0FNVXwrjkq++U7KjBUaxXkRzLV4C7u0NHLNOp0iZwNNE7ypw==", "optional": true }, "@next/swc-freebsd-x64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.1.tgz", - "integrity": "sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.0.tgz", + "integrity": "sha512-sCdyCbboS7CwdnevKH9J6hkJI76LUw1jVWt4eV7kISuLiPba3JmehZSWm80oa4ADChRVAwzhLAo2zJaYRrInbg==", "optional": true }, "@next/swc-linux-arm-gnueabihf": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.1.tgz", - "integrity": "sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.0.tgz", + "integrity": "sha512-/X/VxfFA41C9jrEv+sUsPLQ5vbDPVIgG0CJrzKvrcc+b+4zIgPgtfsaWq9ockjHFQi3ycvlZK4TALOXO8ovQ6Q==", "optional": true }, "@next/swc-linux-arm64-gnu": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.1.tgz", - "integrity": "sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.0.tgz", + "integrity": "sha512-x6Oxr1GIi0ZtNiT6jbw+JVcbEi3UQgF7mMmkrgfL4mfchOwXtWSHKTSSPnwoJWJfXYa0Vy1n8NElWNTGAqoWFw==", "optional": true }, "@next/swc-linux-arm64-musl": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.1.tgz", - "integrity": "sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.0.tgz", + "integrity": "sha512-SnMH9ngI+ipGh3kqQ8+mDtWunirwmhQnQeZkEq9e/9Xsgjf04OetqrqRHKM1HmJtG2qMUJbyXFJ0F81TPuT+3g==", "optional": true }, "@next/swc-linux-x64-gnu": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.1.tgz", - "integrity": "sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.0.tgz", + "integrity": "sha512-VSQwTX9EmdbotArtA1J67X8964oQfe0xHb32x4tu+JqTR+wOHyG6wGzPMdXH2oKAp6rdd7BzqxUXXf0J+ypHlw==", "optional": true }, "@next/swc-linux-x64-musl": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.1.tgz", - "integrity": "sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.0.tgz", + "integrity": "sha512-xBCP0nnpO0q4tsytXkvIwWFINtbFRyVY5gxa1zB0vlFtqYR9lNhrOwH3CBrks3kkeaePOXd611+8sjdUtrLnXA==", "optional": true }, "@next/swc-win32-arm64-msvc": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.1.tgz", - "integrity": "sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.0.tgz", + "integrity": "sha512-NutwDafqhGxqPj/eiUixJq9ImS/0sgx6gqlD7jRndCvQ2Q8AvDdu1+xKcGWGNnhcDsNM/n1avf1e62OG1GaqJg==", "optional": true }, "@next/swc-win32-ia32-msvc": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.1.tgz", - "integrity": "sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.0.tgz", + "integrity": "sha512-zNaxaO+Kl/xNz02E9QlcVz0pT4MjkXGDLb25qxtAzyJL15aU0+VjjbIZAYWctG59dvggNIUNDWgoBeVTKB9xLg==", "optional": true }, "@next/swc-win32-x64-msvc": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz", - "integrity": "sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.0.tgz", + "integrity": "sha512-FFOGGWwTCRMu9W7MF496Urefxtuo2lttxF1vwS+1rIRsKvuLrWhVaVTj3T8sf2EBL6gtJbmh4TYlizS+obnGKA==", "optional": true }, "@nodelib/fs.scandir": { @@ -3559,6 +3567,11 @@ "supports-color": "^7.1.0" } }, + "client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4637,28 +4650,28 @@ "dev": true }, "next": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", - "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/next/-/next-13.0.0.tgz", + "integrity": "sha512-puH1WGM6rGeFOoFdXXYfUxN9Sgi4LMytCV5HkQJvVUOhHfC1DoVqOfvzaEteyp6P04IW+gbtK2Q9pInVSrltPA==", "requires": { - "@next/env": "12.3.1", - "@next/swc-android-arm-eabi": "12.3.1", - "@next/swc-android-arm64": "12.3.1", - "@next/swc-darwin-arm64": "12.3.1", - "@next/swc-darwin-x64": "12.3.1", - "@next/swc-freebsd-x64": "12.3.1", - "@next/swc-linux-arm-gnueabihf": "12.3.1", - "@next/swc-linux-arm64-gnu": "12.3.1", - "@next/swc-linux-arm64-musl": "12.3.1", - "@next/swc-linux-x64-gnu": "12.3.1", - "@next/swc-linux-x64-musl": "12.3.1", - "@next/swc-win32-arm64-msvc": "12.3.1", - "@next/swc-win32-ia32-msvc": "12.3.1", - "@next/swc-win32-x64-msvc": "12.3.1", + "@next/env": "13.0.0", + "@next/swc-android-arm-eabi": "13.0.0", + "@next/swc-android-arm64": "13.0.0", + "@next/swc-darwin-arm64": "13.0.0", + "@next/swc-darwin-x64": "13.0.0", + "@next/swc-freebsd-x64": "13.0.0", + "@next/swc-linux-arm-gnueabihf": "13.0.0", + "@next/swc-linux-arm64-gnu": "13.0.0", + "@next/swc-linux-arm64-musl": "13.0.0", + "@next/swc-linux-x64-gnu": "13.0.0", + "@next/swc-linux-x64-musl": "13.0.0", + "@next/swc-win32-arm64-msvc": "13.0.0", + "@next/swc-win32-ia32-msvc": "13.0.0", + "@next/swc-win32-x64-msvc": "13.0.0", "@swc/helpers": "0.4.11", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", - "styled-jsx": "5.0.7", + "styled-jsx": "5.1.0", "use-sync-external-store": "1.2.0" } }, @@ -5077,10 +5090,12 @@ "dev": true }, "styled-jsx": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz", - "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", - "requires": {} + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.0.tgz", + "integrity": "sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==", + "requires": { + "client-only": "0.0.1" + } }, "supports-color": { "version": "7.2.0", diff --git a/scripts/webframeworks-deploy-tests/hosting/package.json b/scripts/webframeworks-deploy-tests/hosting/package.json index 767c471e2fb..337edffd67c 100644 --- a/scripts/webframeworks-deploy-tests/hosting/package.json +++ b/scripts/webframeworks-deploy-tests/hosting/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "next": "12.3.1", + "next": "13.0.0", "react": "18.2.0", "react-dom": "18.2.0" }, diff --git a/scripts/webframeworks-deploy-tests/hosting/tsconfig.json b/scripts/webframeworks-deploy-tests/hosting/tsconfig.json index 99710e85787..b25c4f834cb 100644 --- a/scripts/webframeworks-deploy-tests/hosting/tsconfig.json +++ b/scripts/webframeworks-deploy-tests/hosting/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -13,8 +17,20 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } diff --git a/scripts/webframeworks-deploy-tests/tests.ts b/scripts/webframeworks-deploy-tests/tests.ts index 120bfad0074..84341796168 100644 --- a/scripts/webframeworks-deploy-tests/tests.ts +++ b/scripts/webframeworks-deploy-tests/tests.ts @@ -45,7 +45,7 @@ describe("webframeworks deploy", function (this) { const result = await setOptsAndDeploy(); expect(result.stdout, "deploy result").to.match(/file upload complete/); - expect(result.stdout, "deploy result").to.match(/found 16 files/); + expect(result.stdout, "deploy result").to.match(/found 20 files/); expect(result.stdout, "deploy result").to.match(/Deploy complete!/); }); }); diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index b79c5a0510f..e13ba65b00e 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -1,11 +1,12 @@ import { execSync } from "child_process"; -import { readFile, mkdir, copyFile, stat } from "fs/promises"; -import { dirname, extname, join } from "path"; +import { readFile, mkdir, copyFile } from "fs/promises"; +import { dirname, join } from "path"; import type { Header, Rewrite, Redirect } from "next/dist/lib/load-custom-routes"; import type { NextConfig } from "next"; import { copy, mkdirp, pathExists } from "fs-extra"; import { pathToFileURL, parse } from "url"; import { existsSync } from "fs"; + import { BuildResult, createServerResponseProxy, @@ -20,6 +21,7 @@ import { gte } from "semver"; import { IncomingMessage, ServerResponse } from "http"; import { logger } from "../../logger"; import { FirebaseError } from "../../error"; +import { fileExistsSync } from "../../fsutils"; // Next.js's exposed interface is incomplete here // TODO see if there's a better way to grab this @@ -47,10 +49,14 @@ export const name = "Next.js"; export const support = SupportLevel.Experimental; export const type = FrameworkType.MetaFramework; -function getNextVersion(cwd: string) { +function getNextVersion(cwd: string): string | undefined { return findDependency("next", { cwd, depth: 0, omitDev: false })?.version; } +function getReactVersion(cwd: string): string | undefined { + return findDependency("react-dom", { cwd, omitDev: false })?.version; +} + /** * Returns whether this codebase is a Next.js backend. */ @@ -67,6 +73,12 @@ export async function discover(dir: string) { export async function build(dir: string): Promise { const { default: nextBuild } = relativeRequire(dir, "next/dist/build"); + const reactVersion = getReactVersion(dir); + if (reactVersion && gte(reactVersion, "18.0.0")) { + // This needs to be set for Next build to succeed with React 18 + process.env.__NEXT_REACT_ROOT = "true"; + } + await nextBuild(dir, null, false, false, true).catch((e) => { // Err on the side of displaying this error, since this is likely a bug in // the developer's code that we want to display immediately @@ -89,6 +101,10 @@ export async function build(dir: string): Promise { const exportDetailBuffer = exportDetailExists ? await readFile(exportDetailPath) : undefined; const exportDetailJson = exportDetailBuffer && JSON.parse(exportDetailBuffer.toString()); if (exportDetailJson?.success) { + const appPathRoutesManifestPath = join(dir, distDir, "app-path-routes-manifest.json"); + const appPathRoutesManifestJSON = fileExistsSync(appPathRoutesManifestPath) + ? await readFile(appPathRoutesManifestPath).then((it) => JSON.parse(it.toString())) + : {}; const prerenderManifestJSON = await readFile( join(dir, distDir, "prerender-manifest.json") ).then((it) => JSON.parse(it.toString())); @@ -100,10 +116,15 @@ export async function build(dir: string): Promise { ).then((it) => JSON.parse(it.toString())); const prerenderedRoutes = Object.keys(prerenderManifestJSON.routes); const dynamicRoutes = Object.keys(prerenderManifestJSON.dynamicRoutes); - const unrenderedPages = Object.keys(pagesManifestJSON).filter( + const unrenderedPages = [ + ...Object.keys(pagesManifestJSON), + // TODO flush out fully rendered detection with a app directory (Next 13) + // we shouldn't go too crazy here yet, as this is currently an expiriment + ...Object.values(appPathRoutesManifestJSON), + ].filter( (it) => !( - ["/_app", "/_error", "/_document", "/404"].includes(it) || + ["/_app", "/", "/_error", "/_document", "/404"].includes(it) || prerenderedRoutes.includes(it) || dynamicRoutes.includes(it) ) @@ -150,7 +171,7 @@ export async function init(setup: any) { choices: ["JavaScript", "TypeScript"], }); execSync( - `npx --yes create-next-app@latest -e hello-world ${setup.hosting.source} ${ + `npx --yes create-next-app@latest -e hello-world ${setup.hosting.source} --use-npm ${ language === "TypeScript" ? "--ts" : "" }`, { stdio: "inherit" } @@ -176,25 +197,37 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin } await copy(join(sourceDir, distDir, "static"), join(destDir, "_next", "static")); - const serverPagesDir = join(sourceDir, distDir, "server", "pages"); - await copy(serverPagesDir, destDir, { - filter: async (filename) => { - const status = await stat(filename); - if (status.isDirectory()) return true; - return extname(filename) === ".html"; - }, - }); + // Copy over the default html files + for (const file of ["index.html", "404.html", "500.html"]) { + const pagesPath = join(sourceDir, distDir, "server", "pages", file); + if (await pathExists(pagesPath)) { + await copyFile(pagesPath, join(destDir, file)); + continue; + } + const appPath = join(sourceDir, distDir, "server", "app", file); + if (await pathExists(appPath)) { + await copyFile(appPath, join(destDir, file)); + } + } const prerenderManifestBuffer = await readFile( join(sourceDir, distDir, "prerender-manifest.json") ); const prerenderManifest = JSON.parse(prerenderManifestBuffer.toString()); - // TODO drop from hosting if revalidate - for (const route in prerenderManifest.routes) { - if (prerenderManifest.routes[route]) { + for (const path in prerenderManifest.routes) { + if (prerenderManifest.routes[path]) { + // Skip ISR in the deploy to hosting + const { initialRevalidateSeconds } = prerenderManifest.routes[path]; + if (initialRevalidateSeconds) { + continue; + } + + // TODO(jamesdaniels) explore oppertunity to simplify this now that we + // are defaulting cleanURLs to true for frameworks + // / => index.json => index.html => index.html // /foo => foo.json => foo.html - const parts = route + const parts = path .split("/") .slice(1) .filter((it) => !!it); @@ -202,16 +235,26 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin const dataPath = `${join(...partsOrIndex)}.json`; const htmlPath = `${join(...partsOrIndex)}.html`; await mkdir(join(destDir, dirname(htmlPath)), { recursive: true }); - await copyFile( - join(sourceDir, distDir, "server", "pages", htmlPath), - join(destDir, htmlPath) - ); - const dataRoute = prerenderManifest.routes[route].dataRoute; + const pagesHtmlPath = join(sourceDir, distDir, "server", "pages", htmlPath); + if (await pathExists(pagesHtmlPath)) { + await copyFile(pagesHtmlPath, join(destDir, htmlPath)); + } else { + const appHtmlPath = join(sourceDir, distDir, "server", "app", htmlPath); + if (await pathExists(appHtmlPath)) { + await copyFile(appHtmlPath, join(destDir, htmlPath)); + } + } + const dataRoute = prerenderManifest.routes[path].dataRoute; await mkdir(join(destDir, dirname(dataRoute)), { recursive: true }); - await copyFile( - join(sourceDir, distDir, "server", "pages", dataPath), - join(destDir, dataRoute) - ); + const pagesDataPath = join(sourceDir, distDir, "server", "pages", dataPath); + if (await pathExists(pagesDataPath)) { + await copyFile(pagesDataPath, join(destDir, dataRoute)); + } else { + const appDataPath = join(sourceDir, distDir, "server", "app", dataPath); + if (await pathExists(appDataPath)) { + await copyFile(appDataPath, join(destDir, dataRoute)); + } + } } } } From 60f6d669472916c6c8e1d4feacdacf922bd6eb29 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 31 Oct 2022 16:29:16 -0700 Subject: [PATCH 0672/1699] Auto-downgrade implicit concurrency (#5196) * Auto-downgrade implicit concurrency * Alternate approach which also upgrades concurrency * Remove outdated comment --- src/deploy/functions/prepare.ts | 20 ++++---- src/test/deploy/functions/prepare.spec.ts | 53 ++++++++++++++++++++++ src/test/deploy/functions/validate.spec.ts | 8 ++-- 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index ad8d373e367..876a434ee16 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -213,7 +213,7 @@ export async function prepare( for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) { inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase)); await ensureTriggerRegions(wantBackend); - resolveCpu(wantBackend); + resolveCpuAndConcurrency(wantBackend); validate.endpointsAreValid(wantBackend); inferBlockingDetails(wantBackend); } @@ -321,17 +321,15 @@ export function inferDetailsFromExisting( wantE.availableMemoryMb = haveE.availableMemoryMb; } - // N.B. This code doesn't handle automatic downgrading of concurrency if - // the customer sets CPU <1. We'll instead error that you can't have both. - // We may want to handle this case, though it might also be surprising to - // customers if they _don't_ get an error and we silently drop concurrency. - if (typeof wantE.concurrency === "undefined" && haveE.concurrency) { - wantE.concurrency = haveE.concurrency; - } if (typeof wantE.cpu === "undefined" && haveE.cpu) { wantE.cpu = haveE.cpu; } + // N.B. concurrency has different defaults based on CPU. If the customer + // only specifies CPU and they change that specification to < 1, we should + // turn off concurrency. + // We'll hanndle this in setCpuAndConcurrency + wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS"; maybeCopyTriggerRegion(wantE, haveE); @@ -408,7 +406,7 @@ export function inferBlockingDetails(want: backend.Backend): void { * provided and sets concurrency based on the CPU level if not provided. * After this function, CPU will be a real number and not "gcf_gen1". */ -export function resolveCpu(want: backend.Backend): void { +export function resolveCpuAndConcurrency(want: backend.Backend): void { for (const e of backend.allEndpoints(want)) { if (e.platform === "gcfv1") { continue; @@ -418,5 +416,9 @@ export function resolveCpu(want: backend.Backend): void { } else if (!e.cpu) { e.cpu = backend.memoryToGen2Cpu(e.availableMemoryMb || backend.DEFAULT_MEMORY); } + + if (!e.concurrency) { + e.concurrency = e.cpu >= 1 ? backend.DEFAULT_CONCURRENCY : 1; + } } } diff --git a/src/test/deploy/functions/prepare.spec.ts b/src/test/deploy/functions/prepare.spec.ts index 4f337a424ea..ca96c93beef 100644 --- a/src/test/deploy/functions/prepare.spec.ts +++ b/src/test/deploy/functions/prepare.spec.ts @@ -122,6 +122,59 @@ describe("prepare", () => { prepare.inferDetailsFromExisting(backend.of(want), backend.of(have), /* usedDotEnv= */ false); expect(want.availableMemoryMb).to.equal(512); }); + + it("downgrades concurrency if necessary (explicit)", () => { + const have: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + concurrency: 80, + cpu: 1, + }; + const want: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + cpu: 0.5, + }; + + prepare.inferDetailsFromExisting(backend.of(want), backend.of(have), /* useDotEnv= */ false); + prepare.resolveCpuAndConcurrency(backend.of(want)); + expect(want.concurrency).to.equal(1); + }); + + it("downgrades concurrency if necessary (implicit)", () => { + const have: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + concurrency: 80, + cpu: 1, + }; + const want: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + cpu: "gcf_gen1", + }; + + prepare.inferDetailsFromExisting(backend.of(want), backend.of(have), /* useDotEnv= */ false); + prepare.resolveCpuAndConcurrency(backend.of(want)); + expect(want.concurrency).to.equal(1); + }); + + it("upgrades default concurrency with CPU upgrades", () => { + const have: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + availableMemoryMb: 256, + cpu: "gcf_gen1", + }; + const want: backend.Endpoint = { + ...ENDPOINT_BASE, + httpsTrigger: {}, + }; + + prepare.inferDetailsFromExisting(backend.of(want), backend.of(have), /* useDotEnv= */ false); + prepare.resolveCpuAndConcurrency(backend.of(want)); + expect(want.concurrency).to.equal(1); + }); }); describe("inferBlockingDetails", () => { diff --git a/src/test/deploy/functions/validate.spec.ts b/src/test/deploy/functions/validate.spec.ts index cbb834694a1..b2f1bb63c4e 100644 --- a/src/test/deploy/functions/validate.spec.ts +++ b/src/test/deploy/functions/validate.spec.ts @@ -8,7 +8,7 @@ import * as projectPath from "../../../projectPath"; import * as secretManager from "../../../gcp/secretManager"; import * as backend from "../../../deploy/functions/backend"; import { BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT } from "../../../functions/events/v1"; -import { resolveCpu } from "../../../deploy/functions/prepare"; +import { resolveCpuAndConcurrency } from "../../../deploy/functions/prepare"; describe("validate", () => { describe("functionsDirectoryExists", () => { @@ -331,7 +331,7 @@ describe("validate", () => { availableMemoryMb: mem, cpu: "gcf_gen1", }; - resolveCpu(backend.of(ep)); + resolveCpuAndConcurrency(backend.of(ep)); expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; } }); @@ -344,7 +344,7 @@ describe("validate", () => { cpu: "gcf_gen1", concurrency: 42, }; - resolveCpu(backend.of(ep)); + resolveCpuAndConcurrency(backend.of(ep)); expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw; } }); @@ -356,7 +356,7 @@ describe("validate", () => { concurrency: 2, cpu: "gcf_gen1", }; - resolveCpu(backend.of(ep)); + resolveCpuAndConcurrency(backend.of(ep)); expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw( /concurrent execution and less than one full CPU/ ); From 88fe601054d8dfd077214cbf11f33c921e897d9c Mon Sep 17 00:00:00 2001 From: akongara-goog <106410469+akongara-goog@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:28:40 -0700 Subject: [PATCH 0673/1699] Fixing handling of automatic rewrites detection to consider backends that don't exist but are being deployed. (#5164) Fix rewrites detection issue with detecting currently-deploying backends. Handle the case of a backend being deleted in the current deploy. --- scripts/hosting-tests/rewrites-tests/tests.ts | 47 ++++ src/deploy/hosting/convertConfig.ts | 64 ++++- src/deploy/hosting/release.ts | 9 +- src/test/deploy/hosting/convertConfig.spec.ts | 252 ++++++++++++++++-- src/test/deploy/hosting/release.spec.ts | 10 +- 5 files changed, 346 insertions(+), 36 deletions(-) diff --git a/scripts/hosting-tests/rewrites-tests/tests.ts b/scripts/hosting-tests/rewrites-tests/tests.ts index 9fbc63d3038..1c2cf4bf5d2 100644 --- a/scripts/hosting-tests/rewrites-tests/tests.ts +++ b/scripts/hosting-tests/rewrites-tests/tests.ts @@ -353,6 +353,53 @@ describe("deploy function-targeted rewrites And functions", () => { ).to.eventually.be.rejectedWith(FirebaseError, "Unable to find a valid endpoint for function"); }).timeout(1000 * 1e3); + it("should fail to deploy rewrites to a function being deleted in a region", async () => { + const firebaseJson = { + hosting: { + public: "hosting", + rewrites: [ + { + source: "/helloWorld", + function: functionName, + region: "asia-northeast1", + }, + ], + }, + }; + + const firebaseJsonFilePath = join(tempDirInfo.tempDir.name, ".", "firebase.json"); + writeFileSync(firebaseJsonFilePath, JSON.stringify(firebaseJson)); + ensureDirSync(tempDirInfo.hostingDirPath); + writeBasicHostingFile(tempDirInfo.hostingDirPath); + + writeHelloWorldFunctionWithRegions( + functionName, + join(tempDirInfo.tempDir.name, ".", "functions"), + ["asia-northeast1"] + ); + + await client.deploy({ + project: process.env.FBTOOLS_TARGET_PROJECT, + cwd: tempDirInfo.tempDir.name, + only: "functions", + force: true, + }); + + writeHelloWorldFunctionWithRegions( + functionName, + join(tempDirInfo.tempDir.name, ".", "functions"), + ["europe-west1"] + ); + await expect( + client.deploy({ + project: process.env.FBTOOLS_TARGET_PROJECT, + cwd: tempDirInfo.tempDir.name, + only: "functions,hosting", + force: true, + }) + ).to.eventually.be.rejectedWith(FirebaseError, "Unable to find a valid endpoint for function"); + }).timeout(1000 * 1e3); + it("should deploy when a rewrite points to a non-existent function", async () => { const firebaseJson = { hosting: { diff --git a/src/deploy/hosting/convertConfig.ts b/src/deploy/hosting/convertConfig.ts index e052a04cd50..f6669343bae 100644 --- a/src/deploy/hosting/convertConfig.ts +++ b/src/deploy/hosting/convertConfig.ts @@ -3,7 +3,7 @@ import { HostingSource } from "../../firebaseConfig"; import { HostingDeploy } from "./context"; import * as api from "../../hosting/api"; import * as backend from "../functions/backend"; -import { Context } from "../functions/args"; +import { Context, Payload as FunctionsPayload } from "../functions/args"; import { last, logLabeledBullet, logLabeledWarning } from "../../utils"; import * as proto from "../../gcp/proto"; import { bold } from "colorette"; @@ -14,7 +14,7 @@ import { logger } from "../../logger"; /** * extractPattern contains the logic for extracting exactly one glob/regexp - * from a Hosting rewrite/redirect/header specification + * from a Hosting rewrite/redirect/header specification. */ function extractPattern(type: string, source: HostingSource): api.HasPattern { let glob: string | undefined; @@ -41,25 +41,31 @@ function extractPattern(type: string, source: HostingSource): api.HasPattern { ); } +interface EndpointSearchResult { + // An endpoint matching the functions ID (and optionally, the region) we're searching for. + matchingEndpoint: backend.Endpoint | undefined; + // Whether we found an endpoint with a matching function ID (but not necessarily function region) + foundMatchingId: boolean; +} + /** - * Finds an endpoint suitable for deploy at a site given an id and optional region + * Finds an endpoint suitable for deploy at a site given an id and optional region. */ export function findEndpointForRewrite( site: string, targetBackend: backend.Backend, id: string, region: string | undefined -): backend.Endpoint | undefined { +): EndpointSearchResult { const endpoints = backend.allEndpoints(targetBackend).filter((e) => e.id === id); - if (endpoints.length === 0) { - return; + return { matchingEndpoint: undefined, foundMatchingId: false }; } if (endpoints.length === 1) { if (region && region !== endpoints[0].region) { - return; + return { matchingEndpoint: undefined, foundMatchingId: true }; } - return endpoints[0]; + return { matchingEndpoint: endpoints[0], foundMatchingId: true }; } if (!region) { const us = endpoints.find((e) => e.region === "us-central1"); @@ -73,9 +79,12 @@ export function findEndpointForRewrite( `Function \`${id}\` found in multiple regions, defaulting to \`us-central1\`. ` + `To rewrite to a different region, specify a \`region\` for the rewrite in \`firebase.json\`.` ); - return us; + return { matchingEndpoint: us, foundMatchingId: true }; } - return endpoints.find((e) => e.region === region); + return { + matchingEndpoint: endpoints.find((e) => e.region === region), + foundMatchingId: true, + }; } /** @@ -88,6 +97,7 @@ export function findEndpointForRewrite( */ export async function convertConfig( context: Context, + functionsPayload: FunctionsPayload, deploy: HostingDeploy ): Promise { const config: api.ServingConfig = {}; @@ -96,9 +106,12 @@ export async function convertConfig( // rewrites to see if it's necessary. const hasBackends = !!deploy.config.rewrites?.some((r) => "function" in r || "run" in r); - // We need to be able to do a rewrite to an existing function that is may not - // even be part of Firebase's control or a function that we're currently - // deploying. + // We need to be able to do a rewrite to an existing function that may not be + // under Firebase's control or a function that we're currently deploying. + const wantBackend = backend.merge( + ...Object.values(functionsPayload.functions || {}).map((c) => c.wantBackend) + ); + let haveBackend = backend.empty(); if (hasBackends) { try { @@ -136,8 +149,31 @@ export async function convertConfig( } const id = rewrite.function.functionId; const region = rewrite.function.region; - const endpoint = findEndpointForRewrite(deploy.config.site, haveBackend, id, region); + + const deployingEndpointSearch = findEndpointForRewrite( + deploy.config.site, + wantBackend, + id, + region + ); + const existingEndpointSearch = + !deployingEndpointSearch.foundMatchingId && !deployingEndpointSearch.matchingEndpoint + ? findEndpointForRewrite(deploy.config.site, haveBackend, id, region) + : undefined; + const endpoint = deployingEndpointSearch.matchingEndpoint + ? deployingEndpointSearch.matchingEndpoint + : existingEndpointSearch?.matchingEndpoint; + if (!endpoint) { + // If we find a function matching the function ID we are looking for in either + // existing or currently-deploying backends, we consider it a firebase function. + // In this case, we throw an error if the rewrite doesn't point to a valid region. + if (deployingEndpointSearch.foundMatchingId || existingEndpointSearch?.foundMatchingId) { + throw new FirebaseError( + `Unable to find a valid endpoint for function. Functions matching the rewrite + are present but in the wrong region.` + ); + } // This could possibly succeed if there has been a function written // outside firebase tooling. But it will break in v2. We might need to // revisit this. diff --git a/src/deploy/hosting/release.ts b/src/deploy/hosting/release.ts index 859ae218416..9ed60783079 100644 --- a/src/deploy/hosting/release.ts +++ b/src/deploy/hosting/release.ts @@ -4,11 +4,16 @@ import * as utils from "../../utils"; import { convertConfig } from "./convertConfig"; import { Context } from "./context"; import { FirebaseError } from "../../error"; +import { Payload as FunctionsPayload } from "../functions/args"; /** * Release finalized a Hosting release. */ -export async function release(context: Context, options: { message?: string }): Promise { +export async function release( + context: Context, + options: { message?: string }, + functionsPayload: FunctionsPayload +): Promise { if (!context.hosting || !context.hosting.deploys) { return; } @@ -26,7 +31,7 @@ export async function release(context: Context, options: { message?: string }): const update: Partial = { status: "FINALIZED", - config: await convertConfig(context, deploy), + config: await convertConfig(context, functionsPayload, deploy), }; const versionId = utils.last(deploy.version.split("/")); diff --git a/src/test/deploy/hosting/convertConfig.spec.ts b/src/test/deploy/hosting/convertConfig.spec.ts index a2bde18c624..665656bf371 100644 --- a/src/test/deploy/hosting/convertConfig.spec.ts +++ b/src/test/deploy/hosting/convertConfig.spec.ts @@ -7,13 +7,14 @@ import { Context, HostingDeploy } from "../../../deploy/hosting/context"; import { HostingSingle } from "../../../firebaseConfig"; import * as api from "../../../hosting/api"; import { FirebaseError } from "../../../error"; +import { Payload } from "../../../deploy/functions/args"; const FUNCTION_ID = "function"; const PROJECT_ID = "project"; const REGION = "region"; function endpoint(opts?: Partial): backend.Endpoint { - // Createa type that allows us to not have a trigger + // Create a type that allows us to not have a trigger const ret: Omit & { httpsTrigger?: backend.HttpsTrigger } = { id: FUNCTION_ID, project: PROJECT_ID, @@ -43,6 +44,7 @@ describe("convertConfig", () => { name: string; input: HostingSingle; want: api.ServingConfig; + functionsPayload?: Payload; existingBackend?: backend.Backend; }> = [ // Rewrites. @@ -59,14 +61,52 @@ describe("convertConfig", () => { { name: "checks for function region if unspecified", input: { rewrites: [{ glob: "/foo", function: { functionId: FUNCTION_ID } }] }, - want: { rewrites: [{ glob: "/foo", function: FUNCTION_ID, functionRegion: "us-central1" }] }, - existingBackend: backend.of(endpoint({ region: "us-central1" })), + want: { + rewrites: [ + { + glob: "/foo", + function: FUNCTION_ID, + functionRegion: "us-central2", + }, + ], + }, + functionsPayload: { + functions: { + default: { + wantBackend: backend.of({ + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "us-central2", + platform: "gcfv1", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), + }, + }, + }, }, { name: "discovers the function region of a callable function", input: { rewrites: [{ glob: "/foo", function: { functionId: FUNCTION_ID } }] }, - want: { rewrites: [{ glob: "/foo", function: FUNCTION_ID, functionRegion: "us-central1" }] }, - existingBackend: backend.of(endpoint({ callableTrigger: {}, region: "us-central1" })), + want: { rewrites: [{ glob: "/foo", function: FUNCTION_ID, functionRegion: "us-central2" }] }, + functionsPayload: { + functions: { + default: { + wantBackend: backend.of({ + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "us-central2", + platform: "gcfv1", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), + }, + }, + }, }, { name: "returns rewrites for glob CF3", @@ -74,13 +114,65 @@ describe("convertConfig", () => { rewrites: [{ glob: "/foo", function: { functionId: FUNCTION_ID, region: "europe-west2" } }], }, want: { rewrites: [{ glob: "/foo", function: FUNCTION_ID, functionRegion: "europe-west2" }] }, - existingBackend: backend.of(endpoint({ region: "europe-west2" }), endpoint()), + functionsPayload: { + functions: { + default: { + wantBackend: backend.of( + { + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "europe-west2", + platform: "gcfv1", + httpsTrigger: {}, + }, + { + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: {}, + } + ), + haveBackend: backend.empty(), + }, + }, + }, }, { name: "defaults to a us-central1 rewrite if one is avaiable, v1 edition", input: { rewrites: [{ glob: "/foo", function: { functionId: FUNCTION_ID } }] }, want: { rewrites: [{ glob: "/foo", function: FUNCTION_ID, functionRegion: "us-central1" }] }, - existingBackend: backend.of(endpoint(), endpoint({ region: "us-central1" })), + functionsPayload: { + functions: { + default: { + wantBackend: backend.of( + { + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "europe-west2", + platform: "gcfv1", + httpsTrigger: {}, + }, + { + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "us-central1", + platform: "gcfv1", + httpsTrigger: {}, + } + ), + haveBackend: backend.empty(), + }, + }, + }, }, { name: "defaults to a us-central1 rewrite if one is avaiable, v2 edition", @@ -88,10 +180,33 @@ describe("convertConfig", () => { want: { rewrites: [{ glob: "/foo", run: { region: "us-central1", serviceId: FUNCTION_ID } }], }, - existingBackend: backend.of( - endpoint({ platform: "gcfv2" }), - endpoint({ platform: "gcfv2", region: "us-central1" }) - ), + functionsPayload: { + functions: { + default: { + wantBackend: backend.of( + { + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "europe-west2", + platform: "gcfv2", + httpsTrigger: {}, + }, + { + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "us-central1", + platform: "gcfv2", + httpsTrigger: {}, + } + ), + haveBackend: backend.empty(), + }, + }, + }, }, { name: "returns rewrites for regex CF3", @@ -101,13 +216,43 @@ describe("convertConfig", () => { want: { rewrites: [{ regex: "/foo$", function: FUNCTION_ID, functionRegion: REGION }], }, - existingBackend: backend.of(endpoint()), + functionsPayload: { + functions: { + default: { + wantBackend: backend.of({ + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: REGION, + platform: "gcfv1", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), + }, + }, + }, }, { name: "rewrites referencing CF3v2 functions being deployed are changed to Cloud Run (during release)", input: { rewrites: [{ regex: "/foo$", function: { functionId: FUNCTION_ID } }] }, want: { rewrites: [{ regex: "/foo$", run: { serviceId: FUNCTION_ID, region: REGION } }] }, - existingBackend: backend.of(endpoint({ platform: "gcfv2" })), + functionsPayload: { + functions: { + default: { + wantBackend: backend.of({ + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: REGION, + platform: "gcfv2", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), + }, + }, + }, }, { name: "rewrites referencing existing CF3v2 functions are changed to Cloud Run (during prepare)", @@ -257,7 +402,7 @@ describe("convertConfig", () => { }, ]; - for (const { name, input, existingBackend, want } of tests) { + for (const { name, input, existingBackend, want, functionsPayload } of tests) { it(name, async () => { const context: Context = { projectId: PROJECT_ID, @@ -272,11 +417,87 @@ describe("convertConfig", () => { config: { site: "site", ...input }, version: "version", }; - const config = await convertConfig(context, deploy); + const config = await convertConfig(context, functionsPayload || {}, deploy); expect(config).to.deep.equal(want); }); } + describe("rewrites errors", () => { + it("should throw when rewrite points to function in the wrong region", async () => { + await expect( + convertConfig( + { projectId: "1" }, + { + functions: { + default: { + wantBackend: backend.of({ + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "europe-west1", + platform: "gcfv1", + httpsTrigger: {}, + }), + haveBackend: backend.empty(), + }, + }, + }, + { + config: { + site: "foo", + rewrites: [ + { glob: "/foo", function: { functionId: FUNCTION_ID, region: "asia-northeast1" } }, + ], + }, + version: "14", + } + ) + ).to.eventually.be.rejectedWith(FirebaseError); + }); + + it("should throw when rewrite points to function being deleted", async () => { + await expect( + convertConfig( + { projectId: "1" }, + { + functions: { + default: { + wantBackend: backend.of({ + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "europe-west1", + platform: "gcfv1", + httpsTrigger: {}, + }), + haveBackend: backend.of({ + id: FUNCTION_ID, + project: PROJECT_ID, + entryPoint: FUNCTION_ID, + runtime: "nodejs16", + region: "asia-northeast1", + platform: "gcfv1", + httpsTrigger: {}, + }), + }, + }, + }, + { + config: { + site: "foo", + rewrites: [ + { glob: "/foo", function: { functionId: FUNCTION_ID, region: "asia-northeast1" } }, + ], + }, + version: "14", + } + ) + ).to.eventually.be.rejectedWith(FirebaseError); + }); + }); + describe("with permissions issues", () => { let existingBackendStub: sinon.SinonStub; @@ -298,6 +519,7 @@ describe("convertConfig", () => { await expect( convertConfig( { projectId: "1" }, + {}, { config: { site: "foo", diff --git a/src/test/deploy/hosting/release.spec.ts b/src/test/deploy/hosting/release.spec.ts index d8ed13d6b50..7ce5458c2db 100644 --- a/src/test/deploy/hosting/release.spec.ts +++ b/src/test/deploy/hosting/release.spec.ts @@ -29,7 +29,7 @@ describe("release", () => { describe("with no Hosting deploys", () => { it("should bail", async () => { - await release({ projectId: "foo" }, {}); + await release({ projectId: "foo" }, {}, {}); expect(updateVersionStub).to.have.been.not.called; expect(createReleaseStub).to.have.been.not.called; @@ -53,7 +53,7 @@ describe("release", () => { updateVersionStub.resolves({}); createReleaseStub.resolves({}); - await release(CONTEXT, {}); + await release(CONTEXT, {}, {}); expect(updateVersionStub).to.have.been.calledOnceWithExactly( SITE, @@ -67,7 +67,7 @@ describe("release", () => { updateVersionStub.resolves({}); createReleaseStub.resolves({}); - await release(CONTEXT, { message: "hello world" }); + await release(CONTEXT, { message: "hello world" }, {}); expect(updateVersionStub).to.have.been.calledOnceWithExactly( SITE, @@ -100,7 +100,7 @@ describe("release", () => { updateVersionStub.resolves({}); createReleaseStub.resolves({}); - await release(CONTEXT, {}); + await release(CONTEXT, {}, {}); expect(updateVersionStub).to.have.been.calledTwice; expect(updateVersionStub).to.have.been.calledWithExactly( @@ -143,7 +143,7 @@ describe("release", () => { updateVersionStub.resolves({}); createReleaseStub.resolves({}); - await release(CONTEXT, {}); + await release(CONTEXT, {}, {}); expect(updateVersionStub).to.have.been.calledOnceWithExactly( SITE, From 4f639508e79b3dade896ac587923ed796bf13e9c Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Tue, 1 Nov 2022 11:07:31 -0400 Subject: [PATCH 0674/1699] Fix parallel requests in the functions emulator (#5149) * adding a promise queue to handle parallel requests * adding create state to workers for concurrent requests * update doc string * increase timeout for parallel firestore * adding e2e test for parallel requests * address pr comments --- CHANGELOG.md | 1 + scripts/triggers-end-to-end-tests/tests.ts | 18 ++++++++- scripts/triggers-end-to-end-tests/v2/index.js | 4 ++ src/emulator/functionsRuntimeWorker.ts | 20 +++++++++- .../emulators/functionsRuntimeWorker.spec.ts | 37 +++++++++++++++---- 5 files changed, 69 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0d5784d68..142ca90ffa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,4 @@ - Adds `--disable-triggers` flag to RTDB write commands. - Default enables experiment to skip deploying unmodified functions (#5192) - Default enables experiment to allow parameterized functions codebases (#5192) +- Fixes parallel requests in the functions emulator (#5149). diff --git a/scripts/triggers-end-to-end-tests/tests.ts b/scripts/triggers-end-to-end-tests/tests.ts index 17a3aadb85c..a0dc6928a12 100755 --- a/scripts/triggers-end-to-end-tests/tests.ts +++ b/scripts/triggers-end-to-end-tests/tests.ts @@ -122,6 +122,20 @@ describe("function triggers", () => { await test.stopEmulators(); }); + describe("https triggers", () => { + it("should handle parallel requests", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + const [resp1, resp2] = await Promise.all([ + test.invokeHttpFunction("httpsv2reaction"), + test.invokeHttpFunction("httpsv2reaction"), + ]); + + expect(resp1.status).to.eq(200); + expect(resp2.status).to.eq(200); + }); + }); + describe("database and firestore emulator triggers", () => { it("should write to the database emulator", async function (this) { this.timeout(EMULATOR_TEST_TIMEOUT); @@ -131,7 +145,7 @@ describe("function triggers", () => { }); it("should write to the firestore emulator", async function (this) { - this.timeout(EMULATOR_TEST_TIMEOUT); + this.timeout(EMULATOR_TEST_TIMEOUT * 2); const response = await test.writeToFirestore(); expect(response.status).to.equal(200); @@ -143,7 +157,7 @@ describe("function triggers", () => { * fixture state handlers to complete before we check * that state in the next test. */ - await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS * 2)); }); it("should have have triggered cloud functions", () => { diff --git a/scripts/triggers-end-to-end-tests/v2/index.js b/scripts/triggers-end-to-end-tests/v2/index.js index 7d1aeb89bff..3978bfad112 100644 --- a/scripts/triggers-end-to-end-tests/v2/index.js +++ b/scripts/triggers-end-to-end-tests/v2/index.js @@ -29,6 +29,10 @@ const START_DOCUMENT_NAME = "test/start"; admin.initializeApp(); +exports.httpsv2reaction = functionsV2.https.onRequest((req, res) => { + res.send("httpsv2reaction"); +}); + exports.pubsubv2reaction = functionsV2.pubsub.onMessagePublished(PUBSUB_TOPIC, (cloudevent) => { console.log(PUBSUB_FUNCTION_LOG); console.log("Message", JSON.stringify(cloudevent.data.message.json)); diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index e40b51c98eb..3358ac68311 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -12,6 +12,9 @@ import { Serializable } from "child_process"; type LogListener = (el: EmulatorLog) => any; export enum RuntimeWorkerState { + // Worker has been created but is not ready to accept work + CREATED = "CREATED", + // Worker is ready to accept new work IDLE = "IDLE", @@ -34,7 +37,7 @@ export class RuntimeWorker { stateEvents: EventEmitter = new EventEmitter(); private logListeners: Array = []; - private _state: RuntimeWorkerState = RuntimeWorkerState.IDLE; + private _state: RuntimeWorkerState = RuntimeWorkerState.CREATED; constructor(key: string, runtime: FunctionsRuntimeInstance) { this.id = uuid.v4(); @@ -86,6 +89,10 @@ export class RuntimeWorker { return lines[lines.length - 1]; } + readyForWork(): void { + this.state = RuntimeWorkerState.IDLE; + } + sendDebugMsg(debug: FunctionsRuntimeBundle["debug"]): Promise { return new Promise((resolve, reject) => { this.runtime.process.send(JSON.stringify(debug), (err) => { @@ -178,7 +185,11 @@ export class RuntimeWorker { path: "/__/health", socketPath: this.runtime.socketPath, }, - () => resolve() + () => { + // Set the worker state to IDLE for new work + this.readyForWork(); + resolve(); + } ) .end(); req.on("error", (error) => { @@ -323,6 +334,11 @@ export class RuntimeWorkerPool { return; } + /** + * Adds a worker to the pool. + * Caller must set the worker status to ready by calling + * `worker.readyForWork()` or `worker.waitForSocketReady()`. + */ addWorker( triggerId: string | undefined, runtime: FunctionsRuntimeInstance, diff --git a/src/test/emulators/functionsRuntimeWorker.spec.ts b/src/test/emulators/functionsRuntimeWorker.spec.ts index cbb0015f7fe..d0cb0ca557d 100644 --- a/src/test/emulators/functionsRuntimeWorker.spec.ts +++ b/src/test/emulators/functionsRuntimeWorker.spec.ts @@ -41,6 +41,7 @@ class MockRuntimeInstance implements FunctionsRuntimeInstance { */ class WorkerStateCounter { counts: { [state in RuntimeWorkerState]: number } = { + CREATED: 0, IDLE: 0, BUSY: 0, FINISHING: 0, @@ -49,6 +50,9 @@ class WorkerStateCounter { constructor(worker: RuntimeWorker) { this.increment(worker.state); + worker.stateEvents.on(RuntimeWorkerState.CREATED, () => { + this.increment(RuntimeWorkerState.CREATED); + }); worker.stateEvents.on(RuntimeWorkerState.IDLE, () => { this.increment(RuntimeWorkerState.IDLE); }); @@ -68,7 +72,13 @@ class WorkerStateCounter { } get total() { - return this.counts.IDLE + this.counts.BUSY + this.counts.FINISHING + this.counts.FINISHED; + return ( + this.counts.CREATED + + this.counts.IDLE + + this.counts.BUSY + + this.counts.FINISHING + + this.counts.FINISHED + ); } } @@ -76,47 +86,52 @@ describe("FunctionsRuntimeWorker", () => { const workerPool = new RuntimeWorkerPool(); describe("RuntimeWorker", () => { - it("goes from idle --> busy --> idle in normal operation", async () => { + it("goes from created --> idle --> busy --> idle in normal operation", async () => { const scope = nock("http://localhost").get("/").reply(200); const worker = new RuntimeWorker(workerPool.getKey("trigger"), new MockRuntimeInstance()); const counter = new WorkerStateCounter(worker); + worker.readyForWork(); await worker.request( { method: "GET", path: "/" }, httpMocks.createResponse({ eventEmitter: EventEmitter }) ); scope.done(); + expect(counter.counts.CREATED).to.eql(1); expect(counter.counts.BUSY).to.eql(1); expect(counter.counts.IDLE).to.eql(2); - expect(counter.total).to.eql(3); + expect(counter.total).to.eql(4); }); - it("goes from idle --> busy --> finished when there's an error", async () => { + it("goes from created --> idle --> busy --> finished when there's an error", async () => { const scope = nock("http://localhost").get("/").replyWithError("boom"); const worker = new RuntimeWorker(workerPool.getKey("trigger"), new MockRuntimeInstance()); const counter = new WorkerStateCounter(worker); + worker.readyForWork(); await worker.request( { method: "GET", path: "/" }, httpMocks.createResponse({ eventEmitter: EventEmitter }) ); scope.done(); + expect(counter.counts.CREATED).to.eql(1); expect(counter.counts.IDLE).to.eql(1); expect(counter.counts.BUSY).to.eql(1); expect(counter.counts.FINISHED).to.eql(1); - expect(counter.total).to.eql(3); + expect(counter.total).to.eql(4); }); - it("goes from busy --> finishing --> finished when marked", async () => { + it("goes from created --> busy --> finishing --> finished when marked", async () => { const scope = nock("http://localhost").get("/").replyWithError("boom"); const worker = new RuntimeWorker(workerPool.getKey("trigger"), new MockRuntimeInstance()); const counter = new WorkerStateCounter(worker); + worker.readyForWork(); const resp = httpMocks.createResponse({ eventEmitter: EventEmitter }); resp.on("end", () => { worker.state = RuntimeWorkerState.FINISHING; @@ -124,11 +139,12 @@ describe("FunctionsRuntimeWorker", () => { await worker.request({ method: "GET", path: "/" }, resp); scope.done(); + expect(counter.counts.CREATED).to.eql(1); expect(counter.counts.IDLE).to.eql(1); expect(counter.counts.BUSY).to.eql(1); expect(counter.counts.FINISHING).to.eql(1); expect(counter.counts.FINISHED).to.eql(1); - expect(counter.total).to.eql(4); + expect(counter.total).to.eql(5); }); }); @@ -144,6 +160,7 @@ describe("FunctionsRuntimeWorker", () => { // Add a worker and make sure it's there const worker = pool.addWorker(trigger, new MockRuntimeInstance()); + worker.readyForWork(); const triggerWorkers = pool.getTriggerWorkers(trigger); expect(triggerWorkers.length).length.to.eq(1); expect(pool.getIdleWorker(trigger)).to.eql(worker); @@ -170,6 +187,7 @@ describe("FunctionsRuntimeWorker", () => { // Add a worker to the pool that's destined to fail. const scope = nock("http://localhost").get("/").replyWithError("boom"); const worker = pool.addWorker(trigger, new MockRuntimeInstance()); + worker.readyForWork(); expect(pool.getIdleWorker(trigger)).to.eql(worker); // Send request to the worker. Request should fail, killing the worker. @@ -188,9 +206,11 @@ describe("FunctionsRuntimeWorker", () => { const trigger = "trigger1"; const busyWorker = pool.addWorker(trigger, new MockRuntimeInstance()); + busyWorker.readyForWork(); const busyWorkerCounter = new WorkerStateCounter(busyWorker); const idleWorker = pool.addWorker(trigger, new MockRuntimeInstance()); + idleWorker.readyForWork(); const idleWorkerCounter = new WorkerStateCounter(idleWorker); // Add a worker to the pool that's destined to fail. @@ -217,9 +237,11 @@ describe("FunctionsRuntimeWorker", () => { const trigger = "trigger1"; const busyWorker = pool.addWorker(trigger, new MockRuntimeInstance()); + busyWorker.readyForWork(); const busyWorkerCounter = new WorkerStateCounter(busyWorker); const idleWorker = pool.addWorker(trigger, new MockRuntimeInstance()); + idleWorker.readyForWork(); const idleWorkerCounter = new WorkerStateCounter(idleWorker); // Add a worker to the pool that's destined to fail. @@ -248,6 +270,7 @@ describe("FunctionsRuntimeWorker", () => { const pool = new RuntimeWorkerPool(FunctionsExecutionMode.SEQUENTIAL); const worker = pool.addWorker(trigger1, new MockRuntimeInstance()); + worker.readyForWork(); const resp = httpMocks.createResponse({ eventEmitter: EventEmitter }); resp.on("end", () => { From 5a6b3760880d47c1a6402c3192a8bdc9a3a64af0 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 1 Nov 2022 15:28:44 -0700 Subject: [PATCH 0675/1699] Add changelog (#5202) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 142ca90ffa8..31f7fbf0349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,4 @@ - Default enables experiment to skip deploying unmodified functions (#5192) - Default enables experiment to allow parameterized functions codebases (#5192) - Fixes parallel requests in the functions emulator (#5149). +- Unspecified functions concurrency will shift between the defaults of 1 or 80 when CPU is changed to support/not support concurrency (#5196) From 6742d76cea8988ecb48587977471d325d887b288 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 1 Nov 2022 22:38:26 +0000 Subject: [PATCH 0676/1699] 11.16.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a8c9e68dfb4..db6a738cd42 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.15.0", + "version": "11.16.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.15.0", + "version": "11.16.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 93cb8e3fa86..3ccb1fc8118 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.15.0", + "version": "11.16.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 7abccc63e6851a33aae8ff3461d15677516c3fb1 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 1 Nov 2022 22:38:39 +0000 Subject: [PATCH 0677/1699] [firebase-release] Removed change log and reset repo after 11.16.0 release --- CHANGELOG.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f7fbf0349..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +0,0 @@ -- Fixes an issue where an error during product provisioning check would block `firebase deploy --only extensions` (#5074). -- Releases RTDB Emulator v4.11.0: Wire protocol update for `startAfter`, `endBefore`. -- Changes `superstatic` dependency to `v8`, addressing Hosting emulator issues on Windows. -- Fixes internal library that was not being correctly published. -- Add support for Next.js 13 in firebase deploy. -- Next.js routes with revalidate are now handled by the a backing Cloud Function. -- Adds `--disable-triggers` flag to RTDB write commands. -- Default enables experiment to skip deploying unmodified functions (#5192) -- Default enables experiment to allow parameterized functions codebases (#5192) -- Fixes parallel requests in the functions emulator (#5149). -- Unspecified functions concurrency will shift between the defaults of 1 or 80 when CPU is changed to support/not support concurrency (#5196) From ab27d16717781c296627efc0547a0e8d11e5c1d0 Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:20:01 -0700 Subject: [PATCH 0678/1699] Storage default rules (#5167) * Default storage config using demo prefix. --- src/emulator/storage/rules/config.ts | 13 ++++++++++ .../emulators/storage/rules/config.spec.ts | 26 +++++++++++++++++++ templates/emulators/default_storage.rules | 8 ++++++ 3 files changed, 47 insertions(+) create mode 100644 templates/emulators/default_storage.rules diff --git a/src/emulator/storage/rules/config.ts b/src/emulator/storage/rules/config.ts index 3786ecae718..911e1effd01 100644 --- a/src/emulator/storage/rules/config.ts +++ b/src/emulator/storage/rules/config.ts @@ -3,6 +3,9 @@ import { FirebaseError } from "../../../error"; import { readFile } from "../../../fsutils"; import { Options } from "../../../options"; import { SourceFile } from "./types"; +import { Constants } from "../../constants"; +import { Emulators } from "../../types"; +import { EmulatorLogger } from "../../emulatorLogger"; function getSourceFile(rules: string, options: Options): SourceFile { const path = options.config.path(rules); @@ -21,6 +24,16 @@ export function getStorageRulesConfig( ): SourceFile | RulesConfig[] { const storageConfig = options.config.data.storage; if (!storageConfig) { + if (Constants.isDemoProject(projectId)) { + const storageLogger = EmulatorLogger.forEmulator(Emulators.STORAGE); + storageLogger.logLabeled( + "BULLET", + "storage", + `Detected demo project ID "${projectId}", using a default (open) rules configuration.` + ); + const path = __dirname + "/../../../../templates/emulators/default_storage.rules"; + return { name: path, content: readFile(path) }; + } throw new FirebaseError( "Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration" ); diff --git a/src/test/emulators/storage/rules/config.spec.ts b/src/test/emulators/storage/rules/config.spec.ts index a797c83e3e9..ddd121dabec 100644 --- a/src/test/emulators/storage/rules/config.spec.ts +++ b/src/test/emulators/storage/rules/config.spec.ts @@ -31,6 +31,32 @@ describe("Storage Rules Config", () => { expect(result.content).to.contain("allow read, write: if true"); }); + it("should use default config for project IDs using demo- prefix if no rules file exists", () => { + const config = getOptions({ + data: {}, + path: resolvePath, + }); + const result = getStorageRulesConfig("demo-projectid", config) as SourceFile; + + expect(result.name).to.contain("templates/emulators/default_storage.rules"); + expect(result.content).to.contain("allow read, write;"); + }); + + it("should use provided config for project IDs using demo- prefix if the provided config exists", () => { + const rulesFile = "storage.rules"; + const rulesContent = Buffer.from(StorageRulesFiles.readWriteIfTrue.content); + const path = persistence.appendBytes(rulesFile, rulesContent); + + const config = getOptions({ + data: { storage: { rules: path } }, + path: resolvePath, + }); + const result = getStorageRulesConfig("demo-projectid", config) as SourceFile; + + expect(result.name).to.equal(path); + expect(result.content).to.contain("allow read, write: if true"); + }); + it("should parse rules file for multiple targets", () => { const mainRulesContent = Buffer.from(StorageRulesFiles.readWriteIfTrue.content); const otherRulesContent = Buffer.from(StorageRulesFiles.readWriteIfAuth.content); diff --git a/templates/emulators/default_storage.rules b/templates/emulators/default_storage.rules new file mode 100644 index 00000000000..80c50ce59be --- /dev/null +++ b/templates/emulators/default_storage.rules @@ -0,0 +1,8 @@ +rules_version = '2'; +service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write; + } + } +} From b02065f779a30470ce7d32aa3e0683a1def29c2d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 3 Nov 2022 08:45:02 -0700 Subject: [PATCH 0679/1699] Fix integration test. (#5204) Concurrency will aggressively default to 80 per https://github.com/firebase/firebase-tools/pull/5196. --- scripts/functions-deploy-tests/tests.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/functions-deploy-tests/tests.ts b/scripts/functions-deploy-tests/tests.ts index 7a4a75078ee..2f18b924aa9 100644 --- a/scripts/functions-deploy-tests/tests.ts +++ b/scripts/functions-deploy-tests/tests.ts @@ -306,7 +306,10 @@ describe("firebase deploy", function (this) { if (e.platform === "gcfv2") { expect(e).to.include({ cpu: 2, - concurrency: 42, + // EXCEPTION: concurrency + // Firebase will aggressively set concurrency to 80 when the CPU setting allows for it + // AND when the concurrency is NOT set on the source code. + concurrency: 80, }); } // BUGBUG: As implemented, Cloud Tasks update doesn't preserve existing setting. Instead, it overwrites the From ea2a9ecab402febcd28ccaa402fd56e7202b7a94 Mon Sep 17 00:00:00 2001 From: abhis3 Date: Thu, 3 Nov 2022 16:55:56 -0400 Subject: [PATCH 0680/1699] Add support for object list using certain Admin SDKs (#5209) * Add support for object list using certain Admin SDKs --- CHANGELOG.md | 1 + .../conformance/gcs.endpoints.test.ts | 26 +++++++++++++++++++ src/emulator/storage/apis/gcloud.ts | 2 +- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..74ad7091bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Add support for object list using certain Admin SDKs (#5208) diff --git a/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts b/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts index 6185ac6d2ed..792ec8f44ef 100644 --- a/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts +++ b/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts @@ -293,4 +293,30 @@ describe("GCS endpoint conformance tests", () => { }); }); }); + + describe("List protocols", () => { + describe("list objects", () => { + // This test is for the '/storage/v1/b/:bucketId/o' url pattern, which is used specifically by the GO Admin SDK + it("should list objects in the provided bucket", async () => { + await supertest(storageHost) + .post(`/upload/storage/v1/b/${storageBucket}/o?name=${TEST_FILE_NAME}`) + .set(authHeader) + .send(Buffer.from("hello world")) + .expect(200); + + await supertest(storageHost) + .post(`/upload/storage/v1/b/${storageBucket}/o?name=${TEST_FILE_NAME}2`) + .set(authHeader) + .send(Buffer.from("hello world")) + .expect(200); + + const data = await supertest(storageHost) + .get(`/storage/v1/b/${storageBucket}/o`) + .set(authHeader) + .expect(200) + .then((res) => res.body); + expect(data.items.length).to.equal(2); + }); + }); + }); }); diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index ff4748921ae..017ab40f671 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -135,7 +135,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return res.json(new CloudStorageObjectMetadata(updatedMetadata)); }); - gcloudStorageAPI.get("/b/:bucketId/o", async (req, res) => { + gcloudStorageAPI.get(["/b/:bucketId/o", "/storage/v1/b/:bucketId/o"], async (req, res) => { let listResponse: ListObjectsResponse; // TODO validate that all query params are single strings and are not repeated. try { From 98e23eded74e49f58d48be0417dc66bd85ea6e11 Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Fri, 4 Nov 2022 19:06:20 -0400 Subject: [PATCH 0681/1699] Fixes source token expiration by token refresh (#5198) * refresh source token * check if token is expired right before deploy call * use Date lib, rename states & remove enums * clean up, revert timer changes * add type guard to token states --- CHANGELOG.md | 1 + src/deploy/functions/release/fabricator.ts | 6 +- .../functions/release/sourceTokenScraper.ts | 51 +++++++++++---- .../release/sourceTokenScraper.spec.ts | 63 ++++++++++++++++--- 4 files changed, 101 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ad7091bcf..a0516d8cf0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Add support for object list using certain Admin SDKs (#5208) +- Fixes source token expiration issue by acquiring new source token upon expiration. diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 4316cb0e37a..310cf82dcb6 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -210,9 +210,10 @@ export class Fabricator { if (apiFunction.httpsTrigger) { apiFunction.httpsTrigger.securityLevel = "SECURE_ALWAYS"; } - apiFunction.sourceToken = await scraper.tokenPromise(); const resultFunction = await this.functionExecutor .run(async () => { + // try to get the source token right before deploying + apiFunction.sourceToken = await scraper.getToken(); const op: { name: string } = await gcf.createFunction(apiFunction); return poller.pollOperation({ ...gcfV1PollerOptions, @@ -374,9 +375,10 @@ export class Fabricator { throw new Error("Precondition failed"); } const apiFunction = gcf.functionFromEndpoint(endpoint, sourceUrl); - apiFunction.sourceToken = await scraper.tokenPromise(); + const resultFunction = await this.functionExecutor .run(async () => { + apiFunction.sourceToken = await scraper.getToken(); const op: { name: string } = await gcf.updateFunction(apiFunction); return await poller.pollOperation({ ...gcfV1PollerOptions, diff --git a/src/deploy/functions/release/sourceTokenScraper.ts b/src/deploy/functions/release/sourceTokenScraper.ts index 482e3d77406..96b5a04d742 100644 --- a/src/deploy/functions/release/sourceTokenScraper.ts +++ b/src/deploy/functions/release/sourceTokenScraper.ts @@ -1,28 +1,55 @@ +import { FirebaseError } from "../../../error"; +import { assertExhaustive } from "../../../functional"; import { logger } from "../../../logger"; +type TokenFetchState = "NONE" | "FETCHING" | "VALID"; + /** * GCF v1 deploys support reusing a build between function deploys. * This class will return a resolved promise for its first call to tokenPromise() * and then will always return a promise that is resolved by the poller function. */ export class SourceTokenScraper { - private firstCall = true; - private resolve!: (token: string) => void; + private tokenValidDurationMs; + private resolve!: (token?: string) => void; private promise: Promise; + private expiry: number | undefined; + private fetchState: TokenFetchState; - constructor() { + constructor(validDurationMs = 1500000) { + this.tokenValidDurationMs = validDurationMs; this.promise = new Promise((resolve) => (this.resolve = resolve)); + this.fetchState = "NONE"; + } + + async getToken(): Promise { + if (this.fetchState === "NONE") { + this.fetchState = "FETCHING"; + return undefined; + } else if (this.fetchState === "FETCHING") { + return this.promise; // wait until we get a source token + } else if (this.fetchState === "VALID") { + if (this.isTokenExpired()) { + this.fetchState = "FETCHING"; + this.promise = new Promise((resolve) => (this.resolve = resolve)); + return undefined; + } + return this.promise; + } else { + assertExhaustive(this.fetchState); + } } - // Token Promise will return undefined for the first caller - // (because we presume it's this function's source token we'll scrape) - // and then returns the promise generated from the first function's onCall - tokenPromise(): Promise { - if (this.firstCall) { - this.firstCall = false; - return Promise.resolve(undefined); + isTokenExpired(): boolean { + if (this.expiry === undefined) { + throw new FirebaseError( + "Your deployment is checking the expiration of a source token that has not yet been polled. " + + "Hitting this case should never happen and should be considered a bug. " + + "Please file an issue at https://github.com/firebase/firebase-tools/issues " + + "and try deploying your functions again." + ); } - return this.promise; + return Date.now() >= this.expiry; } get poller() { @@ -32,6 +59,8 @@ export class SourceTokenScraper { op.metadata?.target?.split("/") || []; logger.debug(`Got source token ${op.metadata?.sourceToken} for region ${region as string}`); this.resolve(op.metadata?.sourceToken); + this.fetchState = "VALID"; + this.expiry = Date.now() + this.tokenValidDurationMs; } }; } diff --git a/src/test/deploy/functions/release/sourceTokenScraper.spec.ts b/src/test/deploy/functions/release/sourceTokenScraper.spec.ts index 40927a0b25c..28a0490e99e 100644 --- a/src/test/deploy/functions/release/sourceTokenScraper.spec.ts +++ b/src/test/deploy/functions/release/sourceTokenScraper.spec.ts @@ -2,23 +2,23 @@ import { expect } from "chai"; import { SourceTokenScraper } from "../../../../deploy/functions/release/sourceTokenScraper"; -describe("SourcTokenScraper", () => { +describe("SourceTokenScraper", () => { it("immediately provides the first result", async () => { const scraper = new SourceTokenScraper(); - await expect(scraper.tokenPromise()).to.eventually.be.undefined; + await expect(scraper.getToken()).to.eventually.be.undefined; }); - it("provides results after the firt operation completes", async () => { + it("provides results after the first operation completes", async () => { const scraper = new SourceTokenScraper(); // First result comes right away; - await expect(scraper.tokenPromise()).to.eventually.be.undefined; + await expect(scraper.getToken()).to.eventually.be.undefined; let gotResult = false; const timeout = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Timeout")), 10); }); const getResult = (async () => { - await scraper.tokenPromise(); + await scraper.getToken(); gotResult = true; })(); await expect(Promise.race([getResult, timeout])).to.be.rejectedWith("Timeout"); @@ -31,7 +31,7 @@ describe("SourcTokenScraper", () => { it("provides tokens from an operation", async () => { const scraper = new SourceTokenScraper(); // First result comes right away - await expect(scraper.tokenPromise()).to.eventually.be.undefined; + await expect(scraper.getToken()).to.eventually.be.undefined; scraper.poller({ metadata: { @@ -39,6 +39,55 @@ describe("SourcTokenScraper", () => { target: "projects/p/locations/l/functions/f", }, }); - await expect(scraper.tokenPromise()).to.eventually.equal("magic token"); + await expect(scraper.getToken()).to.eventually.equal("magic token"); + }); + + it("refreshes token after timer expires", async () => { + const scraper = new SourceTokenScraper(10); + await expect(scraper.getToken()).to.eventually.be.undefined; + scraper.poller({ + metadata: { + sourceToken: "magic token", + target: "projects/p/locations/l/functions/f", + }, + }); + await expect(scraper.getToken()).to.eventually.equal("magic token"); + const timeout = (duration: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, duration)); + }; + await timeout(50); + await expect(scraper.getToken()).to.eventually.be.undefined; + scraper.poller({ + metadata: { + sourceToken: "magic token #2", + target: "projects/p/locations/l/functions/f", + }, + }); + await expect(scraper.getToken()).to.eventually.equal("magic token #2"); + }); + + it("concurrent requests for source token", async () => { + const scraper = new SourceTokenScraper(); + + const promises = []; + for (let i = 0; i < 3; i++) { + promises.push(scraper.getToken()); + } + scraper.poller({ + metadata: { + sourceToken: "magic token", + target: "projects/p/locations/l/functions/f", + }, + }); + + let successes = 0; + const tokens = await Promise.all(promises); + for (const tok of tokens) { + if (tok === "magic token") { + successes++; + } + } + expect(tokens.includes(undefined)).to.be.true; + expect(successes).to.equal(2); }); }); From 72d23ec4e77f0d5b52f62e7be1bf083fa94c95af Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Mon, 7 Nov 2022 15:58:34 +0900 Subject: [PATCH 0682/1699] Handle gzip compression in Storage Emulator (#5185) * fix gzip for gcloud * fix firebase * revert * lint * update changelog --- CHANGELOG.md | 1 + .../conformance/env.ts | 9 ++- .../conformance/firebase-js-sdk.test.ts | 23 +++--- .../conformance/firebase.endpoints.test.ts | 73 ++++++++++++++++++- .../conformance/gcs-js-sdk.test.ts | 72 +++++++++++------- .../conformance/gcs.endpoints.test.ts | 72 ++++++++++++++++++ scripts/storage-emulator-integration/run.sh | 4 + src/emulator/storage/apis/firebase.ts | 43 ++--------- src/emulator/storage/apis/gcloud.ts | 37 +--------- src/emulator/storage/apis/shared.ts | 57 +++++++++++++++ 10 files changed, 277 insertions(+), 114 deletions(-) create mode 100644 src/emulator/storage/apis/shared.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a0516d8cf0f..cb3dd4e68f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ +- Fixes gzipped file handling in Storage Emulator. - Add support for object list using certain Admin SDKs (#5208) - Fixes source token expiration issue by acquiring new source token upon expiration. diff --git a/scripts/storage-emulator-integration/conformance/env.ts b/scripts/storage-emulator-integration/conformance/env.ts index ab5d29736b1..c131178e4ee 100644 --- a/scripts/storage-emulator-integration/conformance/env.ts +++ b/scripts/storage-emulator-integration/conformance/env.ts @@ -74,7 +74,7 @@ function readEmulatorConfig(): FrameworkOptions { class ConformanceTestEnvironment { private _prodAppConfig: any; private _emulatorConfig: any; - private _prodServiceAccountKeyJson?: any; + private _prodServiceAccountKeyJson?: any | null; private _adminAccessToken?: string; get useProductionServers() { @@ -125,9 +125,10 @@ class ConformanceTestEnvironment { get prodServiceAccountKeyJson() { if (this._prodServiceAccountKeyJson === undefined) { const filePath = path.join(__dirname, TEST_CONFIG.prodServiceAccountKeyFilePath); - return TEST_CONFIG.prodServiceAccountKeyFilePath && fs.existsSync(filePath) - ? readAbsoluteJson(filePath) - : null; + this._prodServiceAccountKeyJson = + TEST_CONFIG.prodServiceAccountKeyFilePath && fs.existsSync(filePath) + ? readAbsoluteJson(filePath) + : null; } return this._prodServiceAccountKeyJson; } diff --git a/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts b/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts index 791e5ac57a5..3f3e51f56ec 100644 --- a/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts +++ b/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts @@ -14,11 +14,14 @@ import { SMALL_FILE_SIZE, TEST_SETUP_TIMEOUT, getTmpDir, - writeToFile, } from "../utils"; const TEST_FILE_NAME = "testing/storage_ref/testFile"; +// Test case that should only run when targeting the emulator. +// Example use: emulatorOnly.it("Local only test case", () => {...}); +const emulatorOnly = { it: TEST_ENV.useProductionServers ? it.skip : it }; + describe("Firebase Storage JavaScript SDK conformance tests", () => { const storageBucket = TEST_ENV.appConfig.storageBucket; const expectedFirebaseHost = TEST_ENV.firebaseHost; @@ -27,11 +30,6 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { const tmpDir = getTmpDir(); const smallFilePath: string = createRandomFile("small_file", SMALL_FILE_SIZE, tmpDir); const emptyFilePath: string = createRandomFile("empty_file", 0, tmpDir); - const imageFilePath = writeToFile( - "image_base64", - Buffer.from(IMAGE_FILE_BASE64, "base64"), - tmpDir - ); let test: EmulatorEndToEndTest; let testBucket: Bucket; @@ -451,7 +449,8 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { }); it("serves the right content", async () => { - await testBucket.upload(imageFilePath, { destination: TEST_FILE_NAME }); + const contents = Buffer.from("hello world"); + await testBucket.file(TEST_FILE_NAME).save(contents); await signInToFirebaseAuth(page); const downloadUrl = await page.evaluate((filename) => { @@ -460,11 +459,13 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { await new Promise((resolve, reject) => { TEST_ENV.requestClient.get(downloadUrl, (response) => { - const data: any = []; + let data = Buffer.alloc(0); response - .on("data", (chunk) => data.push(chunk)) + .on("data", (chunk) => { + data = Buffer.concat([data, chunk]); + }) .on("end", () => { - expect(Buffer.concat(data)).to.deep.equal(Buffer.from(IMAGE_FILE_BASE64, "base64")); + expect(data).to.deep.equal(contents); }) .on("close", resolve) .on("error", reject); @@ -472,7 +473,7 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { }); }); - it("serves content successfully when spammed with calls", async () => { + emulatorOnly.it("serves content successfully when spammed with calls", async () => { const NUMBER_OF_FILES = 10; const allFileNames: string[] = []; for (let i = 0; i < NUMBER_OF_FILES; i++) { diff --git a/scripts/storage-emulator-integration/conformance/firebase.endpoints.test.ts b/scripts/storage-emulator-integration/conformance/firebase.endpoints.test.ts index a087279e3bd..dcd7e246411 100644 --- a/scripts/storage-emulator-integration/conformance/firebase.endpoints.test.ts +++ b/scripts/storage-emulator-integration/conformance/firebase.endpoints.test.ts @@ -3,6 +3,7 @@ import { expect } from "chai"; import * as admin from "firebase-admin"; import * as fs from "fs"; import * as supertest from "supertest"; +import { gunzipSync } from "zlib"; import { TEST_ENV } from "./env"; import { EmulatorEndToEndTest } from "../../integration-helpers/framework"; import { @@ -343,7 +344,6 @@ describe("Firebase Storage endpoint conformance tests", () => { }) .expect(200) .then((res) => { - console.log(res); return new URL(res.header["x-goog-upload-url"]); }); @@ -475,6 +475,77 @@ describe("Firebase Storage endpoint conformance tests", () => { }); }); + describe("gzip", () => { + it("should serve gunzipped file by default", async () => { + const contents = Buffer.from("hello world"); + const fileName = "gzippedFile"; + const file = testBucket.file(fileName); + await file.save(contents, { + gzip: true, + contentType: "text/plain", + }); + + // Use requestClient since supertest will decompress the response body by default. + await new Promise((resolve, reject) => { + TEST_ENV.requestClient.get( + `${firebaseHost}/v0/b/${storageBucket}/o/${fileName}?alt=media`, + { headers: { ...authHeader } }, + (res) => { + expect(res.headers["content-encoding"]).to.be.undefined; + expect(res.headers["content-length"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.eql("text/plain"); + + let responseBody = Buffer.alloc(0); + res + .on("data", (chunk) => { + responseBody = Buffer.concat([responseBody, chunk]); + }) + .on("end", () => { + expect(responseBody).to.be.eql(contents); + }) + .on("close", resolve) + .on("error", reject); + } + ); + }); + }); + + it("should serve gzipped file if Accept-Encoding header allows", async () => { + const contents = Buffer.from("hello world"); + const fileName = "gzippedFile"; + const file = testBucket.file(fileName); + await file.save(contents, { + gzip: true, + contentType: "text/plain", + }); + + // Use requestClient since supertest will decompress the response body by default. + await new Promise((resolve, reject) => { + TEST_ENV.requestClient.get( + `${firebaseHost}/v0/b/${storageBucket}/o/${fileName}?alt=media`, + { headers: { ...authHeader, "Accept-Encoding": "gzip" } }, + (res) => { + expect(res.headers["content-encoding"]).to.be.eql("gzip"); + expect(res.headers["content-type"]).to.be.eql("text/plain"); + + let responseBody = Buffer.alloc(0); + res + .on("data", (chunk) => { + responseBody = Buffer.concat([responseBody, chunk]); + }) + .on("end", () => { + expect(responseBody).to.not.be.eql(contents); + const decompressed = gunzipSync(responseBody); + expect(decompressed).to.be.eql(contents); + }) + .on("close", resolve) + .on("error", reject); + } + ); + }); + }); + }); + describe("tokens", () => { beforeEach(async () => { await testBucket.upload(smallFilePath, { destination: TEST_FILE_NAME }); diff --git a/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts b/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts index 17c2ca0bda4..af10db91693 100644 --- a/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts +++ b/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts @@ -3,7 +3,6 @@ import { expect } from "chai"; import * as admin from "firebase-admin"; import * as fs from "fs"; import { EmulatorEndToEndTest } from "../../integration-helpers/framework"; -import * as supertest from "supertest"; import { TEST_ENV } from "./env"; import { createRandomFile, @@ -13,6 +12,7 @@ import { TEST_SETUP_TIMEOUT, getTmpDir, } from "../utils"; +import { gunzipSync } from "zlib"; // Test case that should only run when targeting the emulator. // Example use: emulatorOnly.it("Local only test case", () => {...}); @@ -27,7 +27,6 @@ describe("GCS Javascript SDK conformance tests", () => { const storageBucket = TEST_ENV.appConfig.storageBucket; const otherStorageBucket = TEST_ENV.secondTestBucket; const storageHost = TEST_ENV.storageHost; - const firebaseHost = TEST_ENV.firebaseHost; const googleapisHost = TEST_ENV.googleapisHost; let test: EmulatorEndToEndTest; @@ -109,14 +108,6 @@ describe("GCS Javascript SDK conformance tests", () => { fs.unlinkSync(content2); }); - it("should handle gzip'd uploads", async () => { - // This appears to pass, but the file gets corrupted cause it's gzipped? - // expect(true).to.be.false; - await testBucket.upload(smallFilePath, { - gzip: true, - }); - }); - it("should upload with provided metadata", async () => { const metadata = { contentDisposition: "attachment", @@ -139,27 +130,15 @@ describe("GCS Javascript SDK conformance tests", () => { metadata: {}, }); - const cloudFile = testBucket.file(testFileName); + const file = testBucket.file(testFileName); const incomingMetadata = { metadata: { firebaseStorageDownloadTokens: "myFirstToken,mySecondToken", }, }; - await cloudFile.setMetadata(incomingMetadata); - - // Check that the tokens are saved in Firebase metadata - await supertest(firebaseHost) - .get(`/v0/b/${testBucket.name}/o/${encodeURIComponent(testFileName)}`) - .expect(200) - .then((res) => { - const firebaseMd = res.body; - expect(firebaseMd.downloadTokens).to.equal( - incomingMetadata.metadata.firebaseStorageDownloadTokens - ); - }); + await file.setMetadata(incomingMetadata); - // Check that the tokens are saved in Cloud metadata - const [storedMetadata] = await cloudFile.getMetadata(); + const [storedMetadata] = await file.getMetadata(); expect(storedMetadata.metadata.firebaseStorageDownloadTokens).to.deep.equal( incomingMetadata.metadata.firebaseStorageDownloadTokens ); @@ -392,6 +371,26 @@ describe("GCS Javascript SDK conformance tests", () => { }); describe(".file()", () => { + describe("#save()", () => { + it("should save", async () => { + const contents = Buffer.from("hello world"); + + const file = testBucket.file("gzippedFile"); + await file.save(contents, { contentType: "text/plain" }); + + expect(file.metadata.contentType).to.be.eql("text/plain"); + const [downloadedContents] = await file.download(); + expect(downloadedContents).to.be.eql(contents); + }); + + it("should handle gzipped uploads", async () => { + const file = testBucket.file("gzippedFile"); + await file.save("hello world", { gzip: true }); + + expect(file.metadata.contentEncoding).to.be.eql("gzip"); + }); + }); + describe("#exists()", () => { it("should return false for a file that does not exist", async () => { // Ensure that the file exists on the bucket before deleting it @@ -488,6 +487,29 @@ describe("GCS Javascript SDK conformance tests", () => { expect(err).to.have.property("code", 404); expect(err).not.have.nested.property("errors[0]"); }); + + it("should decompress gzipped file", async () => { + const contents = Buffer.from("hello world"); + + const file = testBucket.file("gzippedFile"); + await file.save(contents, { gzip: true }); + + const [downloadedContents] = await file.download(); + expect(downloadedContents).to.be.eql(contents); + }); + + it("should serve gzipped file if decompress option specified", async () => { + const contents = Buffer.from("hello world"); + + const file = testBucket.file("gzippedFile"); + await file.save(contents, { gzip: true }); + + const [downloadedContents] = await file.download({ decompress: false }); + expect(downloadedContents).to.not.be.eql(contents); + + const ungzippedContents = gunzipSync(downloadedContents); + expect(ungzippedContents).to.be.eql(contents); + }); }); describe("#copy()", () => { diff --git a/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts b/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts index 792ec8f44ef..1f772f5248a 100644 --- a/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts +++ b/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts @@ -4,6 +4,7 @@ import * as admin from "firebase-admin"; import * as fs from "fs"; import * as supertest from "supertest"; import { EmulatorEndToEndTest } from "../../integration-helpers/framework"; +import { gunzipSync } from "zlib"; import { TEST_ENV } from "./env"; import { EMULATORS_SHUTDOWN_DELAY_MS, @@ -294,6 +295,77 @@ describe("GCS endpoint conformance tests", () => { }); }); + describe("Gzip", () => { + it("should serve gunzipped file by default", async () => { + const contents = Buffer.from("hello world"); + const fileName = "gzippedFile"; + const file = testBucket.file(fileName); + await file.save(contents, { + gzip: true, + contentType: "text/plain", + }); + + // Use requestClient since supertest will decompress the response body by default. + await new Promise((resolve, reject) => { + TEST_ENV.requestClient.get( + `${storageHost}/download/storage/v1/b/${storageBucket}/o/${fileName}?alt=media`, + { headers: { ...authHeader } }, + (res) => { + expect(res.headers["content-encoding"]).to.be.undefined; + expect(res.headers["content-length"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.eql("text/plain"); + + let responseBody = Buffer.alloc(0); + res + .on("data", (chunk) => { + responseBody = Buffer.concat([responseBody, chunk]); + }) + .on("end", () => { + expect(responseBody).to.be.eql(contents); + }) + .on("close", resolve) + .on("error", reject); + } + ); + }); + }); + + it("should serve gzipped file if Accept-Encoding header allows", async () => { + const contents = Buffer.from("hello world"); + const fileName = "gzippedFile"; + const file = testBucket.file(fileName); + await file.save(contents, { + gzip: true, + contentType: "text/plain", + }); + + // Use requestClient since supertest will decompress the response body by default. + await new Promise((resolve, reject) => { + TEST_ENV.requestClient.get( + `${storageHost}/download/storage/v1/b/${storageBucket}/o/${fileName}?alt=media`, + { headers: { ...authHeader, "Accept-Encoding": "gzip" } }, + (res) => { + expect(res.headers["content-encoding"]).to.be.eql("gzip"); + expect(res.headers["content-type"]).to.be.eql("text/plain"); + + let responseBody = Buffer.alloc(0); + res + .on("data", (chunk) => { + responseBody = Buffer.concat([responseBody, chunk]); + }) + .on("end", () => { + expect(responseBody).to.not.be.eql(contents); + const decompressed = gunzipSync(responseBody); + expect(decompressed).to.be.eql(contents); + }) + .on("close", resolve) + .on("error", reject); + } + ); + }); + }); + }); + describe("List protocols", () => { describe("list objects", () => { // This test is for the '/storage/v1/b/:bucketId/o' url pattern, which is used specifically by the GO Admin SDK diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index 413fd28b9b3..f2fe4cefceb 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -4,6 +4,9 @@ set -e # Immediately exit on failure # Globally link the CLI for the testing framework ./scripts/npm-link.sh +# Set application default credentials. +source scripts/set-default-credentials.sh + # Prepare the storage emulator rules runtime firebase setup:emulators:storage @@ -16,3 +19,4 @@ mocha scripts/storage-emulator-integration/internal/tests.ts mocha scripts/storage-emulator-integration/multiple-targets/tests.ts mocha scripts/storage-emulator-integration/conformance/*.test.ts + diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 60544de9c31..5a0c88369de 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -1,10 +1,10 @@ import { EmulatorLogger } from "../../emulatorLogger"; import { Emulators } from "../../types"; import * as uuid from "uuid"; -import { gunzipSync } from "zlib"; import { IncomingMetadata, OutgoingFirebaseMetadata, StoredFileMetadata } from "../metadata"; import { Request, Response, Router } from "express"; import { StorageEmulator } from "../index"; +import { sendFileBytes } from "./shared"; import { EmulatorRegistry } from "../../registry"; import { parseObjectUploadMultipartRequest } from "../multipart"; import { NotFoundError, ForbiddenError } from "../errors"; @@ -27,7 +27,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { if (process.env.STORAGE_EMULATOR_DEBUG) { firebaseStorageAPI.use((req, res, next) => { - console.log("--------------INCOMING REQUEST--------------"); + console.log("--------------INCOMING FIREBASE REQUEST--------------"); console.log(`${req.method.toUpperCase()} ${req.path}`); console.log("-- query:"); console.log(JSON.stringify(req.query, undefined, 2)); @@ -121,29 +121,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { // Object data request if (req.query.alt === "media") { - const isGZipped = metadata.contentEncoding === "gzip"; - if (isGZipped) { - data = gunzipSync(data); - } - res.setHeader("Accept-Ranges", "bytes"); - res.setHeader("Content-Type", metadata.contentType || "application/octet-stream"); - res.setHeader("Content-Disposition", metadata.contentDisposition || "inline"); - setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined }); - - const byteRange = req.range(data.byteLength, { combine: true }); - - if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) { - const range = byteRange[0]; - res.setHeader( - "Content-Range", - `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}` - ); - // Byte range requests are inclusive for start and end - res.status(206).end(data.slice(range.start, range.end + 1)); - } else { - res.end(data); - } - return; + return sendFileBytes(metadata, data, req, res); } // Object metadata request @@ -531,27 +509,16 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { return firebaseStorageAPI; } -function setObjectHeaders( - res: Response, - metadata: StoredFileMetadata, - headerOverride: { - "Content-Encoding": string | undefined; - } = { "Content-Encoding": undefined } -): void { +function setObjectHeaders(res: Response, metadata: StoredFileMetadata): void { if (metadata.contentDisposition) { res.setHeader("Content-Disposition", metadata.contentDisposition); } - - if (headerOverride["Content-Encoding"]) { - res.setHeader("Content-Encoding", headerOverride["Content-Encoding"]); - } else if (metadata.contentEncoding) { + if (metadata.contentEncoding) { res.setHeader("Content-Encoding", metadata.contentEncoding); } - if (metadata.cacheControl) { res.setHeader("Cache-Control", metadata.cacheControl); } - if (metadata.contentLanguage) { res.setHeader("Content-Language", metadata.contentLanguage); } diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index 017ab40f671..1efc031052c 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -1,5 +1,4 @@ import { Router } from "express"; -import { gunzipSync } from "zlib"; import { Emulators } from "../../types"; import { CloudStorageObjectAccessControlMetadata, @@ -7,11 +6,11 @@ import { IncomingMetadata, StoredFileMetadata, } from "../metadata"; +import { sendFileBytes } from "./shared"; import { EmulatorRegistry } from "../../registry"; import { StorageEmulator } from "../index"; import { EmulatorLogger } from "../../emulatorLogger"; import { GetObjectResponse, ListObjectsResponse } from "../files"; -import { crc32cToString } from "../crc"; import type { Request, Response } from "express"; import { parseObjectUploadMultipartRequest } from "../multipart"; import { Upload, UploadNotActiveError } from "../upload"; @@ -28,7 +27,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { // Debug statements if (process.env.STORAGE_EMULATOR_DEBUG) { gcloudStorageAPI.use((req, res, next) => { - console.log("--------------INCOMING REQUEST--------------"); + console.log("--------------INCOMING GCS REQUEST--------------"); console.log(`${req.method.toUpperCase()} ${req.path}`); console.log("-- query:"); console.log(JSON.stringify(req.query, undefined, 2)); @@ -429,38 +428,6 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { return gcloudStorageAPI; } -function sendFileBytes(md: StoredFileMetadata, data: Buffer, req: Request, res: Response): void { - const isGZipped = md.contentEncoding === "gzip"; - if (isGZipped) { - data = gunzipSync(data); - } - - res.setHeader("Accept-Ranges", "bytes"); - res.setHeader("Content-Type", md.contentType || "application/octet-stream"); - res.setHeader("Content-Disposition", md.contentDisposition || "attachment"); - res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding || ""); - res.setHeader("ETag", md.etag); - res.setHeader("Cache-Control", md.cacheControl || ""); - res.setHeader("x-goog-generation", `${md.generation}`); - res.setHeader("x-goog-metadatageneration", `${md.metageneration}`); - res.setHeader("x-goog-storage-class", md.storageClass); - res.setHeader("x-goog-hash", `crc32c=${crc32cToString(md.crc32c)},md5=${md.md5Hash}`); - - const byteRange = req.range(data.byteLength, { combine: true }); - - if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) { - const range = byteRange[0]; - res.setHeader( - "Content-Range", - `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}` - ); - // Byte range requests are inclusive for start and end - res.status(206).end(data.slice(range.start, range.end + 1)); - } else { - res.end(data); - } -} - /** Sends 404 matching API */ function sendObjectNotFound(req: Request, res: Response): void { res.status(404); diff --git a/src/emulator/storage/apis/shared.ts b/src/emulator/storage/apis/shared.ts new file mode 100644 index 00000000000..8cdb0fd09d9 --- /dev/null +++ b/src/emulator/storage/apis/shared.ts @@ -0,0 +1,57 @@ +import { gunzipSync } from "zlib"; +import { StoredFileMetadata } from "../metadata"; +import { Request, Response } from "express"; +import { crc32cToString } from "../crc"; + +/** Populates an object media GET Express response. */ +export function sendFileBytes( + md: StoredFileMetadata, + data: Buffer, + req: Request, + res: Response +): void { + let didGunzip = false; + if (md.contentEncoding === "gzip") { + const acceptEncoding = req.header("accept-encoding") || ""; + const shouldGunzip = !acceptEncoding.includes("gzip"); + if (shouldGunzip) { + data = gunzipSync(data); + didGunzip = true; + } + } + res.setHeader("Accept-Ranges", "bytes"); + res.setHeader("Content-Type", md.contentType || "application/octet-stream"); + res.setHeader("Content-Disposition", md.contentDisposition || "attachment"); + if (didGunzip) { + // Set to mirror server behavior and supress express's "content-length" header. + res.setHeader("Transfer-Encoding", "chunked"); + } else { + // Don't populate Content-Encoding if decompressed, see + // https://cloud.google.com/storage/docs/transcoding#decompressive_transcoding. + res.setHeader("Content-Encoding", md.contentEncoding || ""); + } + res.setHeader("ETag", md.etag); + res.setHeader("Cache-Control", md.cacheControl || ""); + res.setHeader("x-goog-generation", `${md.generation}`); + res.setHeader("x-goog-metadatageneration", `${md.metageneration}`); + res.setHeader("x-goog-storage-class", md.storageClass); + res.setHeader("x-goog-hash", `crc32c=${crc32cToString(md.crc32c)},md5=${md.md5Hash}`); + + // Content Range headers should be respected only if data was not decompressed, see + // https://cloud.google.com/storage/docs/transcoding#range. + const shouldRespectContentRange = !didGunzip; + if (shouldRespectContentRange) { + const byteRange = req.range(data.byteLength, { combine: true }); + if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) { + const range = byteRange[0]; + res.setHeader( + "Content-Range", + `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}` + ); + // Byte range requests are inclusive for start and end + res.status(206).end(data.slice(range.start, range.end + 1)); + return; + } + } + res.end(data); +} From db6223ecdafddf36266275800f02e71036c89c5f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 7 Nov 2022 09:57:13 -0800 Subject: [PATCH 0683/1699] Fix bug where event triggered functions failed in debug mode (#5211) A classic case of deadlock! Internally, the Functions Emulator maintains a work queue which processes unit of work called "tasks" with configured parallelism. When Functions Emulator runs in debug mode, the work queue runs in `SEQUENTIAL` mode where tasks are processed one by one in FIFO order. When event functions are triggered via the multicast route (e.g. storage and auth triggers), Functions Emulator submits a task per function associated with the event to the work queue where each task makes call to the event function via its HTTP endpoint. Let's call this task an "invocation task". When the Function Emulator receives an HTTP request for function invocation, it submits a new task to the work queue to handle the http request. Let's call this task a "http task". So, a call to the multicast route begets zero or more "invocation task" (one per trigger). Each "invocation task" begets exactly one "http task". The code today is written such that an "invocation task" will wait for the corresponding "http task" to complete. In debug mode this causes the invocation task to wait forever - the "http task" is stuck behind its "invocation task" in the queue. It never has chance to be processed. This PR proposes that "invocation task" doesn't wait for the corresponding "http task" to complete, effectively breaking the deadlock. The result is that "invocation task" now fire-and-forget an "http task". AFAIK, this doesn't have any negative effect because "invocation task" didn't do anything with the result of the "http task" anyway. Fixes https://github.com/firebase/firebase-tools/issues/5008, https://github.com/firebase/firebase-tools/issues/5050 --- CHANGELOG.md | 1 + .../tests.inspect.ts | 84 ++++++++++++------- src/emulator/functionsEmulator.ts | 20 ++--- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3dd4e68f4..6ec1619b56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Fixes gzipped file handling in Storage Emulator. - Add support for object list using certain Admin SDKs (#5208) - Fixes source token expiration issue by acquiring new source token upon expiration. +- Fix bug where emulated event triggered function broke in debug mode (#5211) diff --git a/scripts/triggers-end-to-end-tests/tests.inspect.ts b/scripts/triggers-end-to-end-tests/tests.inspect.ts index 9ea3d84cc1f..a25fe795d9b 100755 --- a/scripts/triggers-end-to-end-tests/tests.inspect.ts +++ b/scripts/triggers-end-to-end-tests/tests.inspect.ts @@ -10,6 +10,7 @@ const FIREBASE_PROJECT = process.env.FBTOOLS_TARGET_PROJECT || ""; * parallel emulator subprocesses. */ const TEST_SETUP_TIMEOUT = 80000; +const EMULATORS_WRITE_DELAY_MS = 5000; const EMULATORS_SHUTDOWN_DELAY_MS = 5000; function readConfig(): FrameworkOptions { @@ -28,7 +29,7 @@ describe("function triggers with inspect flag", () => { const config = readConfig(); test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, config); - await test.startEmulators(["--only", "functions", "--inspect-functions"]); + await test.startEmulators(["--only", "functions,auth,storage", "--inspect-functions"]); }); after(async function (this) { @@ -36,37 +37,60 @@ describe("function triggers with inspect flag", () => { await test.stopEmulators(); }); - it("should invoke correct function in the same codebase", async function (this) { - this.timeout(TEST_SETUP_TIMEOUT); - const v1response = await test.invokeHttpFunction("onreqv2b"); - expect(v1response.status).to.equal(200); - const v1body = await v1response.text(); - expect(v1body).to.deep.equal("onreqv2b"); - - const v2response = await test.invokeHttpFunction("onreqv2a"); - expect(v2response.status).to.equal(200); - const v2body = await v2response.text(); - expect(v2body).to.deep.equal("onreqv2a"); - }); + describe("http functions", () => { + it("should invoke correct function in the same codebase", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + const v1response = await test.invokeHttpFunction("onreqv2b"); + expect(v1response.status).to.equal(200); + const v1body = await v1response.text(); + expect(v1body).to.deep.equal("onreqv2b"); - it("should invoke correct function across codebases", async function (this) { - this.timeout(TEST_SETUP_TIMEOUT); - const v1response = await test.invokeHttpFunction("onReq"); - expect(v1response.status).to.equal(200); - const v1body = await v1response.text(); - expect(v1body).to.deep.equal("onReq"); - - const v2response = await test.invokeHttpFunction("onreqv2a"); - expect(v2response.status).to.equal(200); - const v2body = await v2response.text(); - expect(v2body).to.deep.equal("onreqv2a"); + const v2response = await test.invokeHttpFunction("onreqv2a"); + expect(v2response.status).to.equal(200); + const v2body = await v2response.text(); + expect(v2body).to.deep.equal("onreqv2a"); + }); + + it("should invoke correct function across codebases", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + const v1response = await test.invokeHttpFunction("onReq"); + expect(v1response.status).to.equal(200); + const v1body = await v1response.text(); + expect(v1body).to.deep.equal("onReq"); + + const v2response = await test.invokeHttpFunction("onreqv2a"); + expect(v2response.status).to.equal(200); + const v2body = await v2response.text(); + expect(v2body).to.deep.equal("onreqv2a"); + }); + + it("should disable timeout", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + const v2response = await test.invokeHttpFunction("onreqv2timeout"); + expect(v2response.status).to.equal(200); + const v2body = await v2response.text(); + expect(v2body).to.deep.equal("onreqv2timeout"); + }); }); - it("should disable timeout", async function (this) { - this.timeout(TEST_SETUP_TIMEOUT); - const v2response = await test.invokeHttpFunction("onreqv2timeout"); - expect(v2response.status).to.equal(200); - const v2body = await v2response.text(); - expect(v2body).to.deep.equal("onreqv2timeout"); + describe("event triggered (multicast) functions", () => { + it("should trigger auth triggered functions in response to auth events", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + const response = await test.writeToAuth(); + expect(response.status).to.equal(200); + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); + expect(test.authTriggerCount).to.equal(1); + }); + + it("should trigger storage triggered functions in response to storage events across codebases", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + const response = await test.writeToDefaultStorage(); + expect(response.status).to.equal(200); + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); + + expect(test.storageFinalizedTriggerCount).to.equal(1); + expect(test.storageV2FinalizedTriggerCount).to.equal(1); + }); }); }); diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 1896ef14bdf..fcf8a3f80c2 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -292,20 +292,18 @@ export class FunctionsEmulator implements EmulatorInstance { const { host, port } = this.getInfo(); triggers.forEach((triggerId) => { this.workQueue.submit(() => { - return new Promise((resolve, reject) => { - const trigReq = http.request( - { - host: connectableHostname(host), - port, - method: req.method, - path: `/functions/projects/${projectId}/triggers/${triggerId}`, - headers: req.headers, - }, - resolve - ); + return new Promise((resolve, reject) => { + const trigReq = http.request({ + host: connectableHostname(host), + port, + method: req.method, + path: `/functions/projects/${projectId}/triggers/${triggerId}`, + headers: req.headers, + }); trigReq.on("error", reject); trigReq.write(rawBody); trigReq.end(); + resolve(); }); }); }); From 577bd7eee65e08685c66bd28c05044e2b65b97f4 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Tue, 8 Nov 2022 15:57:32 -0800 Subject: [PATCH 0684/1699] Updating link to docs and massaging some text. (#5131) Co-authored-by: Bryan Kendall --- templates/init/functions/golang/functions.go | 2 +- templates/init/functions/javascript/index.js | 4 ++-- templates/init/functions/typescript/index.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/init/functions/golang/functions.go b/templates/init/functions/golang/functions.go index 9b44a27868b..b8795b7a4d3 100644 --- a/templates/init/functions/golang/functions.go +++ b/templates/init/functions/golang/functions.go @@ -1,7 +1,7 @@ package PACKAGE // Welcome to Cloud Functions for Firebase for Golang! -// To get started, simply uncomment the below code or create your own. +// To get started, uncomment the below code or create your own. // Deploy with `firebase deploy` /* diff --git a/templates/init/functions/javascript/index.js b/templates/init/functions/javascript/index.js index 081873b3f9b..0d3ca7f46ee 100644 --- a/templates/init/functions/javascript/index.js +++ b/templates/init/functions/javascript/index.js @@ -1,7 +1,7 @@ const functions = require("firebase-functions"); -// // Create and Deploy Your First Cloud Functions -// // https://firebase.google.com/docs/functions/write-firebase-functions +// // Create and deploy your first functions +// // https://firebase.google.com/docs/functions/get-started // // exports.helloWorld = functions.https.onRequest((request, response) => { // functions.logger.info("Hello logs!", {structuredData: true}); diff --git a/templates/init/functions/typescript/index.ts b/templates/init/functions/typescript/index.ts index 10c30843a62..079282359d2 100644 --- a/templates/init/functions/typescript/index.ts +++ b/templates/init/functions/typescript/index.ts @@ -1,6 +1,6 @@ import * as functions from "firebase-functions"; -// // Start writing Firebase Functions +// // Start writing functions // // https://firebase.google.com/docs/functions/typescript // // export const helloWorld = functions.https.onRequest((request, response) => { From be10d72656614f2dfdcea8a8d5f54decde45e4de Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Tue, 8 Nov 2022 17:27:33 -0800 Subject: [PATCH 0685/1699] Update pubsub version/md5/byte size. (#5205) * Update pubsub version/md5/byte size. * Update some download logic to pause for 2s to allow the unzip to complete before chmod. --- CHANGELOG.md | 2 ++ src/emulator/download.ts | 6 +++++- src/emulator/downloadableEmulators.ts | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ec1619b56d..e1424f12fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- Updated the pubsub emulator to v0.7.1. +- Updated some emulator download logic to pause after unzipping to avoid a file not found issue. - Fixes gzipped file handling in Storage Emulator. - Add support for object list using certain Admin SDKs (#5208) - Fixes source token expiration issue by acquiring new source token upon expiration. diff --git a/src/emulator/download.ts b/src/emulator/download.ts index 32398cdb2b3..0841482ce48 100644 --- a/src/emulator/download.ts +++ b/src/emulator/download.ts @@ -37,6 +37,10 @@ export async function downloadEmulator(name: DownloadableEmulators): Promise setTimeout(f, 2000)); + const executablePath = emulator.binaryPath || emulator.downloadPath; fs.chmodSync(executablePath, 0o755); @@ -79,7 +83,7 @@ function unzip(zipPath: string, unzipDir: string): Promise { fs.createReadStream(zipPath) .pipe(unzipper.Extract({ path: unzipDir })) // eslint-disable-line new-cap .on("error", reject) - .on("finish", resolve); + .on("close", resolve); }); } diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 95821f8b304..d307cc5567f 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -50,9 +50,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet expectedChecksum: "a4944414518be206280b495f526f18bf", }, pubsub: { - version: "0.1.0", - expectedSize: 36623622, - expectedChecksum: "81704b24737d4968734d3e175f4cde71", + version: "0.7.1", + expectedSize: 65137179, + expectedChecksum: "b59a6e705031a54a69e5e1dced7ca9bf", }, }; From dc44460d5a2d5f75e6ba013c484589856dfd359c Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Wed, 9 Nov 2022 00:15:39 -0800 Subject: [PATCH 0686/1699] Adds an omit parametrized configuration option for functions to skip deploy (#5117) --- src/deploy/functions/build.ts | 6 ++++ .../functions/runtimes/discovery/v1alpha1.ts | 3 ++ src/test/deploy/functions/build.spec.ts | 29 +++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index 40d884b3d76..0032602da6b 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -220,6 +220,9 @@ export const AllIngressSettings: IngressSetting[] = [ ]; export type Endpoint = Triggered & { + // Defaults to false. If true, the function will be ignored during the deploy process. + omit?: Field; + // Defaults to "gcfv2". "Run" will be an additional option defined later platform?: "gcfv1" | "gcfv2"; @@ -413,6 +416,9 @@ export function toBackend( const bkEndpoints: Array = []; for (const endpointId of Object.keys(build.endpoints)) { const bdEndpoint = build.endpoints[endpointId]; + if (r.resolveBoolean(bdEndpoint.omit || false)) { + continue; + } let regions = bdEndpoint.region; if (typeof regions === "undefined") { diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index be232b4cf06..5b2369c76a4 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -44,6 +44,7 @@ export type WireEndpoint = build.Triggered & Partial & Partial & Partial<{ scheduleTrigger: WireScheduleTrigger }> & { + omit?: build.Field; labels?: Record | null; environmentVariables?: Record | null; availableMemoryMb?: build.MemoryOption | build.Expression | null; @@ -124,6 +125,7 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { region: "array", platform: (platform) => build.AllFunctionsPlatforms.includes(platform), entryPoint: "string", + omit: "Field?", availableMemoryMb: (mem) => mem === null || isCEL(mem) || build.isValidMemoryOption(mem), maxInstances: "Field?", minInstances: "Field?", @@ -395,6 +397,7 @@ function parseEndpointForBuild( copyIfPresent( parsed, ep, + "omit", "availableMemoryMb", "cpu", "maxInstances", diff --git a/src/test/deploy/functions/build.spec.ts b/src/test/deploy/functions/build.spec.ts index 21cf8b7e350..873c9a2a3b7 100644 --- a/src/test/deploy/functions/build.spec.ts +++ b/src/test/deploy/functions/build.spec.ts @@ -44,6 +44,35 @@ describe("toBackend", () => { } }); + it("doesn't populate if omit is set on the build", () => { + const desiredBuild: build.Build = build.of({ + func: { + omit: true, + platform: "gcfv1", + region: ["us-central1"], + project: "project", + runtime: "nodejs16", + entryPoint: "func", + maxInstances: 42, + minInstances: 1, + serviceAccount: "service-account-1@", + vpc: { + connector: "projects/project/locations/region/connectors/connector", + egressSettings: "PRIVATE_RANGES_ONLY", + }, + ingressSettings: "ALLOW_ALL", + labels: { + test: "testing", + }, + httpsTrigger: { + invoker: ["public"], + }, + }, + }); + const backend = build.toBackend(desiredBuild, {}); + expect(Object.keys(backend.endpoints).length).to.equal(0); + }); + it("populates multiple specified invokers correctly", () => { const desiredBuild: build.Build = build.of({ func: { From 95799e1db587371ba800b2578d51358c1657e898 Mon Sep 17 00:00:00 2001 From: Yuangwang Date: Thu, 10 Nov 2022 12:32:57 -0500 Subject: [PATCH 0687/1699] Fix storage admin sdk content type (#5229) * Fix storage admin sdk content type * lint * lint * rename variables * lint --- .../conformance/gcs-js-sdk.test.ts | 7 ++++ .../conformance/gcs.endpoints.test.ts | 37 +++++++++++++++++++ src/emulator/storage/apis/firebase.ts | 4 +- src/emulator/storage/apis/gcloud.ts | 7 ++-- src/emulator/storage/upload.ts | 8 ++-- src/test/emulators/storage/files.spec.ts | 6 +-- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts b/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts index af10db91693..18703e6b775 100644 --- a/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts +++ b/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts @@ -123,6 +123,13 @@ describe("GCS Javascript SDK conformance tests", () => { expect(fileMetadata).to.deep.include(metadata); }); + it("should upload with proper content type", async () => { + const jpgFile = createRandomFile("small_file.jpg", SMALL_FILE_SIZE, tmpDir); + const [, fileMetadata] = await testBucket.upload(jpgFile); + + expect(fileMetadata.contentType).to.equal("image/jpeg"); + }); + it("should handle firebaseStorageDownloadTokens", async () => { const testFileName = "public/file"; await testBucket.upload(smallFilePath, { diff --git a/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts b/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts index 1f772f5248a..11e57fd3d16 100644 --- a/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts +++ b/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts @@ -251,6 +251,26 @@ describe("GCS endpoint conformance tests", () => { expect(returnedMetadata.contentType).to.equal(customMetadata.contentType); expect(returnedMetadata.contentDisposition).to.equal(customMetadata.contentDisposition); }); + + it("should upload content type properly from x-upload-content-type headers", async () => { + const uploadURL = await supertest(storageHost) + .post( + `/upload/storage/v1/b/${storageBucket}/o?name=${TEST_FILE_NAME}&uploadType=resumable` + ) + .set(authHeader) + .set({ + "x-upload-content-type": "image/png", + }) + .expect(200) + .then((res) => new URL(res.header["location"])); + + const returnedMetadata = await supertest(storageHost) + .put(uploadURL.pathname + uploadURL.search) + .expect(200) + .then((res) => res.body); + + expect(returnedMetadata.contentType).to.equal("image/png"); + }); }); describe("multipart upload", () => { @@ -292,6 +312,23 @@ describe("GCS endpoint conformance tests", () => { expect(res.text).to.include("Bad content type."); }); + + it("should upload content type properly from x-upload headers", async () => { + const returnedMetadata = await supertest(storageHost) + .post(`/upload/storage/v1/b/${storageBucket}/o?uploadType=multipart`) + .set(authHeader) + .set({ + "content-type": "multipart/related; boundary=b1d5b2e3-1845-4338-9400-6ac07ce53c1e", + }) + .set({ + "x-upload-content-type": "text/plain", + }) + .send(MULTIPART_REQUEST_BODY) + .expect(200) + .then((res) => res.body); + + expect(returnedMetadata.contentType).to.equal("text/plain"); + }); }); }); diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 5a0c88369de..762a28bd3bd 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -232,7 +232,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { const upload = uploadService.startResumableUpload({ bucketId, objectId, - metadataRaw: JSON.stringify(req.body), + metadata: req.body, // Store auth header for use in the finalize request authorization: req.header("authorization"), }); @@ -355,7 +355,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { const upload = uploadService.multipartUpload({ bucketId, objectId, - metadataRaw, + metadata: JSON.parse(metadataRaw), dataRaw: dataRaw, authorization: req.header("authorization"), }); diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index 1efc031052c..a51070e35b2 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -261,10 +261,11 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { res.sendStatus(400); return; } + const contentType = req.header("x-upload-content-type"); const upload = uploadService.startResumableUpload({ bucketId: req.params.bucketId, objectId: name, - metadataRaw: JSON.stringify(req.body), + metadata: { contentType, ...req.body }, authorization: req.header("authorization"), }); @@ -292,6 +293,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { // Multipart upload protocol. if (uploadType === "multipart") { const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type"); + const contentType = req.header("x-upload-content-type"); if (!contentTypeHeader) { return res.sendStatus(400); } @@ -319,11 +321,10 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { res.sendStatus(400); return; } - const upload = uploadService.multipartUpload({ bucketId: req.params.bucketId, objectId: name, - metadataRaw: metadataRaw, + metadata: { contentType, ...JSON.parse(metadataRaw) }, dataRaw: dataRaw, authorization: req.header("authorization"), }); diff --git a/src/emulator/storage/upload.ts b/src/emulator/storage/upload.ts index d60e95e16d8..5d0b0dd7a81 100644 --- a/src/emulator/storage/upload.ts +++ b/src/emulator/storage/upload.ts @@ -44,7 +44,7 @@ export type MediaUploadRequest = { export type MultipartUploadRequest = { bucketId: string; objectId: string; - metadataRaw: string; + metadata: object; dataRaw: Buffer; authorization?: string; }; @@ -53,7 +53,7 @@ export type MultipartUploadRequest = { export type StartResumableUploadRequest = { bucketId: string; objectId: string; - metadataRaw: string; + metadata: object; authorization?: string; }; @@ -117,7 +117,7 @@ export class UploadService { objectId: request.objectId, uploadType: UploadType.MULTIPART, dataRaw: request.dataRaw, - metadata: JSON.parse(request.metadataRaw), + metadata: request.metadata, authorization: request.authorization, }); this._persistence.deleteFile(upload.path, /* failSilently = */ true); @@ -155,7 +155,7 @@ export class UploadService { type: UploadType.RESUMABLE, path: this.getStagingFileName(id, request.bucketId, request.objectId), status: UploadStatus.ACTIVE, - metadata: JSON.parse(request.metadataRaw), + metadata: request.metadata, size: 0, authorization: request.authorization, }; diff --git a/src/test/emulators/storage/files.spec.ts b/src/test/emulators/storage/files.spec.ts index 2140a226946..3fead95d173 100644 --- a/src/test/emulators/storage/files.spec.ts +++ b/src/test/emulators/storage/files.spec.ts @@ -83,7 +83,7 @@ describe("files", () => { bucketId, objectId: encodeURIComponent(objectId), dataRaw: Buffer.from(opts?.data ?? "hello world"), - metadataRaw: JSON.stringify(opts?.metadata ?? {}), + metadata: opts?.metadata ?? {}, }); await storageLayer.uploadObject(upload); } @@ -99,7 +99,7 @@ describe("files", () => { const upload = _uploadService.startResumableUpload({ bucketId: "bucket", objectId: "dir%2Fobject", - metadataRaw: "{}", + metadata: {}, }); expect(storageLayer.uploadObject(upload)).to.be.rejectedWith("Unexpected upload status"); @@ -110,7 +110,7 @@ describe("files", () => { const uploadId = _uploadService.startResumableUpload({ bucketId: "bucket", objectId: "dir%2Fobject", - metadataRaw: "{}", + metadata: {}, }).id; _uploadService.continueResumableUpload(uploadId, Buffer.from("hello world")); const upload = _uploadService.finalizeResumableUpload(uploadId); From d6871aabb9760e25d5936d5db668b529902d213c Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 15 Nov 2022 13:04:17 -0800 Subject: [PATCH 0688/1699] Fixes a bug where extensions emulator was not appearing in the registry (#5242) * Fixes a bug where extensions emualtor was not appearing in the registry * changelog --- CHANGELOG.md | 3 ++- src/emulator/extensionsEmulator.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1424f12fa2..3d7e779fa0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,4 +3,5 @@ - Fixes gzipped file handling in Storage Emulator. - Add support for object list using certain Admin SDKs (#5208) - Fixes source token expiration issue by acquiring new source token upon expiration. -- Fix bug where emulated event triggered function broke in debug mode (#5211) +- Fixes bug where emulated event triggered function broke in debug mode (#5211) +- Fixes bug that caused the Extensions Emulator to always appear to be inactive in the Emulator UI. diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 3d69232be73..04233bfe57d 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -67,7 +67,7 @@ export class ExtensionsEmulator implements EmulatorInstance { "Extensions Emulator is running but Functions emulator is not. This should never happen." ); } - return functionsEmulator.getInfo(); + return { ...functionsEmulator.getInfo(), name: this.getName() }; } public getName(): Emulators { From a5bfe2481c393a4df441d1eff1fda162659f5325 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 15 Nov 2022 22:52:52 +0000 Subject: [PATCH 0689/1699] 11.16.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index db6a738cd42..054c7074274 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.16.0", + "version": "11.16.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.16.0", + "version": "11.16.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 3ccb1fc8118..274da8d6943 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.16.0", + "version": "11.16.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 392d8a8565eeb1f7018170148baee15ad169359a Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 15 Nov 2022 22:53:03 +0000 Subject: [PATCH 0690/1699] [firebase-release] Removed change log and reset repo after 11.16.1 release --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7e779fa0c..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +0,0 @@ -- Updated the pubsub emulator to v0.7.1. -- Updated some emulator download logic to pause after unzipping to avoid a file not found issue. -- Fixes gzipped file handling in Storage Emulator. -- Add support for object list using certain Admin SDKs (#5208) -- Fixes source token expiration issue by acquiring new source token upon expiration. -- Fixes bug where emulated event triggered function broke in debug mode (#5211) -- Fixes bug that caused the Extensions Emulator to always appear to be inactive in the Emulator UI. From 2354100aca96eb927dd931a5afbe4e0a380a728e Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 16 Nov 2022 12:46:45 -0800 Subject: [PATCH 0691/1699] Fix bug where disabling background triggers did nothing. (#5221) Fixes https://github.com/firebase/firebase-tools/issues/5026 --- CHANGELOG.md | 1 + scripts/integration-helpers/framework.ts | 13 +++++ scripts/triggers-end-to-end-tests/tests.ts | 62 ++++++++++++++++++++++ src/emulator/functionsEmulator.ts | 5 ++ 4 files changed, 81 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..66abef19ff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fix bug where disabling background triggers did nothing. (#5221) diff --git a/scripts/integration-helpers/framework.ts b/scripts/integration-helpers/framework.ts index a5aeb59df3f..e0a90602944 100644 --- a/scripts/integration-helpers/framework.ts +++ b/scripts/integration-helpers/framework.ts @@ -53,6 +53,7 @@ interface ConnectionInfo { export interface FrameworkOptions { emulators?: { + hub: ConnectionInfo; database: ConnectionInfo; firestore: ConnectionInfo; functions: ConnectionInfo; @@ -63,6 +64,7 @@ export interface FrameworkOptions { } export class EmulatorEndToEndTest { + emulatorHubPort = 0; rtdbEmulatorHost = "localhost"; rtdbEmulatorPort = 0; firestoreEmulatorHost = "localhost"; @@ -87,6 +89,7 @@ export class EmulatorEndToEndTest { if (!config.emulators) { return; } + this.emulatorHubPort = config.emulators.hub?.port; this.rtdbEmulatorPort = config.emulators.database?.port; this.firestoreEmulatorPort = config.emulators.firestore?.port; this.functionsEmulatorPort = config.emulators.functions?.port; @@ -409,4 +412,14 @@ export class TriggerEndToEndTest extends EmulatorEndToEndTest { } }, interval); } + + disableBackgroundTriggers(): Promise { + const url = `http://localhost:${this.emulatorHubPort}/functions/disableBackgroundTriggers`; + return fetch(url, { method: "PUT" }); + } + + enableBackgroundTriggers(): Promise { + const url = `http://localhost:${this.emulatorHubPort}/functions/enableBackgroundTriggers`; + return fetch(url, { method: "PUT" }); + } } diff --git a/scripts/triggers-end-to-end-tests/tests.ts b/scripts/triggers-end-to-end-tests/tests.ts index a0dc6928a12..fc0ee330e21 100755 --- a/scripts/triggers-end-to-end-tests/tests.ts +++ b/scripts/triggers-end-to-end-tests/tests.ts @@ -429,4 +429,66 @@ describe("function triggers", () => { const v2response = await test.invokeHttpFunction("onreqv2timeout"); expect(v2response.status).to.equal(500); }); + + describe("disable/enableBackgroundTriggers", () => { + before(() => { + test.resetCounts(); + }); + + it("should disable all background triggers", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + const response = await test.disableBackgroundTriggers(); + expect(response.status).to.equal(200); + + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); + + await Promise.all([ + test.writeToRtdb(), + test.writeToFirestore(), + test.writeToPubsub(), + test.writeToAuth(), + test.writeToDefaultStorage(), + ]); + + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS * 2)); + + expect(test.rtdbTriggerCount).to.equal(0); + expect(test.rtdbV2TriggerCount).to.eq(0); + expect(test.firestoreTriggerCount).to.equal(0); + expect(test.pubsubTriggerCount).to.equal(0); + expect(test.pubsubV2TriggerCount).to.equal(0); + expect(test.authTriggerCount).to.equal(0); + expect(test.storageFinalizedTriggerCount).to.equal(0); + expect(test.storageV2FinalizedTriggerCount).to.equal(0); + }); + + it("should re-enable all background triggers", async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + + const response = await test.enableBackgroundTriggers(); + expect(response.status).to.equal(200); + + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); + + await Promise.all([ + test.writeToRtdb(), + test.writeToFirestore(), + test.writeToPubsub(), + test.writeToAuth(), + test.writeToDefaultStorage(), + ]); + + await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS * 3)); + + expect(test.rtdbTriggerCount).to.equal(1); + expect(test.rtdbV2TriggerCount).to.eq(1); + expect(test.firestoreTriggerCount).to.equal(1); + expect(test.pubsubTriggerCount).to.equal(1); + expect(test.pubsubV2TriggerCount).to.equal(1); + expect(test.authTriggerCount).to.equal(1); + expect(test.storageFinalizedTriggerCount).to.equal(1); + expect(test.storageV2FinalizedTriggerCount).to.equal(1); + }); + }); }); diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index fcf8a3f80c2..fb47df888fd 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -1395,6 +1395,11 @@ export class FunctionsEmulator implements EmulatorInstance { } const record = this.getTriggerRecordByKey(triggerId); + // If trigger is disabled, exit early + if (!record.enabled) { + res.status(204).send("Background triggers are currently disabled."); + return; + } const trigger = record.def; logger.debug(`Accepted request ${method} ${req.url} --> ${triggerId}`); From a71d911c0d62b47296bf6f296c32397f3aa606f8 Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Thu, 17 Nov 2022 15:49:01 -0800 Subject: [PATCH 0692/1699] Change signInWithPassword error handling for empty string emails (#5258) --- CHANGELOG.md | 1 + src/emulator/auth/operations.ts | 3 ++- src/test/emulators/auth/password.spec.ts | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66abef19ff3..553b7ea3314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Fix bug where disabling background triggers did nothing. (#5221) +- Fix bug in auth emulator where empty string should throw invalid email instead of missing email. (#3898) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index d72d6faca3d..783754e42f2 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -1708,7 +1708,8 @@ async function signInWithPassword( ): Promise { assert(!state.disableAuth, "PROJECT_DISABLED"); assert(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED"); - assert(reqBody.email, "MISSING_EMAIL"); + assert(reqBody.email !== undefined, "MISSING_EMAIL"); + assert(isValidEmailAddress(reqBody.email), "INVALID_EMAIL"); assert(reqBody.password, "MISSING_PASSWORD"); if (reqBody.captchaResponse || reqBody.captchaChallenge) { throw new NotImplementedError("captcha unimplemented"); diff --git a/src/test/emulators/auth/password.spec.ts b/src/test/emulators/auth/password.spec.ts index 064ca3c8541..29314547fc8 100644 --- a/src/test/emulators/auth/password.spec.ts +++ b/src/test/emulators/auth/password.spec.ts @@ -101,6 +101,25 @@ describeAuthEmulator("accounts:signInWithPassword", ({ authApi, getClock }) => { }); }); + it("should error if email is invalid", async () => { + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword") + .query({ key: "fake-api-key" }) + .send({ email: "ill-formatted-email", password: "notasecret" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).equals("INVALID_EMAIL"); + }); + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword") + .query({ key: "fake-api-key" }) + .send({ email: "", password: "notasecret" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).equals("INVALID_EMAIL"); + }); + }); + it("should error if email is not found", async () => { await authApi() .post("/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword") From b2d23b685a12a9f98deb125940f4dc419fb5ab5f Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Mon, 21 Nov 2022 10:35:46 -0800 Subject: [PATCH 0693/1699] Show prereleases when resolving Extension versions. (#5161) --- src/deploy/extensions/planner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 6559f3eb782..c48d04224e7 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -209,7 +209,7 @@ export async function want(args: { */ export async function resolveVersion(ref: refs.Ref): Promise { const extensionRef = refs.toExtensionRef(ref); - const versions = await extensionsApi.listExtensionVersions(extensionRef); + const versions = await extensionsApi.listExtensionVersions(extensionRef, undefined, true); if (versions.length === 0) { throw new FirebaseError(`No versions found for ${extensionRef}`); } From e984a6c3a82f43eec505b9a923a8d8be93b7aae7 Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Mon, 21 Nov 2022 12:46:56 -0800 Subject: [PATCH 0694/1699] Add createdAt time for signInWithIdp new users (#5260) --- CHANGELOG.md | 1 + src/emulator/auth/operations.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 553b7ea3314..aa5c30b100a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Fix bug where disabling background triggers did nothing. (#5221) - Fix bug in auth emulator where empty string should throw invalid email instead of missing email. (#3898) +- Fix bug in auth emulator in which createdAt was not set for signInWithIdp new users. (#5203) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index 783754e42f2..f40c1beea10 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -1608,9 +1608,11 @@ async function signInWithIdp( oauthExpiresIn: coercePrimitiveToString(response.oauthExpireIn), }; if (response.isNewUser) { + const timestamp = new Date(); let updates: Partial = { ...accountUpdates.fields, - lastLoginAt: Date.now().toString(), + createdAt: timestamp.getTime().toString(), + lastLoginAt: timestamp.getTime().toString(), providerUserInfo: [providerUserInfo], tenantId: state instanceof TenantProjectState ? state.tenantId : undefined, }; From e968d50f2b0f138bc5fbded19dd8a44a32a7caf6 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 1 Dec 2022 09:05:57 -0800 Subject: [PATCH 0695/1699] Temporarily disable storage emulator test. (#5281) --- .github/workflows/node-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 5e72bc66f0f..f276fe2381f 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -89,7 +89,8 @@ jobs: # - npm run test:hosting-rewrites # Long-running test that might conflict across test runs. Run this manually. - npm run test:import-export - npm run test:storage-deploy - - npm run test:storage-emulator-integration + # Temporarily disable broken storage emulator integration test. + # - npm run test:storage-emulator-integration - npm run test:triggers-end-to-end - npm run test:triggers-end-to-end:inspect steps: From 76450efc702f60b3f7a23b9dd8a7bb92c3315310 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Thu, 1 Dec 2022 09:25:51 -0800 Subject: [PATCH 0696/1699] Support x-goog-api-key in Auth Emulator. Fix #5249. (#5263) * Update Auth Emulator API spec. * Add x-goog-api-key in API spec. * Support x-goog-api-key in Auth Emulator. Fix #5249. --- CHANGELOG.md | 1 + scripts/gen-auth-api-spec.ts | 17 +- src/emulator/auth/apiSpec.ts | 766 ++++++++++++++++++++------- src/emulator/auth/schema.ts | 93 +++- src/emulator/auth/server.ts | 28 +- src/test/emulators/auth/rest.spec.ts | 28 +- 6 files changed, 708 insertions(+), 225 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa5c30b100a..33ff5550bca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Fix bug where disabling background triggers did nothing. (#5221) - Fix bug in auth emulator where empty string should throw invalid email instead of missing email. (#3898) - Fix bug in auth emulator in which createdAt was not set for signInWithIdp new users. (#5203) +- Support the x-goog-api-key header in auth emulator. (#5249) diff --git a/scripts/gen-auth-api-spec.ts b/scripts/gen-auth-api-spec.ts index b64d11d8656..d5d9cc54509 100644 --- a/scripts/gen-auth-api-spec.ts +++ b/scripts/gen-auth-api-spec.ts @@ -252,13 +252,20 @@ function patchSecurity(openapi3: any, apiKeyDescription: string): void { securitySchemes = openapi3.components.securitySchemes = {}; } - // Add the missing apiKey method here. - securitySchemes.apiKey = { + // Add the missing apiKeyQuery and apiKeyHeader schemes here. + // https://cloud.google.com/docs/authentication/api-keys#using-with-rest + securitySchemes.apiKeyQuery = { type: "apiKey", name: "key", in: "query", description: apiKeyDescription, }; + securitySchemes.apiKeyHeader = { + type: "apiKey", + name: "x-goog-api-key", + in: "header", + description: apiKeyDescription, + }; forEachOperation(openapi3, (operation) => { if (!operation.security) { @@ -271,9 +278,9 @@ function patchSecurity(openapi3: any, apiKeyDescription: string): void { delete alt.Oauth2c; }); - // Forcibly add API Key as an alternative auth method. Note that some - // operations may not support it, but those can be handled within impl. - operation.security.push({ apiKey: [] }); + // Add alternative auth schemes (query OR header) for API key. Note that + // some operations may not support it, but those can be handled within impl. + operation.security.push({ apiKeyQuery: [] }, { apiKeyHeader: [] }); }); } diff --git a/src/emulator/auth/apiSpec.ts b/src/emulator/auth/apiSpec.ts index 310a071cd21..125afaa542d 100644 --- a/src/emulator/auth/apiSpec.ts +++ b/src/emulator/auth/apiSpec.ts @@ -17,7 +17,7 @@ export default { termsOfService: "https://developers.google.com/terms/", }, servers: [{ url: "https://identitytoolkit.googleapis.com" }], - externalDocs: { url: "https://firebase.google.com/docs/auth/" }, + externalDocs: { url: "https://cloud.google.com/identity-platform" }, tags: [ { name: "accounts" }, { name: "projects" }, @@ -33,7 +33,7 @@ export default { "If an email identifier is specified, checks and returns if any user account is registered with the email. If there is a registered account, fetches all providers associated with the account's email. If the provider ID of an Identity Provider (IdP) is specified, creates an authorization URI for the IdP. The user can be directed to this URI to sign in with the IdP. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.createAuthUri", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -53,7 +53,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -73,7 +77,7 @@ export default { description: "Deletes a user's account.", operationId: "identitytoolkit.accounts.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -87,7 +91,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1DeleteAccountRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -107,7 +115,7 @@ export default { description: "Experimental", operationId: "identitytoolkit.accounts.issueSamlResponse", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -127,7 +135,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -148,7 +160,7 @@ export default { "Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria.", operationId: "identitytoolkit.accounts.lookup", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -162,7 +174,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1GetAccountInfoRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -183,7 +199,7 @@ export default { "Resets the password of an account either using an out-of-band code generated by sendOobCode or by specifying the email and password of the account to be modified. Can also check the purpose of an out-of-band code without consuming it.", operationId: "identitytoolkit.accounts.resetPassword", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -203,7 +219,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -224,7 +244,7 @@ export default { "Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it.", operationId: "identitytoolkit.accounts.sendOobCode", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -238,7 +258,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1GetOobCodeRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -259,7 +283,7 @@ export default { "Sends a SMS verification code for phone number sign-in. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.sendVerificationCode", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -279,7 +303,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -300,7 +328,7 @@ export default { "Signs in or signs up a user by exchanging a custom Auth token. Upon a successful sign-in or sign-up, a new Identity Platform ID token and refresh token are issued for the user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.signInWithCustomToken", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -320,7 +348,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -341,7 +373,7 @@ export default { "Signs in or signs up a user with a out-of-band code from an email link. If a user does not exist with the given email address, a user record will be created. If the sign-in succeeds, an Identity Platform ID and refresh token are issued for the authenticated user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.signInWithEmailLink", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -361,7 +393,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -382,7 +418,7 @@ export default { "Signs in or signs up a user with iOS Game Center credentials. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. The bundle ID is required in the request header as `x-ios-bundle-identifier`. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.signInWithGameCenter", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -402,7 +438,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -423,7 +463,7 @@ export default { 'Signs in or signs up a user using credentials from an Identity Provider (IdP). This is done by manually providing an IdP credential, or by providing the authorization response obtained via the authorization request from CreateAuthUri. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. A new Identity Platform user account will be created if the user has not previously signed in to the IdP with the same account. In addition, when the "One account per email address" setting is enabled, there should not be an existing Identity Platform user account with the same email address for a new user account to be created. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.', operationId: "identitytoolkit.accounts.signInWithIdp", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -443,7 +483,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -464,7 +508,7 @@ export default { "Signs in a user with email and password. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.signInWithPassword", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -484,7 +528,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -505,7 +553,7 @@ export default { "Completes a phone number authentication attempt. If a user already exists with the given phone number, an ID token is minted for that user. Otherwise, a new user is created and associated with the phone number. This method may also be used to link a phone number to an existing user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.signInWithPhoneNumber", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -525,7 +573,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -546,7 +598,7 @@ export default { "Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.signUp", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -558,7 +610,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1SignUpRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -579,7 +635,7 @@ export default { "Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported.", operationId: "identitytoolkit.accounts.update", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -593,7 +649,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1SetAccountInfoRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -611,10 +671,10 @@ export default { "/v1/accounts:verifyIosClient": { post: { description: - "Verifies an iOS client is a real iOS device. If the request is valid, a reciept will be sent in the response and a secret will be sent via Apple Push Notification Service. The client should send both of them back to certain Identity Platform APIs in a later call (for example, /accounts:sendVerificationCode), in order to verify the client. The bundle ID is required in the request header as `x-ios-bundle-identifier`. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", + "Verifies an iOS client is a real iOS device. If the request is valid, a receipt will be sent in the response and a secret will be sent via Apple Push Notification Service. The client should send both of them back to certain Identity Platform APIs in a later call (for example, /accounts:sendVerificationCode), in order to verify the client. The bundle ID is required in the request header as `x-ios-bundle-identifier`. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.accounts.verifyIosClient", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -634,7 +694,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -655,7 +719,7 @@ export default { "Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.projects.accounts", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -677,7 +741,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1SignUpRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -698,7 +766,7 @@ export default { "Creates a session cookie for the given Identity Platform ID token. The session cookie is used by the client to preserve the user's login state.", operationId: "identitytoolkit.projects.createSessionCookie", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -721,7 +789,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1CreateSessionCookieRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -742,7 +814,7 @@ export default { "Looks up user accounts within a project or a tenant based on conditions in the request.", operationId: "identitytoolkit.projects.queryAccounts", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -768,7 +840,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -790,7 +863,7 @@ export default { "Uploads multiple accounts into the Google Cloud project. If there is a problem uploading one or more of the accounts, the rest will be uploaded, and a list of the errors will be returned. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control).", operationId: "identitytoolkit.projects.accounts.batchCreate", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -817,7 +890,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -836,10 +910,10 @@ export default { "/v1/projects/{targetProjectId}/accounts:batchDelete": { post: { description: - "Batch deletes multiple accounts. For accounts that fail to be deleted, error info is contained in the response. The method ignores accounts that do not exist or are duplicated in the request. This method requires a Google OAuth 2.0 credential with proper permissions. (https://cloud.google.com/identity-platform/docs/access-control)", + "Batch deletes multiple accounts. For accounts that fail to be deleted, error info is contained in the response. The method ignores accounts that do not exist or are duplicated in the request. This method requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control).", operationId: "identitytoolkit.projects.accounts.batchDelete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -866,7 +940,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -888,7 +963,7 @@ export default { "Download account information for all accounts on the project in a paginated manner. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control).. Furthermore, additional permissions are needed to get password hash, password salt, and password version from accounts; otherwise these fields are redacted.", operationId: "identitytoolkit.projects.accounts.batchGet", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -933,7 +1008,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -954,7 +1030,7 @@ export default { description: "Deletes a user's account.", operationId: "identitytoolkit.projects.accounts.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -978,7 +1054,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1DeleteAccountRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -999,7 +1079,7 @@ export default { "Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria.", operationId: "identitytoolkit.projects.accounts.lookup", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1023,7 +1103,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1GetAccountInfoRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1044,7 +1128,7 @@ export default { "Looks up user accounts within a project or a tenant based on conditions in the request.", operationId: "identitytoolkit.projects.accounts.query", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1070,7 +1154,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -1092,7 +1177,7 @@ export default { "Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it.", operationId: "identitytoolkit.projects.accounts.sendOobCode", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1116,7 +1201,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1GetOobCodeRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1137,7 +1226,7 @@ export default { "Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported.", operationId: "identitytoolkit.projects.accounts.update", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1153,7 +1242,7 @@ export default { name: "targetProjectId", in: "path", description: - "The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead.", + "The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead.", required: true, schema: { type: "string" }, }, @@ -1161,7 +1250,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1SetAccountInfoRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1182,7 +1275,7 @@ export default { "Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project.", operationId: "identitytoolkit.projects.tenants.accounts", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1212,7 +1305,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1SignUpRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1233,7 +1330,7 @@ export default { "Creates a session cookie for the given Identity Platform ID token. The session cookie is used by the client to preserve the user's login state.", operationId: "identitytoolkit.projects.tenants.createSessionCookie", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1263,7 +1360,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1CreateSessionCookieRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1284,7 +1385,7 @@ export default { "Uploads multiple accounts into the Google Cloud project. If there is a problem uploading one or more of the accounts, the rest will be uploaded, and a list of the errors will be returned. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control).", operationId: "identitytoolkit.projects.tenants.accounts.batchCreate", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1318,7 +1419,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -1337,10 +1439,10 @@ export default { "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:batchDelete": { post: { description: - "Batch deletes multiple accounts. For accounts that fail to be deleted, error info is contained in the response. The method ignores accounts that do not exist or are duplicated in the request. This method requires a Google OAuth 2.0 credential with proper permissions. (https://cloud.google.com/identity-platform/docs/access-control)", + "Batch deletes multiple accounts. For accounts that fail to be deleted, error info is contained in the response. The method ignores accounts that do not exist or are duplicated in the request. This method requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control).", operationId: "identitytoolkit.projects.tenants.accounts.batchDelete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1375,7 +1477,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -1397,7 +1500,7 @@ export default { "Download account information for all accounts on the project in a paginated manner. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control).. Furthermore, additional permissions are needed to get password hash, password salt, and password version from accounts; otherwise these fields are redacted.", operationId: "identitytoolkit.projects.tenants.accounts.batchGet", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1443,7 +1546,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -1464,7 +1568,7 @@ export default { description: "Deletes a user's account.", operationId: "identitytoolkit.projects.tenants.accounts.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1496,7 +1600,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1DeleteAccountRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1517,7 +1625,7 @@ export default { "Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria.", operationId: "identitytoolkit.projects.tenants.accounts.lookup", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1549,7 +1657,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1GetAccountInfoRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1570,7 +1682,7 @@ export default { "Looks up user accounts within a project or a tenant based on conditions in the request.", operationId: "identitytoolkit.projects.tenants.accounts.query", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1603,7 +1715,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -1625,7 +1738,7 @@ export default { "Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it.", operationId: "identitytoolkit.projects.tenants.accounts.sendOobCode", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1656,7 +1769,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1GetOobCodeRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1677,7 +1794,7 @@ export default { "Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported.", operationId: "identitytoolkit.projects.tenants.accounts.update", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1693,7 +1810,7 @@ export default { name: "targetProjectId", in: "path", description: - "The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead.", + "The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead.", required: true, schema: { type: "string" }, }, @@ -1709,7 +1826,11 @@ export default { requestBody: { $ref: "#/components/requestBodies/GoogleCloudIdentitytoolkitV1SetAccountInfoRequest", }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, parameters: [ @@ -1730,7 +1851,7 @@ export default { "Gets a project's public Identity Toolkit configuration. (Legacy) This method also supports authenticated calls from a developer to retrieve non-public configuration.", operationId: "identitytoolkit.getProjects", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1798,7 +1919,11 @@ export default { schema: { type: "string" }, }, ], - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["v1"], }, parameters: [ @@ -1818,7 +1943,7 @@ export default { description: "Gets parameters needed for generating a reCAPTCHA challenge.", operationId: "identitytoolkit.getRecaptchaParams", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1829,7 +1954,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["v1"], }, parameters: [ @@ -1850,7 +1979,7 @@ export default { "Retrieves the set of public keys of the session cookie JSON Web Token (JWT) signer that can be used to validate the session cookie created through createSessionCookie.", operationId: "identitytoolkit.getSessionCookiePublicKeys", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1862,7 +1991,7 @@ export default { }, }, tags: ["v1"], - security: [{ apiKey: [] }], + security: [{ apiKeyQuery: [] }, { apiKeyHeader: [] }], }, parameters: [ { $ref: "#/components/parameters/access_token" }, @@ -1881,7 +2010,7 @@ export default { description: "Finishes enrolling a second factor for the user.", operationId: "identitytoolkit.accounts.mfaEnrollment.finalize", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1901,7 +2030,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -1922,7 +2055,7 @@ export default { "Step one of the MFA enrollment process. In SMS case, this sends an SMS verification code to the user.", operationId: "identitytoolkit.accounts.mfaEnrollment.start", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1942,7 +2075,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -1962,7 +2099,7 @@ export default { description: "Revokes one second factor from the enrolled second factors for an account.", operationId: "identitytoolkit.accounts.mfaEnrollment.withdraw", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -1982,7 +2119,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -2002,7 +2143,7 @@ export default { description: "Verifies the MFA challenge and performs sign-in", operationId: "identitytoolkit.accounts.mfaSignIn.finalize", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2022,7 +2163,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -2042,7 +2187,7 @@ export default { description: "Sends the MFA challenge", operationId: "identitytoolkit.accounts.mfaSignIn.start", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2062,7 +2207,11 @@ export default { }, }, }, - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["accounts"], }, parameters: [ @@ -2082,7 +2231,7 @@ export default { description: "List all default supported Idps.", operationId: "identitytoolkit.defaultSupportedIdps.list", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2110,7 +2259,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["defaultSupportedIdps"], }, @@ -2131,7 +2281,7 @@ export default { description: "Retrieve an Identity Toolkit project configuration.", operationId: "identitytoolkit.projects.getConfig", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2143,14 +2293,18 @@ export default { parameters: [ { name: "targetProjectId", in: "path", required: true, schema: { type: "string" } }, ], - security: [{ Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { apiKey: [] }], + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], tags: ["projects"], }, patch: { description: "Update an Identity Toolkit project configuration.", operationId: "identitytoolkit.projects.updateConfig", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2179,7 +2333,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2201,7 +2356,7 @@ export default { "Create a default supported Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.defaultSupportedIdpConfigs.create", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2228,7 +2383,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2237,7 +2393,7 @@ export default { "List all default supported Idp configurations for an Identity Toolkit project.", operationId: "identitytoolkit.projects.defaultSupportedIdpConfigs.list", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2266,7 +2422,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2288,7 +2445,7 @@ export default { "Delete a default supported Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.defaultSupportedIdpConfigs.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, }, @@ -2305,7 +2462,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2314,7 +2472,7 @@ export default { "Retrieve a default supported Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.defaultSupportedIdpConfigs.get", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2337,7 +2495,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2346,7 +2505,7 @@ export default { "Update a default supported Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.defaultSupportedIdpConfigs.patch", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2379,7 +2538,56 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, + ], + tags: ["projects"], + }, + parameters: [ + { $ref: "#/components/parameters/access_token" }, + { $ref: "#/components/parameters/alt" }, + { $ref: "#/components/parameters/callback" }, + { $ref: "#/components/parameters/fields" }, + { $ref: "#/components/parameters/oauth_token" }, + { $ref: "#/components/parameters/prettyPrint" }, + { $ref: "#/components/parameters/quotaUser" }, + { $ref: "#/components/parameters/uploadType" }, + { $ref: "#/components/parameters/upload_protocol" }, + ], + }, + "/v2/projects/{targetProjectId}/identityPlatform:initializeAuth": { + post: { + description: + "Initialize Identity Platform for a Cloud project. Identity Platform is an end-to-end authentication system for third-party users to access your apps and services. These could include mobile/web apps, games, APIs and beyond. This is the publicly available variant of EnableIdentityPlatform that is only available to billing-enabled projects.", + operationId: "identitytoolkit.projects.identityPlatform.initializeAuth", + responses: { + "200": { + description: "Successful response", + content: { + "*/*": { + schema: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformResponse", + }, + }, + }, + }, + }, + parameters: [ + { name: "targetProjectId", in: "path", required: true, schema: { type: "string" } }, + ], + requestBody: { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformRequest", + }, + }, + }, + }, + security: [ + { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2400,7 +2608,7 @@ export default { description: "Create an inbound SAML configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.inboundSamlConfigs.create", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2426,7 +2634,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2434,7 +2643,7 @@ export default { description: "List all inbound SAML configurations for an Identity Toolkit project.", operationId: "identitytoolkit.projects.inboundSamlConfigs.list", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2463,7 +2672,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2484,7 +2694,7 @@ export default { description: "Delete an inbound SAML configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.inboundSamlConfigs.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, }, @@ -2496,7 +2706,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2504,7 +2715,7 @@ export default { description: "Retrieve an inbound SAML configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.inboundSamlConfigs.get", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2522,7 +2733,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2530,7 +2742,7 @@ export default { description: "Update an inbound SAML configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.inboundSamlConfigs.patch", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2558,7 +2770,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2579,7 +2792,7 @@ export default { description: "Create an Oidc Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.oauthIdpConfigs.create", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2605,7 +2818,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2613,7 +2827,7 @@ export default { description: "List all Oidc Idp configurations for an Identity Toolkit project.", operationId: "identitytoolkit.projects.oauthIdpConfigs.list", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2642,7 +2856,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2663,7 +2878,7 @@ export default { description: "Delete an Oidc Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.oauthIdpConfigs.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, }, @@ -2675,7 +2890,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2683,7 +2899,7 @@ export default { description: "Retrieve an Oidc Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.oauthIdpConfigs.get", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2701,7 +2917,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2709,7 +2926,7 @@ export default { description: "Update an Oidc Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.oauthIdpConfigs.patch", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2737,7 +2954,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2758,7 +2976,7 @@ export default { description: "Create a tenant. Requires write permission on the Agent project.", operationId: "identitytoolkit.projects.tenants.create", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2774,7 +2992,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2783,7 +3002,7 @@ export default { "List tenants under the given agent project. Requires read permission on the Agent project.", operationId: "identitytoolkit.projects.tenants.list", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2813,7 +3032,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2834,7 +3054,7 @@ export default { description: "Delete a tenant. Requires write permission on the Agent project.", operationId: "identitytoolkit.projects.tenants.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, }, @@ -2846,7 +3066,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2854,7 +3075,7 @@ export default { description: "Get a tenant. Requires read permission on the Tenant resource.", operationId: "identitytoolkit.projects.tenants.get", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2870,7 +3091,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2878,7 +3100,7 @@ export default { description: "Update a tenant. Requires write permission on the Tenant resource.", operationId: "identitytoolkit.projects.tenants.patch", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -2902,7 +3124,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2924,7 +3147,7 @@ export default { "Gets the access control policy for a resource. An error is returned if the resource does not exist. An empty policy is returned if the resource exists but does not have a policy set on it. Caller must have the right Google IAM permission on the resource.", operationId: "identitytoolkit.projects.tenants.getIamPolicy", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleIamV1Policy" } } }, }, @@ -2943,7 +3166,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -2965,7 +3189,7 @@ export default { "Sets the access control policy for a resource. If the policy exists, it is replaced. Caller must have the right Google IAM permission on the resource.", operationId: "identitytoolkit.projects.tenants.setIamPolicy", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleIamV1Policy" } } }, }, @@ -2984,7 +3208,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3006,7 +3231,7 @@ export default { "Returns the caller's permissions on a resource. An error is returned if the resource does not exist. A caller is not required to have Google IAM permission to make this request.", operationId: "identitytoolkit.projects.tenants.testIamPermissions", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3029,7 +3254,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3051,7 +3277,7 @@ export default { "Create a default supported Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.create", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3079,7 +3305,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3088,7 +3315,7 @@ export default { "List all default supported Idp configurations for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.list", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3118,7 +3345,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3141,7 +3369,7 @@ export default { "Delete a default supported Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, }, @@ -3159,7 +3387,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3168,7 +3397,7 @@ export default { "Retrieve a default supported Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.get", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3192,7 +3421,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3201,7 +3431,7 @@ export default { "Update a default supported Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.patch", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3235,7 +3465,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3256,7 +3487,7 @@ export default { description: "Create an inbound SAML configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.inboundSamlConfigs.create", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3283,7 +3514,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3291,7 +3523,7 @@ export default { description: "List all inbound SAML configurations for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.inboundSamlConfigs.list", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3321,7 +3553,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3342,7 +3575,7 @@ export default { description: "Delete an inbound SAML configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.inboundSamlConfigs.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, }, @@ -3355,7 +3588,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3363,7 +3597,7 @@ export default { description: "Retrieve an inbound SAML configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.inboundSamlConfigs.get", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3382,7 +3616,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3390,7 +3625,7 @@ export default { description: "Update an inbound SAML configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.inboundSamlConfigs.patch", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3419,7 +3654,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3440,7 +3676,7 @@ export default { description: "Create an Oidc Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.oauthIdpConfigs.create", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3467,7 +3703,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3475,7 +3712,7 @@ export default { description: "List all Oidc Idp configurations for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.oauthIdpConfigs.list", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3505,7 +3742,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3526,7 +3764,7 @@ export default { description: "Delete an Oidc Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.oauthIdpConfigs.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GoogleProtobufEmpty" } } }, }, @@ -3539,7 +3777,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3547,7 +3786,7 @@ export default { description: "Retrieve an Oidc Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.oauthIdpConfigs.get", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3566,7 +3805,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3574,7 +3814,7 @@ export default { description: "Update an Oidc Idp configuration for an Identity Toolkit project.", operationId: "identitytoolkit.projects.tenants.oauthIdpConfigs.patch", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { @@ -3603,7 +3843,8 @@ export default { security: [ { Oauth2: ["https://www.googleapis.com/auth/cloud-platform"] }, { Oauth2: ["https://www.googleapis.com/auth/firebase"] }, - { apiKey: [] }, + { apiKeyQuery: [] }, + { apiKeyHeader: [] }, ], tags: ["projects"], }, @@ -3625,7 +3866,7 @@ export default { "The Token Service API lets you exchange either an ID token or a refresh token for an access token and a new refresh token. You can use the access token to securely call APIs that require user authorization.", operationId: "securetoken.token", responses: { - 200: { + "200": { description: "Successful response", content: { "*/*": { schema: { $ref: "#/components/schemas/GrantTokenResponse" } } }, }, @@ -3639,7 +3880,7 @@ export default { }, }, tags: ["secureToken"], - security: [{ apiKey: [] }], + security: [{ apiKeyQuery: [] }, { apiKeyHeader: [] }], }, parameters: [ { $ref: "#/components/parameters/access_token" }, @@ -3669,7 +3910,7 @@ export default { description: "Remove all accounts in the project, regardless of state.", operationId: "emulator.projects.accounts.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "application/json": { schema: { type: "object" } } }, }, @@ -3701,7 +3942,7 @@ export default { description: "Remove all accounts in the project, regardless of state.", operationId: "emulator.projects.accounts.delete", responses: { - 200: { + "200": { description: "Successful response", content: { "application/json": { schema: { type: "object" } } }, }, @@ -3725,7 +3966,7 @@ export default { description: "Get emulator-specific configuration for the project.", operationId: "emulator.projects.config.get", responses: { - 200: { + "200": { description: "Successful response", content: { "application/json": { @@ -3748,7 +3989,7 @@ export default { }, }, responses: { - 200: { + "200": { description: "Successful response", content: { "application/json": { @@ -3776,7 +4017,7 @@ export default { description: "List all pending confirmation codes for the project.", operationId: "emulator.projects.oobCodes.list", responses: { - 200: { + "200": { description: "Successful response", content: { "application/json": { @@ -3812,7 +4053,7 @@ export default { description: "List all pending confirmation codes for the project.", operationId: "emulator.projects.oobCodes.list", responses: { - 200: { + "200": { description: "Successful response", content: { "application/json": { @@ -3840,7 +4081,7 @@ export default { description: "List all pending phone verification codes for the project.", operationId: "emulator.projects.verificationCodes.list", responses: { - 200: { + "200": { description: "Successful response", content: { "application/json": { @@ -3876,7 +4117,7 @@ export default { description: "List all pending phone verification codes for the project.", operationId: "emulator.projects.verificationCodes.list", responses: { - 200: { + "200": { description: "Successful response", content: { "application/json": { @@ -4892,7 +5133,7 @@ export default { }, customAttributes: { description: - "JSON formatted custom attributes to be stored in the Identity Platform ID token. Specifying this field requires a Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control).", + "JSON formatted custom attributes to be stored in the Identity Platform ID token. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control).", type: "string", }, delegatedProjectNumber: { format: "int64", type: "string" }, @@ -4934,7 +5175,7 @@ export default { }, emailVerified: { description: - "Whether the user's email has been verified. Specifying this field requires a Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control).", + "Whether the user's email has been verified. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control).", type: "boolean", }, idToken: { @@ -4953,7 +5194,7 @@ export default { }, localId: { description: - "The ID of the user. Specifying this field requires a Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control). For requests from end-users, an ID token should be passed instead.", + "The ID of the user. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). For requests from end-users, an ID token should be passed instead.", type: "string", }, mfa: { $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1MfaInfo" }, @@ -4987,7 +5228,7 @@ export default { }, targetProjectId: { description: - "The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead.", + "The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead.", type: "string", }, tenantId: { @@ -5770,12 +6011,7 @@ export default { format: "int32", type: "integer", }, - delegatedProjectNumber: { - description: - "If true, the service will do the following list of checks before an account is uploaded: * Duplicate emails * Duplicate federated IDs * Federated ID provider validation If the duplication exists within the list of accounts to be uploaded, it will prevent the entire list from being uploaded. If the email or federated ID is a duplicate of a user already within the project/tenant, the account will not be uploaded, but the rest of the accounts will be unaffected. If false, these checks will be skipped.", - format: "int64", - type: "string", - }, + delegatedProjectNumber: { format: "int64", type: "string" }, dkLen: { description: "The desired key length for the STANDARD_SCRYPT hashing function. Must be at least 1.", @@ -5816,7 +6052,11 @@ export default { format: "byte", type: "string", }, - sanityCheck: { type: "boolean" }, + sanityCheck: { + description: + "If true, the service will do the following list of checks before an account is uploaded: * Duplicate emails * Duplicate federated IDs * Federated ID provider validation If the duplication exists within the list of accounts to be uploaded, it will prevent the entire list from being uploaded. If the email or federated ID is a duplicate of a user already within the project/tenant, the account will not be uploaded, but the rest of the accounts will be unaffected. If false, these checks will be skipped.", + type: "boolean", + }, signerKey: { description: "The signer key used to hash the password. Required for the following hashing functions: * SCRYPT, * HMAC_MD5, * HMAC_SHA1, * HMAC_SHA256, * HMAC_SHA512", @@ -5828,7 +6068,8 @@ export default { type: "string", }, users: { - description: "A list of accounts to upload.", + description: + "A list of accounts to upload. `local_id` is required for each user; everything else is optional.", items: { $ref: "#/components/schemas/GoogleCloudIdentitytoolkitV1UserInfo" }, type: "array", }, @@ -6019,6 +6260,32 @@ export default { }, type: "object", }, + GoogleCloudIdentitytoolkitAdminV2AllowByDefault: { + description: + "Defines a policy of allowing every region by default and adding disallowed regions to a disallow list.", + properties: { + disallowedRegions: { + description: + "Two letter unicode region codes to disallow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json", + items: { type: "string" }, + type: "array", + }, + }, + type: "object", + }, + GoogleCloudIdentitytoolkitAdminV2AllowlistOnly: { + description: + "Defines a policy of only allowing regions by explicitly adding them to an allowlist.", + properties: { + allowedRegions: { + description: + "Two letter unicode region codes to allow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json", + items: { type: "string" }, + type: "array", + }, + }, + type: "object", + }, GoogleCloudIdentitytoolkitAdminV2Anonymous: { description: "Configuration options related to authenticating an anonymous user.", properties: { @@ -6081,6 +6348,33 @@ export default { }, type: "object", }, + GoogleCloudIdentitytoolkitAdminV2ClientPermissionConfig: { + description: + "Options related to how clients making requests on behalf of a tenant should be configured.", + properties: { + permissions: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ClientPermissions", + }, + }, + type: "object", + }, + GoogleCloudIdentitytoolkitAdminV2ClientPermissions: { + description: + "Configuration related to restricting a user's ability to affect their account.", + properties: { + disabledUserDeletion: { + description: + "When true, end users cannot delete their account on the associated project through any of our API methods", + type: "boolean", + }, + disabledUserSignup: { + description: + "When true, end users cannot sign up for a new account on the associated project through any of our API methods", + type: "boolean", + }, + }, + type: "object", + }, GoogleCloudIdentitytoolkitAdminV2CodeFlowConfig: { description: "Additional config for Apple for code flow.", properties: { @@ -6101,10 +6395,17 @@ export default { items: { type: "string" }, type: "array", }, + autodeleteAnonymousUsers: { + description: "Whether anonymous users will be auto-deleted after a period of 30 days.", + type: "boolean", + }, blockingFunctions: { $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2BlockingFunctionsConfig", }, client: { $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ClientConfig" }, + emailPrivacyConfig: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig", + }, mfa: { $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2MultiFactorAuthConfig", }, @@ -6125,6 +6426,9 @@ export default { }, quota: { $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2QuotaConfig" }, signIn: { $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2SignInConfig" }, + smsRegionConfig: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig", + }, subtype: { description: "Output only. The subtype of this config.", enum: ["SUBTYPE_UNSPECIFIED", "IDENTITY_PLATFORM", "FIREBASE_AUTH"], @@ -6217,6 +6521,18 @@ export default { }, type: "object", }, + GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig: { + description: + "Configuration for settings related to email privacy and public visibility. Settings in this config protect against email enumeration, but may make some trade-offs in user-friendliness.", + properties: { + enableImprovedEmailPrivacy: { + description: + "Migrates the project to a state of improved email privacy. For example certain error codes are more generic to avoid giving away information on whether the account exists. In addition, this disables certain features that as a side-effect allow user enumeration. Enabling this toggle disables the fetchSignInMethodsForEmail functionality and changing the user's email to an unverified email. It is recommended to remove dependence on this functionality and enable this toggle to improve user privacy.", + type: "boolean", + }, + }, + type: "object", + }, GoogleCloudIdentitytoolkitAdminV2EmailTemplate: { description: "Email template. The subject and body fields can contain the following placeholders which will be replaced with the appropriate values: %LINK% - The link to use to redeem the send OOB code. %EMAIL% - The email where the email is being sent. %NEW_EMAIL% - The new email being set for the account (when applicable). %APP_NAME% - The GCP project's display name. %DISPLAY_NAME% - The user's display name.", @@ -6368,6 +6684,16 @@ export default { }, type: "object", }, + GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformRequest: { + description: "Request for InitializeIdentityPlatform.", + properties: {}, + type: "object", + }, + GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformResponse: { + description: "Response for InitializeIdentityPlatform. Empty for now.", + properties: {}, + type: "object", + }, GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpConfigsResponse: { description: "Response for DefaultSupportedIdpConfigs", properties: { @@ -6663,6 +6989,19 @@ export default { }, type: "object", }, + GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig: { + description: + "Configures the regions where users are allowed to send verification SMS for the project or tenant. This is based on the calling code of the destination phone number.", + properties: { + allowByDefault: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2AllowByDefault", + }, + allowlistOnly: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2AllowlistOnly", + }, + }, + type: "object", + }, GoogleCloudIdentitytoolkitAdminV2SmsTemplate: { description: "The template to use when sending an SMS.", properties: { @@ -6751,12 +7090,22 @@ export default { description: "Whether to allow email/password user authentication.", type: "boolean", }, + autodeleteAnonymousUsers: { + description: "Whether anonymous users will be auto-deleted after a period of 30 days.", + type: "boolean", + }, + client: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2ClientPermissionConfig", + }, disableAuth: { description: "Whether authentication is disabled for the tenant. If true, the users under the disabled tenant are not allowed to sign-in. Admins of the disabled tenant are not able to manage its users.", type: "boolean", }, displayName: { description: "Display name of the tenant.", type: "string" }, + emailPrivacyConfig: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig", + }, enableAnonymousUser: { description: "Whether to enable anonymous user authentication.", type: "boolean", @@ -6772,12 +7121,18 @@ export default { mfaConfig: { $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2MultiFactorAuthConfig", }, + monitoring: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2MonitoringConfig", + }, name: { description: 'Output only. Resource name of a tenant. For example: "projects/{project-id}/tenants/{tenant-id}"', readOnly: true, type: "string", }, + smsRegionConfig: { + $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig", + }, testPhoneNumbers: { additionalProperties: { type: "string" }, description: @@ -7077,7 +7432,7 @@ export default { condition: { $ref: "#/components/schemas/GoogleTypeExpr" }, members: { description: - "Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. ", + "Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. Does not include identities that come from external identity providers (IdPs) through identity federation. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a Google service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `serviceAccount:{projectid}.svc.id.goog[{namespace}/{kubernetes-sa}]`: An identifier for a [Kubernetes service account](https://cloud.google.com/kubernetes-engine/docs/how-to/kubernetes-service-accounts). For example, `my-project.svc.id.goog[my-namespace/my-kubernetes-sa]`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. ", items: { type: "string" }, type: "array", }, @@ -7488,13 +7843,20 @@ export default { }, }, }, - apiKey: { + apiKeyQuery: { type: "apiKey", name: "key", in: "query", description: "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", }, + apiKeyHeader: { + type: "apiKey", + name: "x-goog-api-key", + in: "header", + description: + "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", + }, }, }, }; diff --git a/src/emulator/auth/schema.ts b/src/emulator/auth/schema.ts index 31c2144929f..2f977a49c4f 100644 --- a/src/emulator/auth/schema.ts +++ b/src/emulator/auth/schema.ts @@ -879,7 +879,7 @@ export interface components { */ createdAt?: string; /** - * JSON formatted custom attributes to be stored in the Identity Platform ID token. Specifying this field requires a Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control). + * JSON formatted custom attributes to be stored in the Identity Platform ID token. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). */ customAttributes?: string; delegatedProjectNumber?: string; @@ -912,7 +912,7 @@ export interface components { */ email?: string; /** - * Whether the user's email has been verified. Specifying this field requires a Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control). + * Whether the user's email has been verified. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). */ emailVerified?: boolean; /** @@ -926,7 +926,7 @@ export interface components { lastLoginAt?: string; linkProviderUserInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1ProviderUserInfo"]; /** - * The ID of the user. Specifying this field requires a Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control). For requests from end-users, an ID token should be passed instead. + * The ID of the user. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). For requests from end-users, an ID token should be passed instead. */ localId?: string; mfa?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaInfo"]; @@ -955,7 +955,7 @@ export interface components { */ returnSecureToken?: boolean; /** - * The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper permissions (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead. + * The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead. */ targetProjectId?: string; /** @@ -1674,9 +1674,6 @@ export interface components { * The CPU memory cost parameter to be used by the STANDARD_SCRYPT hashing function. This parameter, along with block_size and cpu_mem_cost help tune the resources needed to hash a password, and should be tuned as processor speeds and memory technologies advance. */ cpuMemCost?: number; - /** - * If true, the service will do the following list of checks before an account is uploaded: * Duplicate emails * Duplicate federated IDs * Federated ID provider validation If the duplication exists within the list of accounts to be uploaded, it will prevent the entire list from being uploaded. If the email or federated ID is a duplicate of a user already within the project/tenant, the account will not be uploaded, but the rest of the accounts will be unaffected. If false, these checks will be skipped. - */ delegatedProjectNumber?: string; /** * The desired key length for the STANDARD_SCRYPT hashing function. Must be at least 1. @@ -1706,6 +1703,9 @@ export interface components { * One or more bytes to be inserted between the salt and plain text password. For stronger security, this should be a single non-printable character. */ saltSeparator?: string; + /** + * If true, the service will do the following list of checks before an account is uploaded: * Duplicate emails * Duplicate federated IDs * Federated ID provider validation If the duplication exists within the list of accounts to be uploaded, it will prevent the entire list from being uploaded. If the email or federated ID is a duplicate of a user already within the project/tenant, the account will not be uploaded, but the rest of the accounts will be unaffected. If false, these checks will be skipped. + */ sanityCheck?: boolean; /** * The signer key used to hash the password. Required for the following hashing functions: * SCRYPT, * HMAC_MD5, * HMAC_SHA1, * HMAC_SHA256, * HMAC_SHA512 @@ -1716,7 +1716,7 @@ export interface components { */ tenantId?: string; /** - * A list of accounts to upload. + * A list of accounts to upload. `local_id` is required for each user; everything else is optional. */ users?: components["schemas"]["GoogleCloudIdentitytoolkitV1UserInfo"][]; }; @@ -1869,6 +1869,24 @@ export interface components { */ suggestedTimeout?: string; }; + /** + * Defines a policy of allowing every region by default and adding disallowed regions to a disallow list. + */ + GoogleCloudIdentitytoolkitAdminV2AllowByDefault: { + /** + * Two letter unicode region codes to disallow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json + */ + disallowedRegions?: string[]; + }; + /** + * Defines a policy of only allowing regions by explicitly adding them to an allowlist. + */ + GoogleCloudIdentitytoolkitAdminV2AllowlistOnly: { + /** + * Two letter unicode region codes to allow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json + */ + allowedRegions?: string[]; + }; /** * Configuration options related to authenticating an anonymous user. */ @@ -1914,6 +1932,25 @@ export interface components { firebaseSubdomain?: string; permissions?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Permissions"]; }; + /** + * Options related to how clients making requests on behalf of a tenant should be configured. + */ + GoogleCloudIdentitytoolkitAdminV2ClientPermissionConfig: { + permissions?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ClientPermissions"]; + }; + /** + * Configuration related to restricting a user's ability to affect their account. + */ + GoogleCloudIdentitytoolkitAdminV2ClientPermissions: { + /** + * When true, end users cannot delete their account on the associated project through any of our API methods + */ + disabledUserDeletion?: boolean; + /** + * When true, end users cannot sign up for a new account on the associated project through any of our API methods + */ + disabledUserSignup?: boolean; + }; /** * Additional config for Apple for code flow. */ @@ -1939,8 +1976,13 @@ export interface components { * List of domains authorized for OAuth redirects */ authorizedDomains?: string[]; + /** + * Whether anonymous users will be auto-deleted after a period of 30 days. + */ + autodeleteAnonymousUsers?: boolean; blockingFunctions?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2BlockingFunctionsConfig"]; client?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ClientConfig"]; + emailPrivacyConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig"]; mfa?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MultiFactorAuthConfig"]; monitoring?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MonitoringConfig"]; multiTenant?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MultiTenantConfig"]; @@ -1951,6 +1993,7 @@ export interface components { notification?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2NotificationConfig"]; quota?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2QuotaConfig"]; signIn?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SignInConfig"]; + smsRegionConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig"]; /** * Output only. The subtype of this config. */ @@ -2034,6 +2077,15 @@ export interface components { */ passwordRequired?: boolean; }; + /** + * Configuration for settings related to email privacy and public visibility. Settings in this config protect against email enumeration, but may make some trade-offs in user-friendliness. + */ + GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig: { + /** + * Migrates the project to a state of improved email privacy. For example certain error codes are more generic to avoid giving away information on whether the account exists. In addition, this disables certain features that as a side-effect allow user enumeration. Enabling this toggle disables the fetchSignInMethodsForEmail functionality and changing the user's email to an unverified email. It is recommended to remove dependence on this functionality and enable this toggle to improve user privacy. + */ + enableImprovedEmailPrivacy?: boolean; + }; /** * Email template. The subject and body fields can contain the following placeholders which will be replaced with the appropriate values: %LINK% - The link to use to redeem the send OOB code. %EMAIL% - The email where the email is being sent. %NEW_EMAIL% - The new email being set for the account (when applicable). %APP_NAME% - The GCP project's display name. %DISPLAY_NAME% - The user's display name. */ @@ -2181,6 +2233,14 @@ export interface components { */ emailSendingConfig?: boolean; }; + /** + * Request for InitializeIdentityPlatform. + */ + GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformRequest: { [key: string]: any }; + /** + * Response for InitializeIdentityPlatform. Empty for now. + */ + GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformResponse: { [key: string]: any }; /** * Response for DefaultSupportedIdpConfigs */ @@ -2420,6 +2480,13 @@ export interface components { hashConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2HashConfig"]; phoneNumber?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2PhoneNumber"]; }; + /** + * Configures the regions where users are allowed to send verification SMS for the project or tenant. This is based on the calling code of the destination phone number. + */ + GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig: { + allowByDefault?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2AllowByDefault"]; + allowlistOnly?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2AllowlistOnly"]; + }; /** * The template to use when sending an SMS. */ @@ -2513,6 +2580,11 @@ export interface components { * Whether to allow email/password user authentication. */ allowPasswordSignup?: boolean; + /** + * Whether anonymous users will be auto-deleted after a period of 30 days. + */ + autodeleteAnonymousUsers?: boolean; + client?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ClientPermissionConfig"]; /** * Whether authentication is disabled for the tenant. If true, the users under the disabled tenant are not allowed to sign-in. Admins of the disabled tenant are not able to manage its users. */ @@ -2521,6 +2593,7 @@ export interface components { * Display name of the tenant. */ displayName?: string; + emailPrivacyConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig"]; /** * Whether to enable anonymous user authentication. */ @@ -2532,10 +2605,12 @@ export interface components { hashConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2HashConfig"]; inheritance?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Inheritance"]; mfaConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MultiFactorAuthConfig"]; + monitoring?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MonitoringConfig"]; /** * Output only. Resource name of a tenant. For example: "projects/{project-id}/tenants/{tenant-id}" */ name?: string; + smsRegionConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig"]; /** * A map of pairs that can be used for MFA. The phone number should be in E.164 format (https://www.itu.int/rec/T-REC-E.164/) and a maximum of 10 pairs can be added (error will be thrown once exceeded). */ @@ -2802,7 +2877,7 @@ export interface components { GoogleIamV1Binding: { condition?: components["schemas"]["GoogleTypeExpr"]; /** - * Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. + * Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. Does not include identities that come from external identity providers (IdPs) through identity federation. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a Google service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `serviceAccount:{projectid}.svc.id.goog[{namespace}/{kubernetes-sa}]`: An identifier for a [Kubernetes service account](https://cloud.google.com/kubernetes-engine/docs/how-to/kubernetes-service-accounts). For example, `my-project.svc.id.goog[my-namespace/my-kubernetes-sa]`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. */ members?: string[]; /** diff --git a/src/emulator/auth/server.ts b/src/emulator/auth/server.ts index e63eef89086..f7c61b6c9d2 100644 --- a/src/emulator/auth/server.ts +++ b/src/emulator/auth/server.ts @@ -166,14 +166,25 @@ export async function createApp( ); const apiKeyAuthenticator: PromiseAuthenticator = (ctx, info) => { - if (info.in !== "query") { - throw new Error('apiKey must be defined as in: "query" in API spec.'); - } if (!info.name) { - throw new Error("apiKey param name is undefined in API spec."); + throw new Error("apiKey param/header name is undefined in API spec."); + } + + let key: string | undefined; + const req = ctx.req as express.Request; + switch (info.in) { + case "header": + key = req.get(info.name); + break; + case "query": { + const q = req.query[info.name]; + key = typeof q === "string" ? q : undefined; + break; + } + default: + throw new Error('apiKey must be defined as in: "query" or "header" in API spec.'); } - const key = (ctx.req as express.Request).query[info.name]; - if (typeof key === "string" && key.length > 0) { + if (key) { return { type: "success", user: getProjectIdByApiKey(key) }; } else { return undefined; @@ -218,7 +229,8 @@ export async function createApp( const apis = await exegesisExpress.middleware(specForRouter(), { controllers: { auth: toExegesisController(authOperations, getProjectStateById) }, authenticators: { - apiKey: apiKeyAuthenticator, + apiKeyQuery: apiKeyAuthenticator, + apiKeyHeader: apiKeyAuthenticator, Oauth2: oauth2Authenticator, }, autoHandleHttpErrors(err) { @@ -305,7 +317,7 @@ export async function createApp( if (ctx.res.statusCode === 401) { // Normalize unauthenticated responses to match production. const requirements = (ctx.api.operationObject as OperationObject).security; - if (requirements?.some((req) => req.apiKey)) { + if (requirements?.some((req) => req.apiKeyQuery || req.apiKeyHeader)) { throw new PermissionDeniedError("The request is missing a valid API key."); } else { throw new UnauthenticatedError( diff --git a/src/test/emulators/auth/rest.spec.ts b/src/test/emulators/auth/rest.spec.ts index b2adb5efeb4..8e2494e968f 100644 --- a/src/test/emulators/auth/rest.spec.ts +++ b/src/test/emulators/auth/rest.spec.ts @@ -93,6 +93,28 @@ describeAuthEmulator("authentication", ({ authApi }) => { }); }); + it("should accept API key as a query parameter", async () => { + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:signUp") + .query({ key: "fake-api-key" }) + .send({}) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).not.to.have.property("error"); + }); + }); + + it("should accept API key in HTTP Header x-goog-api-key", async () => { + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:signUp") + .set("x-goog-api-key", "fake-api-key") + .send({}) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).not.to.have.property("error"); + }); + }); + it("should ignore non-Bearer Authorization headers", async () => { await authApi() .post("/identitytoolkit.googleapis.com/v1/accounts:signUp") @@ -143,7 +165,11 @@ describeAuthEmulator("authentication", ({ authApi }) => { .post("/identitytoolkit.googleapis.com/v1/accounts:signUp") // This authenticates as owner of the default projectId. The exact value // and expiry don't matter -- the Emulator only checks for the format. - .set("Authorization", "Bearer ya29.AHES6ZRVmB7fkLtd1XTmq6mo0S1wqZZi3-Lh_s-6Uw7p8vtgSwg") + .set( + "Authorization", + // Not an actual token. Breaking it down to avoid linter false positives. + "Bearer ya" + "29.AHES0ZZZZZ0fff" + "ff0XXXX0mmmm0wwwww0-LL_l-0bb0b0bbbbbb" + ) .send({ // This field requires OAuth 2 and should work correctly. targetProjectId: "example2", From 6ed818b747f8847948759639bf2c1c013d8e9cc1 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 1 Dec 2022 12:40:21 -0500 Subject: [PATCH 0697/1699] Detect Google Cloud Workstations (#5283) Default to --no-localhost when calling login from Cloud Workstations --- CHANGELOG.md | 1 + src/test/utils.spec.ts | 26 ++++++++++++++++++++++++++ src/utils.ts | 2 +- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33ff5550bca..29b20fd25a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ - Fix bug where disabling background triggers did nothing. (#5221) - Fix bug in auth emulator where empty string should throw invalid email instead of missing email. (#3898) - Fix bug in auth emulator in which createdAt was not set for signInWithIdp new users. (#5203) +- Default to --no-localhost when calling login from Google Cloud Workstations - Support the x-goog-api-key header in auth emulator. (#5249) diff --git a/src/test/utils.spec.ts b/src/test/utils.spec.ts index a6dc6570906..384ca44ef49 100644 --- a/src/test/utils.spec.ts +++ b/src/test/utils.spec.ts @@ -70,6 +70,32 @@ describe("utils", () => { }); }); + describe("isCloudEnvironment", () => { + let originalEnv: NodeJS.ProcessEnv; + + beforeEach(() => { + originalEnv = { ...process.env }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should return false by default", () => { + expect(utils.isCloudEnvironment()).to.be.false; + }); + + it("should return true when in codespaces", () => { + process.env.CODESPACES = "true"; + expect(utils.isCloudEnvironment()).to.be.true; + }); + + it("should return true when in Cloud Workstations", () => { + process.env.GOOGLE_CLOUD_WORKSTATIONS = "true"; + expect(utils.isCloudEnvironment()).to.be.true; + }); + }); + describe("getDatabaseUrl", () => { it("should create a url for prod", () => { expect(utils.getDatabaseUrl("https://firebaseio.com", "fir-proj", "/")).to.equal( diff --git a/src/utils.ts b/src/utils.ts index e14d43ef73f..39c517587b2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -577,7 +577,7 @@ export function datetimeString(d: Date): string { * Indicates whether the end-user is running the CLI from a cloud-based environment. */ export function isCloudEnvironment() { - return !!process.env.CODESPACES; + return !!process.env.CODESPACES || !!process.env.GOOGLE_CLOUD_WORKSTATIONS; } /** From d59f265bd4c8ae563a5e42deb5f3e6472656da8c Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 1 Dec 2022 10:15:43 -0800 Subject: [PATCH 0698/1699] Fix bug using --only filter to disable no-op deploy fails deploy because function source isn't uploaded (#5280) Fixes https://github.com/firebase/firebase-tools/issues/5270. --- CHANGELOG.md | 1 + src/deploy/functions/deploy.ts | 8 +++++- src/test/deploy/functions/deploy.spec.ts | 31 +++++++++++++++++++++--- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29b20fd25a6..074bef81184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Fix bug in auth emulator in which createdAt was not set for signInWithIdp new users. (#5203) - Default to --no-localhost when calling login from Google Cloud Workstations - Support the x-goog-api-key header in auth emulator. (#5249) +- Fix bug where function deployments using --only filter sometimes failed deployments. (#5280) diff --git a/src/deploy/functions/deploy.ts b/src/deploy/functions/deploy.ts index a432dc4caa5..ecdc7b68050 100644 --- a/src/deploy/functions/deploy.ts +++ b/src/deploy/functions/deploy.ts @@ -112,7 +112,7 @@ export async function deploy( await checkHttpIam(context, options, payload); const uploads: Promise[] = []; for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) { - if (shouldUploadBeSkipped(wantBackend, haveBackend)) { + if (shouldUploadBeSkipped(context, wantBackend, haveBackend)) { continue; } uploads.push(uploadCodebase(context, codebase, wantBackend)); @@ -124,9 +124,15 @@ export async function deploy( * @return True IFF wantBackend + haveBackend are the same */ export function shouldUploadBeSkipped( + context: args.Context, wantBackend: backend.Backend, haveBackend: backend.Backend ): boolean { + // If function targets are specified by --only flag, assume that function will be deployed + // and go ahead and upload the source. + if (context.filters && context.filters.length > 0) { + return false; + } const wantEndpoints = backend.allEndpoints(wantBackend); const haveEndpoints = backend.allEndpoints(haveBackend); diff --git a/src/test/deploy/functions/deploy.spec.ts b/src/test/deploy/functions/deploy.spec.ts index 78e3ab5feaa..1fb4e103090 100644 --- a/src/test/deploy/functions/deploy.spec.ts +++ b/src/test/deploy/functions/deploy.spec.ts @@ -1,5 +1,6 @@ import { expect } from "chai"; +import * as args from "../../../deploy/functions/args"; import * as backend from "../../../deploy/functions/backend"; import * as deploy from "../../../deploy/functions/deploy"; @@ -17,6 +18,11 @@ describe("deploy", () => { ...ENDPOINT_BASE, httpsTrigger: {}, }; + + const CONTEXT: args.Context = { + projectId: "project", + }; + describe("shouldUploadBeSkipped", () => { let endpoint1InWantBackend: backend.Endpoint; let endpoint2InWantBackend: backend.Endpoint; @@ -63,7 +69,7 @@ describe("deploy", () => { endpoint2InHaveBackend.hash = endpoint2InWantBackend.hash; // Execute - const result = deploy.shouldUploadBeSkipped(wantBackend, haveBackend); + const result = deploy.shouldUploadBeSkipped(CONTEXT, wantBackend, haveBackend); // Expect expect(result).to.be.true; @@ -76,7 +82,7 @@ describe("deploy", () => { endpoint2InHaveBackend.hash = "No_match"; // Execute - const result = deploy.shouldUploadBeSkipped(wantBackend, haveBackend); + const result = deploy.shouldUploadBeSkipped(CONTEXT, wantBackend, haveBackend); // Expect expect(result).to.be.false; @@ -92,7 +98,7 @@ describe("deploy", () => { haveBackend = backend.of(endpoint1InHaveBackend); // Execute - const result = deploy.shouldUploadBeSkipped(wantBackend, haveBackend); + const result = deploy.shouldUploadBeSkipped(CONTEXT, wantBackend, haveBackend); // Expect expect(result).to.be.false; @@ -108,7 +114,24 @@ describe("deploy", () => { haveBackend = backend.of(endpoint1InHaveBackend, endpoint2InHaveBackend); // Execute - const result = deploy.shouldUploadBeSkipped(wantBackend, haveBackend); + const result = deploy.shouldUploadBeSkipped(CONTEXT, wantBackend, haveBackend); + + // Expect + expect(result).to.be.false; + }); + + it("should not skip if endpoint filter is specified", () => { + endpoint1InWantBackend.hash = "1"; + endpoint2InWantBackend.hash = "2"; + endpoint1InHaveBackend.hash = endpoint1InWantBackend.hash; + endpoint2InHaveBackend.hash = endpoint2InWantBackend.hash; + + // Execute + const result = deploy.shouldUploadBeSkipped( + { ...CONTEXT, filters: [{ idChunks: ["foobar"] }] }, + wantBackend, + haveBackend + ); // Expect expect(result).to.be.false; From e7fa7325f244ed6493430fa86deb409706a83675 Mon Sep 17 00:00:00 2001 From: Tristan Waddington Date: Thu, 1 Dec 2022 14:59:29 -0800 Subject: [PATCH 0699/1699] Fix functions:delete id filter matching. --- src/commands/functions-delete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/functions-delete.ts b/src/commands/functions-delete.ts index 4f5071d7225..fd99a4c6bfa 100644 --- a/src/commands/functions-delete.ts +++ b/src/commands/functions-delete.ts @@ -35,7 +35,7 @@ export const command = new Command("functions:delete [filters...]") const context: args.Context = { projectId: needProjectId(options), - filters: filters.map((f) => ({ idChunks: f.split(".") })), + filters: filters.map((f) => ({ idChunks: f.split(/[-.]/) })), }; const [config, existingBackend] = await Promise.all([ From 10e022bb87ab7ddad1a81c08e2ad4ad8b6dc575a Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 1 Dec 2022 21:30:48 -0500 Subject: [PATCH 0700/1699] Fix/next js predeploy hooks (#5288) * Fix predeploy hooks not running for framework deploy Co-authored-by: SirFreakness --- CHANGELOG.md | 1 + src/deploy/lifecycleHooks.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074bef81184..2d018fea5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,4 +3,5 @@ - Fix bug in auth emulator in which createdAt was not set for signInWithIdp new users. (#5203) - Default to --no-localhost when calling login from Google Cloud Workstations - Support the x-goog-api-key header in auth emulator. (#5249) +- Fix bug in deploying web frameworks when a predeploy hook was configured in firebase.json (#5199) - Fix bug where function deployments using --only filter sometimes failed deployments. (#5280) diff --git a/src/deploy/lifecycleHooks.ts b/src/deploy/lifecycleHooks.ts index fca96fb3370..0c08e09a8cb 100644 --- a/src/deploy/lifecycleHooks.ts +++ b/src/deploy/lifecycleHooks.ts @@ -48,7 +48,7 @@ function getChildEnvironment(target: string, overallOptions: any, config: any) { let resourceDir; switch (target) { case "hosting": - resourceDir = overallOptions.config.path(config.public); + resourceDir = overallOptions.config.path(config.public ?? config.source); break; case "functions": resourceDir = overallOptions.config.path(config.source); From 7b070b015b3d2caedc331269ff59f6859d354246 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Fri, 2 Dec 2022 11:16:40 -0500 Subject: [PATCH 0701/1699] Add int tests for test lab, remote config, and fireperf (#5200) * bumping functions package * revert package * updated to 4.0.2 * increase the function count * try testlab only * testlab might have a bug, trying out perf & remote config * adding in preserveExternalChanges and RESET_VALUE * formatter * trying run with functions binary * adding in required package to root & cleaning up reset test case * fixing imports * adding firebase/types * yanking tar * bumping functions package * revert to RuntimeOptions * remove @firebase/logger --- npm-shrinkwrap.json | 2055 ++++++++--------- package.json | 5 +- .../functionsEmulatorRuntime.spec.ts | 2 +- .../functions-deploy-tests/functions/fns.js | 3 + .../functions/package.json | 2 +- scripts/functions-deploy-tests/tests.ts | 81 +- 6 files changed, 960 insertions(+), 1188 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 054c7074274..cac6a02c3f3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -90,6 +90,7 @@ "@types/cross-spawn": "^6.0.1", "@types/express": "^4.17.0", "@types/express-serve-static-core": "^4.17.8", + "@types/firebase": "^3.2.1", "@types/fs-extra": "^9.0.13", "@types/glob": "^7.1.1", "@types/inquirer": "^8.1.3", @@ -134,8 +135,8 @@ "eslint-plugin-jsdoc": "^39.2.9", "eslint-plugin-prettier": "^4.0.0", "firebase": "^7.24.0", - "firebase-admin": "^9.4.2", - "firebase-functions": "^3.23.0", + "firebase-admin": "^10.0.0", + "firebase-functions": "^4.1.0", "google-discovery-to-swagger": "^2.1.0", "googleapis": "^105.0.0", "mocha": "^9.1.3", @@ -812,6 +813,18 @@ "integrity": "sha512-W98NvvOe/Med3o66xTO03pd7a2omZebH79PV64gSE+ceDdU8uxQhFTa7ISiD1kseyqyOrMyW5/MNdsGEU02i3Q==", "dev": true }, + "node_modules/@fastify/busboy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", + "integrity": "sha512-Fv854f94v0CzIDllbY3i/0NJPNBRNLDawf3BTYVGCe9VrIIs3Wi7AFx24F9NzCxdf0wyx/x0Q9kEVnvDOPnlxA==", + "dev": true, + "dependencies": { + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/@firebase/analytics": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.6.0.tgz", @@ -836,6 +849,22 @@ "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==", "dev": true }, + "node_modules/@firebase/analytics/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/analytics/node_modules/@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "node_modules/@firebase/analytics/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -860,75 +889,32 @@ "xmlhttprequest": "1.8.0" } }, - "node_modules/@firebase/app-compat": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.23.tgz", - "integrity": "sha512-c0QOhU2UVxZ7N5++nLQgKZ899ZC8+/ESa8VCzsQDwBw1T3MFAD1cG40KhB+CGtp/uYk/w6Jtk8k0xyZu6O2LOg==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/app": "0.7.22", - "@firebase/component": "0.5.13", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.5.2", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/app": { - "version": "0.7.22", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.22.tgz", - "integrity": "sha512-v3AXSCwAvZyIFzOGgPAYtzjltm1M9R4U4yqsIBPf5B4ryaT1EGK+3ETZUOckNl5y2YwdKRJVPDDore+B2xg0Ug==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/component": "0.5.13", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.5.2", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/component": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", - "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", - "dev": true, - "peer": true, - "dependencies": { - "@firebase/util": "1.5.2", - "tslib": "^2.1.0" - } + "node_modules/@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==", + "dev": true }, - "node_modules/@firebase/app-compat/node_modules/@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } + "node_modules/@firebase/app/node_modules/@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "dev": true }, - "node_modules/@firebase/app-compat/node_modules/@firebase/util": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", - "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "node_modules/@firebase/app/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", "dev": true, - "peer": true, "dependencies": { - "tslib": "^2.1.0" + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" } }, - "node_modules/@firebase/app-compat/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true, - "peer": true - }, - "node_modules/@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "node_modules/@firebase/app/node_modules/@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", "dev": true }, "node_modules/@firebase/app/node_modules/@firebase/util": { @@ -953,16 +939,16 @@ } }, "node_modules/@firebase/auth-interop-types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", - "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", "dev": true, "peerDependencies": { "@firebase/app-types": "0.x", - "@firebase/util": "0.x" + "@firebase/util": "1.x" } }, - "node_modules/@firebase/auth-types": { + "node_modules/@firebase/auth/node_modules/@firebase/auth-types": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", @@ -972,141 +958,81 @@ "@firebase/util": "0.x" } }, - "node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "node_modules/@firebase/component/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "node_modules/@firebase/auth/node_modules/@firebase/util": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.4.1.tgz", + "integrity": "sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==", "dev": true, + "peer": true, "dependencies": { - "tslib": "^1.11.1" + "tslib": "^2.1.0" } }, - "node_modules/@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "node_modules/@firebase/auth/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true, - "dependencies": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "faye-websocket": "0.11.3", - "tslib": "^1.11.1" - } + "peer": true }, - "node_modules/@firebase/database-compat": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.5.tgz", - "integrity": "sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==", + "node_modules/@firebase/component": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", "dev": true, "dependencies": { - "@firebase/component": "0.5.10", - "@firebase/database": "0.12.5", - "@firebase/database-types": "0.9.4", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/database-compat/node_modules/@firebase/app-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", - "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==", + "node_modules/@firebase/component/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@firebase/database-compat/node_modules/@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", - "dev": true, - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/component": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", - "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", - "dev": true, - "dependencies": { - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/database": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.5.tgz", - "integrity": "sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==", + "node_modules/@firebase/database": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", "dev": true, "dependencies": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, - "node_modules/@firebase/database-compat/node_modules/@firebase/database-types": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.4.tgz", - "integrity": "sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==", - "dev": true, - "dependencies": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.4.3" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/util": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", - "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", + "node_modules/@firebase/database-compat": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", "dev": true, "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, - "node_modules/@firebase/database-compat/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==", + "node_modules/@firebase/database-compat/node_modules/@firebase/database-types": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "dev": true, "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" } }, "node_modules/@firebase/database-compat/node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, "node_modules/@firebase/database-types": { @@ -1118,14 +1044,17 @@ "@firebase/app-types": "0.6.1" } }, - "node_modules/@firebase/database/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } + "node_modules/@firebase/database-types/node_modules/@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "dev": true + }, + "node_modules/@firebase/database/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, "node_modules/@firebase/firestore": { "version": "1.18.0", @@ -1160,6 +1089,22 @@ "@firebase/app-types": "0.x" } }, + "node_modules/@firebase/firestore/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/firestore/node_modules/@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "node_modules/@firebase/firestore/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1169,6 +1114,19 @@ "tslib": "^1.11.1" } }, + "node_modules/@firebase/firestore/node_modules/@grpc/proto-loader": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz", + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", + "dev": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@firebase/firestore/node_modules/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -1201,6 +1159,25 @@ "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==", "dev": true }, + "node_modules/@firebase/functions/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/functions/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "dependencies": { + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/functions/node_modules/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -1236,6 +1213,16 @@ "@firebase/app-types": "0.x" } }, + "node_modules/@firebase/installations/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/installations/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1246,9 +1233,18 @@ } }, "node_modules/@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/logger/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, "node_modules/@firebase/messaging": { @@ -1278,6 +1274,16 @@ "@firebase/app-types": "0.x" } }, + "node_modules/@firebase/messaging/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "node_modules/@firebase/messaging/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1311,6 +1317,22 @@ "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==", "dev": true }, + "node_modules/@firebase/performance/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/performance/node_modules/@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "node_modules/@firebase/performance/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -1355,10 +1377,26 @@ "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==", "dev": true }, - "node_modules/@firebase/remote-config/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "node_modules/@firebase/remote-config/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/remote-config/node_modules/@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, + "node_modules/@firebase/remote-config/node_modules/@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", "dev": true, "dependencies": { "tslib": "^1.11.1" @@ -1380,7 +1418,17 @@ "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/storage-types": { + "node_modules/@firebase/storage/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/@firebase/storage/node_modules/@firebase/storage-types": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz", "integrity": "sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==", @@ -1400,21 +1448,19 @@ } }, "node_modules/@firebase/util": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.4.1.tgz", - "integrity": "sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", "dev": true, - "peer": true, "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@firebase/util/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true, - "peer": true + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, "node_modules/@firebase/webchannel-wrapper": { "version": "0.4.0", @@ -1428,81 +1474,17 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "optional": true }, - "node_modules/@google-cloud/common": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", - "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", - "dev": true, - "optional": true, - "dependencies": { - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", - "arrify": "^2.0.1", - "duplexify": "^4.1.1", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^6.1.1", - "retry-request": "^4.1.1", - "teeny-request": "^7.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/common/node_modules/google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "dev": true, - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/common/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dev": true, - "optional": true, - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@google-cloud/common/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==", - "dev": true, - "optional": true, - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, "node_modules/@google-cloud/firestore": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.8.0.tgz", - "integrity": "sha512-cBPo7QQG+aUhS7AIr6fDlA9KIX0/U26rKZyL2K/L68LArDQzgBk1/xOiMoflHRNDQARwCQ0PAZmw8V8CXg7vTg==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", "dev": true, "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.9.2" + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" }, "engines": { "node": ">=10.10.0" @@ -1583,24 +1565,6 @@ "node": ">=12.0.0" } }, - "node_modules/@google-cloud/pubsub/node_modules/@grpc/proto-loader": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.12.tgz", - "integrity": "sha512-filTVbETFnxb9CyRX98zN18ilChTuf/C5scZ2xyaOTp0EHGq0/ufX8rjqXUcSb1Gpv7eZq4M2jDvbh9BogKnrg==", - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.2.0" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@google-cloud/pubsub/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1748,48 +1712,51 @@ } }, "node_modules/@google-cloud/storage": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.7.0.tgz", - "integrity": "sha512-6nPTylNaYWsVo5yHDdjQfUSh9qP/DFwahhyvOAf9CSDKfeoOys8+PAyHsoKyL29uyYoC6ymws7uJDO48y/SzBA==", + "version": "5.20.5", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", + "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", "dev": true, "optional": true, "dependencies": { - "@google-cloud/common": "^3.5.0", - "@google-cloud/paginator": "^3.0.0", + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", "arrify": "^2.0.0", + "async-retry": "^1.3.3", "compressible": "^2.0.12", - "date-and-time": "^0.14.0", + "configstore": "^5.0.0", "duplexify": "^4.0.0", + "ent": "^2.2.0", "extend": "^3.0.2", "gaxios": "^4.0.0", - "gcs-resumable-upload": "^3.1.0", - "get-stream": "^6.0.0", + "google-auth-library": "^7.14.1", "hash-stream-validation": "^0.2.2", - "mime": "^2.2.0", + "mime": "^3.0.0", "mime-types": "^2.0.8", - "onetime": "^5.1.0", "p-limit": "^3.0.1", "pumpify": "^2.0.0", - "snakeize": "^0.1.0", - "stream-events": "^1.0.1", + "retry-request": "^4.2.2", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "uuid": "^8.0.0", "xdg-basedir": "^4.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/@google-cloud/storage/node_modules/get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, "optional": true, - "engines": { - "node": ">=10" + "bin": { + "mime": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10.0.0" } }, "node_modules/@google-cloud/storage/node_modules/p-limit": { @@ -1829,15 +1796,15 @@ "node": "^8.13.0 || >=10.10.0" } }, - "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", - "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.10.0", + "protobufjs": "^6.11.3", "yargs": "^16.2.0" }, "bin": { @@ -1847,19 +1814,6 @@ "node": ">=6" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.1.tgz", - "integrity": "sha512-3y0FhacYAwWvyXshH18eDkUI40wT/uGio7MAegzY8lO5+wVsc19+1A7T0pPptae4kl7bdITL+0cHpnAPmryBjQ==", - "dev": true, - "dependencies": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", @@ -2624,6 +2578,16 @@ "@types/express": "*" } }, + "node_modules/@types/firebase": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/firebase/-/firebase-3.2.1.tgz", + "integrity": "sha512-G8XgHMu2jHlElfc2xVNaYP50F0qrqeTCjgeG1v5b4SRwWG4XKC4fCuEdVZuZaMRmVygcnbRZBAo9O7RsDvmkGQ==", + "deprecated": "This is a stub types definition for Firebase API (https://www.firebase.com/docs/javascript/firebase). Firebase API provides its own type definitions, so you don't need @types/firebase installed!", + "dev": true, + "dependencies": { + "firebase": "*" + } + }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -3875,6 +3839,16 @@ "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.3.2.tgz", "integrity": "sha512-phnXdS3RP7PPcmP6NWWzWMU0sLTeyvtZCxBPpZdkYE3seGLKSQZs9FrmVO/qwypq98FUtWWUEYxziLkdGk5nnA==" }, + "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==", + "dev": true, + "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", @@ -5199,13 +5173,6 @@ "node": ">= 6" } }, - "node_modules/date-and-time": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.2.tgz", - "integrity": "sha512-EFTCh9zRSEpGPmJaexg7HTuzZHh6cnJj1ui7IGCFNXzd2QdpsNh05Db5TF3xzJm30YN+A8/6xHSuRcQqoc3kFA==", - "dev": true, - "optional": true - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5378,18 +5345,6 @@ "kuler": "1.0.x" } }, - "node_modules/dicer": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz", - "integrity": "sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==", - "dev": true, - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -5563,7 +5518,7 @@ "node_modules/ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true, "optional": true }, @@ -6595,9 +6550,9 @@ } }, "node_modules/faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "dev": true, "dependencies": { "websocket-driver": ">=0.5.1" @@ -6749,63 +6704,58 @@ } }, "node_modules/firebase-admin": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", - "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.3.0.tgz", + "integrity": "sha512-A0wgMLEjyVyUE+heyMJYqHRkPVjpebhOYsa47RHdrTM4ltApcx8Tn86sUmjqxlfh09gNnILAm7a8q5+FmgBYpg==", "dev": true, "dependencies": { - "@firebase/database-compat": "^0.1.1", - "@firebase/database-types": "^0.7.2", + "@fastify/busboy": "^1.1.0", + "@firebase/database-compat": "^0.2.0", + "@firebase/database-types": "^0.9.7", "@types/node": ">=12.12.47", - "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", "jwks-rsa": "^2.0.2", - "node-forge": "^0.10.0" + "node-forge": "^1.3.1", + "uuid": "^8.3.2" }, "engines": { - "node": ">=10.13.0" + "node": ">=12.7.0" }, "optionalDependencies": { - "@google-cloud/firestore": "^4.5.0", - "@google-cloud/storage": "^5.3.0" + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3" } }, - "node_modules/firebase-admin/node_modules/@firebase/app-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", - "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==", - "dev": true - }, "node_modules/firebase-admin/node_modules/@firebase/database-types": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", - "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "dev": true, "dependencies": { - "@firebase/app-types": "0.6.3" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" } }, "node_modules/firebase-functions": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.24.0.tgz", - "integrity": "sha512-YKZm/AxjnWTP9VbxAyjs7ImWfMydleQAiHB2T6li3imRCcwC4+h6BXU/Jf2uELz9AkCb+UabWbdVrklk3b+70Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.1.0.tgz", + "integrity": "sha512-brbww5lGQVm8+d4KFmHF+O8wJBthws1NGXgphy7UDguMbUoW0fq6bL0NI442w+3nDE8IYUbnR4p3U8/cLAhnOA==", "dev": true, "dependencies": { "@types/cors": "^2.8.5", "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", - "lodash": "^4.17.14", "node-fetch": "^2.6.7" }, "bin": { "firebase-functions": "lib/bin/firebase-functions.js" }, "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=14.10.0" }, "peerDependencies": { - "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + "firebase-admin": "^10.0.0 || ^11.0.0" } }, "node_modules/firebase-functions/node_modules/@types/express": { @@ -6819,6 +6769,53 @@ "@types/serve-static": "*" } }, + "node_modules/firebase/node_modules/@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "dev": true + }, + "node_modules/firebase/node_modules/@firebase/auth-interop-types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", + "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", + "dev": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "0.x" + } + }, + "node_modules/firebase/node_modules/@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "dependencies": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "node_modules/firebase/node_modules/@firebase/database": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "dev": true, + "dependencies": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "faye-websocket": "0.11.3", + "tslib": "^1.11.1" + } + }, + "node_modules/firebase/node_modules/@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "node_modules/firebase/node_modules/@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -6828,6 +6825,18 @@ "tslib": "^1.11.1" } }, + "node_modules/firebase/node_modules/faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -7166,131 +7175,21 @@ "node": ">=8" } }, - "node_modules/gcs-resumable-upload": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz", - "integrity": "sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A==", + "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, - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "configstore": "^5.0.0", - "extend": "^3.0.2", - "gaxios": "^3.0.0", - "google-auth-library": "^6.0.0", - "pumpify": "^2.0.0", - "stream-events": "^1.0.4" - }, - "bin": { - "gcs-upload": "build/src/cli.js" - }, "engines": { - "node": ">=10" + "node": ">=6.9.0" } }, - "node_modules/gcs-resumable-upload/node_modules/gaxios": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", - "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", - "dev": true, - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.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==", "engines": { - "node": ">=10" - } - }, - "node_modules/gcs-resumable-upload/node_modules/google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "dev": true, - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gcs-resumable-upload/node_modules/google-auth-library/node_modules/gaxios": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", - "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", - "dev": true, - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gcs-resumable-upload/node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gcs-resumable-upload/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dev": true, - "optional": true, - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/gcs-resumable-upload/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==", - "dev": true, - "optional": true, - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "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, - "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==", - "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-func-name": { @@ -7574,26 +7473,6 @@ "node": ">=10" } }, - "node_modules/google-gax/node_modules/@grpc/proto-loader": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.12.tgz", - "integrity": "sha512-filTVbETFnxb9CyRX98zN18ilChTuf/C5scZ2xyaOTp0EHGq0/ufX8rjqXUcSb1Gpv7eZq4M2jDvbh9BogKnrg==", - "dev": true, - "optional": true, - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.2.0" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/google-p12-pem": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", @@ -7608,14 +7487,6 @@ "node": ">=10" } }, - "node_modules/google-p12-pem/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==", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/googleapis": { "version": "105.0.0", "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-105.0.0.tgz", @@ -7756,15 +7627,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/googleapis-common/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==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/googleapis-common/node_modules/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", @@ -7884,15 +7746,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/googleapis/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==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -8139,9 +7992,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "node_modules/http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", "dev": true }, "node_modules/http-proxy-agent": { @@ -10481,12 +10334,11 @@ } }, "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true, + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "engines": { - "node": ">= 6.0.0" + "node": ">= 6.13.0" } }, "node_modules/node-gyp": { @@ -12374,22 +12226,23 @@ } }, "node_modules/retry-request": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", - "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", "dev": true, "optional": true, "dependencies": { - "debug": "^4.1.1" + "debug": "^4.1.1", + "extend": "^3.0.2" }, "engines": { "node": ">=8.10.0" } }, "node_modules/retry-request/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "optional": true, "dependencies": { @@ -12827,13 +12680,6 @@ "npm": ">= 3.0.0" } }, - "node_modules/snakeize": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", - "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", - "dev": true, - "optional": true - }, "node_modules/socks": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", @@ -13072,15 +12918,6 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -13175,7 +13012,7 @@ "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "dev": true, "optional": true }, @@ -13780,13 +13617,13 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/teeny-request": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", - "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", "dev": true, "optional": true, "dependencies": { - "http-proxy-agent": "^4.0.0", + "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", @@ -13796,6 +13633,56 @@ "node": ">=10" } }, + "node_modules/teeny-request/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==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/teeny-request/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/teeny-request/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==", + "dev": true, + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + }, "node_modules/term-size": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", @@ -13849,6 +13736,12 @@ "node": ">=8" } }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==", + "dev": true + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -14729,12 +14622,12 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dev": true, "dependencies": { - "http-parser-js": ">=0.4.0 <0.4.11", + "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" }, @@ -14955,7 +14848,7 @@ "node_modules/xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", "dev": true, "engines": { "node": ">=0.4.0" @@ -15602,6 +15495,15 @@ "integrity": "sha512-W98NvvOe/Med3o66xTO03pd7a2omZebH79PV64gSE+ceDdU8uxQhFTa7ISiD1kseyqyOrMyW5/MNdsGEU02i3Q==", "dev": true }, + "@fastify/busboy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", + "integrity": "sha512-Fv854f94v0CzIDllbY3i/0NJPNBRNLDawf3BTYVGCe9VrIIs3Wi7AFx24F9NzCxdf0wyx/x0Q9kEVnvDOPnlxA==", + "dev": true, + "requires": { + "text-decoding": "^1.0.0" + } + }, "@firebase/analytics": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.6.0.tgz", @@ -15616,6 +15518,22 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -15648,6 +15566,28 @@ "xmlhttprequest": "1.8.0" }, "dependencies": { + "@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "dev": true + }, + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -15659,58 +15599,32 @@ } } }, - "@firebase/app-compat": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.23.tgz", - "integrity": "sha512-c0QOhU2UVxZ7N5++nLQgKZ899ZC8+/ESa8VCzsQDwBw1T3MFAD1cG40KhB+CGtp/uYk/w6Jtk8k0xyZu6O2LOg==", + "@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==", + "dev": true + }, + "@firebase/auth": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.15.0.tgz", + "integrity": "sha512-IFuzhxS+HtOQl7+SZ/Mhaghy/zTU7CENsJFWbC16tv2wfLZbayKF5jYGdAU3VFLehgC8KjlcIWd10akc3XivfQ==", "dev": true, - "peer": true, "requires": { - "@firebase/app": "0.7.22", - "@firebase/component": "0.5.13", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.5.2", - "tslib": "^2.1.0" + "@firebase/auth-types": "0.10.1" }, "dependencies": { - "@firebase/app": { - "version": "0.7.22", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.22.tgz", - "integrity": "sha512-v3AXSCwAvZyIFzOGgPAYtzjltm1M9R4U4yqsIBPf5B4ryaT1EGK+3ETZUOckNl5y2YwdKRJVPDDore+B2xg0Ug==", - "dev": true, - "peer": true, - "requires": { - "@firebase/component": "0.5.13", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.5.2", - "tslib": "^2.1.0" - } - }, - "@firebase/component": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", - "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", - "dev": true, - "peer": true, - "requires": { - "@firebase/util": "1.5.2", - "tslib": "^2.1.0" - } - }, - "@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "@firebase/auth-types": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", + "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", "dev": true, - "peer": true, - "requires": { - "tslib": "^2.1.0" - } + "requires": {} }, "@firebase/util": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", - "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.4.1.tgz", + "integrity": "sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==", "dev": true, "peer": true, "requires": { @@ -15718,182 +15632,89 @@ } }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true, "peer": true } } }, - "@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", - "dev": true - }, - "@firebase/auth": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.15.0.tgz", - "integrity": "sha512-IFuzhxS+HtOQl7+SZ/Mhaghy/zTU7CENsJFWbC16tv2wfLZbayKF5jYGdAU3VFLehgC8KjlcIWd10akc3XivfQ==", - "dev": true, - "requires": { - "@firebase/auth-types": "0.10.1" - } - }, "@firebase/auth-interop-types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", - "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", - "dev": true, - "requires": {} - }, - "@firebase/auth-types": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", - "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", "dev": true, "requires": {} }, "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", "dev": true, "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true } } }, "@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", "dev": true, "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "faye-websocket": "0.11.3", - "tslib": "^1.11.1" + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true } } }, "@firebase/database-compat": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.5.tgz", - "integrity": "sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", "dev": true, "requires": { - "@firebase/component": "0.5.10", - "@firebase/database": "0.12.5", - "@firebase/database-types": "0.9.4", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" }, "dependencies": { - "@firebase/app-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", - "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==", - "dev": true - }, - "@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", - "dev": true, - "requires": {} - }, - "@firebase/component": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.10.tgz", - "integrity": "sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA==", - "dev": true, - "requires": { - "@firebase/util": "1.4.3", - "tslib": "^2.1.0" - } - }, - "@firebase/database": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.5.tgz", - "integrity": "sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg==", - "dev": true, - "requires": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.10", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.4.3", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, "@firebase/database-types": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.4.tgz", - "integrity": "sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "dev": true, "requires": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.4.3" - } - }, - "@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/util": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.3.tgz", - "integrity": "sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true } } @@ -15905,6 +15726,14 @@ "dev": true, "requires": { "@firebase/app-types": "0.6.1" + }, + "dependencies": { + "@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "dev": true + } } }, "@firebase/firestore": { @@ -15924,6 +15753,22 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -15933,6 +15778,16 @@ "tslib": "^1.11.1" } }, + "@grpc/proto-loader": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz", + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", + "dev": true, + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -15961,6 +15816,25 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + } + }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -15988,6 +15862,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -16007,10 +15891,21 @@ "requires": {} }, "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", - "dev": true + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } }, "@firebase/messaging": { "version": "0.7.1", @@ -16026,6 +15921,16 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -16058,6 +15963,22 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -16100,6 +16021,22 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -16129,6 +16066,23 @@ "tslib": "^1.11.1" }, "dependencies": { + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/storage-types": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz", + "integrity": "sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==", + "dev": true, + "requires": {} + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -16140,115 +16094,46 @@ } } }, - "@firebase/storage-types": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz", - "integrity": "sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==", - "dev": true, - "requires": {} - }, "@firebase/util": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.4.1.tgz", - "integrity": "sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", "dev": true, - "peer": true, "requires": { "tslib": "^2.1.0" }, "dependencies": { "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true, - "peer": true + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true } } }, - "@firebase/webchannel-wrapper": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", - "integrity": "sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==", - "dev": true - }, - "@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "optional": true - }, - "@google-cloud/common": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", - "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", - "dev": true, - "optional": true, - "requires": { - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", - "arrify": "^2.0.1", - "duplexify": "^4.1.1", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^6.1.1", - "retry-request": "^4.1.1", - "teeny-request": "^7.0.0" - }, - "dependencies": { - "google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "dev": true, - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dev": true, - "optional": true, - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "dev": true, - "optional": true, - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - } - } + "@firebase/webchannel-wrapper": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", + "integrity": "sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==", + "dev": true + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true }, "@google-cloud/firestore": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.8.0.tgz", - "integrity": "sha512-cBPo7QQG+aUhS7AIr6fDlA9KIX0/U26rKZyL2K/L68LArDQzgBk1/xOiMoflHRNDQARwCQ0PAZmw8V8CXg7vTg==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", "dev": true, "optional": true, "requires": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.9.2" + "google-gax": "^2.24.1", + "protobufjs": "^6.8.6" } }, "@google-cloud/paginator": { @@ -16308,18 +16193,6 @@ "extend": "^3.0.2" } }, - "@grpc/proto-loader": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.12.tgz", - "integrity": "sha512-filTVbETFnxb9CyRX98zN18ilChTuf/C5scZ2xyaOTp0EHGq0/ufX8rjqXUcSb1Gpv7eZq4M2jDvbh9BogKnrg==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.2.0" - } - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -16434,38 +16307,41 @@ } }, "@google-cloud/storage": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.7.0.tgz", - "integrity": "sha512-6nPTylNaYWsVo5yHDdjQfUSh9qP/DFwahhyvOAf9CSDKfeoOys8+PAyHsoKyL29uyYoC6ymws7uJDO48y/SzBA==", + "version": "5.20.5", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", + "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", "dev": true, "optional": true, "requires": { - "@google-cloud/common": "^3.5.0", - "@google-cloud/paginator": "^3.0.0", + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^2.0.0", "@google-cloud/promisify": "^2.0.0", + "abort-controller": "^3.0.0", "arrify": "^2.0.0", + "async-retry": "^1.3.3", "compressible": "^2.0.12", - "date-and-time": "^0.14.0", + "configstore": "^5.0.0", "duplexify": "^4.0.0", + "ent": "^2.2.0", "extend": "^3.0.2", "gaxios": "^4.0.0", - "gcs-resumable-upload": "^3.1.0", - "get-stream": "^6.0.0", + "google-auth-library": "^7.14.1", "hash-stream-validation": "^0.2.2", - "mime": "^2.2.0", + "mime": "^3.0.0", "mime-types": "^2.0.8", - "onetime": "^5.1.0", "p-limit": "^3.0.1", "pumpify": "^2.0.0", - "snakeize": "^0.1.0", - "stream-events": "^1.0.1", + "retry-request": "^4.2.2", + "stream-events": "^1.0.4", + "teeny-request": "^7.1.3", + "uuid": "^8.0.0", "xdg-basedir": "^4.0.0" }, "dependencies": { - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, "optional": true }, @@ -16494,30 +16370,18 @@ "requires": { "@grpc/proto-loader": "^0.6.4", "@types/node": ">=12.12.47" - }, - "dependencies": { - "@grpc/proto-loader": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", - "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.2.0" - } - } } }, "@grpc/proto-loader": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.1.tgz", - "integrity": "sha512-3y0FhacYAwWvyXshH18eDkUI40wT/uGio7MAegzY8lO5+wVsc19+1A7T0pPptae4kl7bdITL+0cHpnAPmryBjQ==", - "dev": true, + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", "requires": { + "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" } }, "@humanwhocodes/config-array": { @@ -17156,6 +17020,15 @@ "@types/express": "*" } }, + "@types/firebase": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/firebase/-/firebase-3.2.1.tgz", + "integrity": "sha512-G8XgHMu2jHlElfc2xVNaYP50F0qrqeTCjgeG1v5b4SRwWG4XKC4fCuEdVZuZaMRmVygcnbRZBAo9O7RsDvmkGQ==", + "dev": true, + "requires": { + "firebase": "*" + } + }, "@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -18180,6 +18053,16 @@ "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.3.2.tgz", "integrity": "sha512-phnXdS3RP7PPcmP6NWWzWMU0sLTeyvtZCxBPpZdkYE3seGLKSQZs9FrmVO/qwypq98FUtWWUEYxziLkdGk5nnA==" }, + "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==", + "dev": true, + "optional": true, + "requires": { + "retry": "0.13.1" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -19170,13 +19053,6 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" }, - "date-and-time": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.2.tgz", - "integrity": "sha512-EFTCh9zRSEpGPmJaexg7HTuzZHh6cnJj1ui7IGCFNXzd2QdpsNh05Db5TF3xzJm30YN+A8/6xHSuRcQqoc3kFA==", - "dev": true, - "optional": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -19321,15 +19197,6 @@ "kuler": "1.0.x" } }, - "dicer": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz", - "integrity": "sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==", - "dev": true, - "requires": { - "streamsearch": "^1.1.0" - } - }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -19486,7 +19353,7 @@ "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true, "optional": true }, @@ -20249,9 +20116,9 @@ } }, "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "dev": true, "requires": { "websocket-driver": ">=0.5.1" @@ -20363,6 +20230,50 @@ "@firebase/util": "0.3.2" }, "dependencies": { + "@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "dev": true + }, + "@firebase/auth-interop-types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", + "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", + "dev": true, + "requires": {} + }, + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/database": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", + "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "dev": true, + "requires": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "faye-websocket": "0.11.3", + "tslib": "^1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, "@firebase/util": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", @@ -20371,54 +20282,58 @@ "requires": { "tslib": "^1.11.1" } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } } } }, "firebase-admin": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", - "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.3.0.tgz", + "integrity": "sha512-A0wgMLEjyVyUE+heyMJYqHRkPVjpebhOYsa47RHdrTM4ltApcx8Tn86sUmjqxlfh09gNnILAm7a8q5+FmgBYpg==", "dev": true, "requires": { - "@firebase/database-compat": "^0.1.1", - "@firebase/database-types": "^0.7.2", - "@google-cloud/firestore": "^4.5.0", - "@google-cloud/storage": "^5.3.0", + "@fastify/busboy": "^1.1.0", + "@firebase/database-compat": "^0.2.0", + "@firebase/database-types": "^0.9.7", + "@google-cloud/firestore": "^4.15.1", + "@google-cloud/storage": "^5.18.3", "@types/node": ">=12.12.47", - "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", "jwks-rsa": "^2.0.2", - "node-forge": "^0.10.0" + "node-forge": "^1.3.1", + "uuid": "^8.3.2" }, "dependencies": { - "@firebase/app-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", - "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==", - "dev": true - }, "@firebase/database-types": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", - "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "dev": true, "requires": { - "@firebase/app-types": "0.6.3" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" } } } }, "firebase-functions": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.24.0.tgz", - "integrity": "sha512-YKZm/AxjnWTP9VbxAyjs7ImWfMydleQAiHB2T6li3imRCcwC4+h6BXU/Jf2uELz9AkCb+UabWbdVrklk3b+70Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.1.0.tgz", + "integrity": "sha512-brbww5lGQVm8+d4KFmHF+O8wJBthws1NGXgphy7UDguMbUoW0fq6bL0NI442w+3nDE8IYUbnR4p3U8/cLAhnOA==", "dev": true, "requires": { "@types/cors": "^2.8.5", "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", - "lodash": "^4.17.14", "node-fetch": "^2.6.7" }, "dependencies": { @@ -20696,102 +20611,6 @@ } } }, - "gcs-resumable-upload": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz", - "integrity": "sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A==", - "dev": true, - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "configstore": "^5.0.0", - "extend": "^3.0.2", - "gaxios": "^3.0.0", - "google-auth-library": "^6.0.0", - "pumpify": "^2.0.0", - "stream-events": "^1.0.4" - }, - "dependencies": { - "gaxios": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", - "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", - "dev": true, - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - }, - "google-auth-library": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", - "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", - "dev": true, - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "dependencies": { - "gaxios": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", - "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", - "dev": true, - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.1" - } - } - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "optional": true - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dev": true, - "optional": true, - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "dev": true, - "optional": true, - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - } - } - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -21031,22 +20850,6 @@ "proto3-json-serializer": "^0.1.8", "protobufjs": "6.11.3", "retry-request": "^4.0.0" - }, - "dependencies": { - "@grpc/proto-loader": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.12.tgz", - "integrity": "sha512-filTVbETFnxb9CyRX98zN18ilChTuf/C5scZ2xyaOTp0EHGq0/ufX8rjqXUcSb1Gpv7eZq4M2jDvbh9BogKnrg==", - "dev": true, - "optional": true, - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.2.0" - } - } } }, "google-p12-pem": { @@ -21055,13 +20858,6 @@ "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", "requires": { "node-forge": "^1.0.0" - }, - "dependencies": { - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" - } } }, "googleapis": { @@ -21159,12 +20955,6 @@ "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } - }, - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true } } }, @@ -21268,12 +21058,6 @@ "safe-buffer": "^5.0.1" } }, - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true - }, "uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", @@ -21482,9 +21266,9 @@ } }, "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", "dev": true }, "http-proxy-agent": { @@ -23296,10 +23080,9 @@ } }, "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, "node-gyp": { "version": "9.1.0", @@ -24753,19 +24536,20 @@ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" }, "retry-request": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", - "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", "dev": true, "optional": true, "requires": { - "debug": "^4.1.1" + "debug": "^4.1.1", + "extend": "^3.0.2" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "optional": true, "requires": { @@ -25120,13 +24904,6 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, - "snakeize": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", - "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", - "dev": true, - "optional": true - }, "socks": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", @@ -25324,12 +25101,6 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, - "streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "dev": true - }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -25404,7 +25175,7 @@ "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "dev": true, "optional": true }, @@ -25861,17 +25632,55 @@ } }, "teeny-request": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", - "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", + "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", "dev": true, "optional": true, "requires": { - "http-proxy-agent": "^4.0.0", + "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", "uuid": "^8.0.0" + }, + "dependencies": { + "@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==", + "dev": true, + "optional": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "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==", + "dev": true, + "optional": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } } }, "term-size": { @@ -25914,6 +25723,12 @@ "minimatch": "^3.0.4" } }, + "text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==", + "dev": true + }, "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -26524,12 +26339,12 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dev": true, "requires": { - "http-parser-js": ">=0.4.0 <0.4.11", + "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } @@ -26701,7 +26516,7 @@ "xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", "dev": true }, "xregexp": { diff --git a/package.json b/package.json index 274da8d6943..c7d22aaeb87 100644 --- a/package.json +++ b/package.json @@ -171,6 +171,7 @@ "@types/cross-spawn": "^6.0.1", "@types/express": "^4.17.0", "@types/express-serve-static-core": "^4.17.8", + "@types/firebase": "^3.2.1", "@types/fs-extra": "^9.0.13", "@types/glob": "^7.1.1", "@types/inquirer": "^8.1.3", @@ -215,8 +216,8 @@ "eslint-plugin-jsdoc": "^39.2.9", "eslint-plugin-prettier": "^4.0.0", "firebase": "^7.24.0", - "firebase-admin": "^9.4.2", - "firebase-functions": "^3.23.0", + "firebase-admin": "^10.0.0", + "firebase-functions": "^4.1.0", "google-discovery-to-swagger": "^2.1.0", "googleapis": "^105.0.0", "mocha": "^9.1.3", diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index 2c41ff599e2..7c473bf4efc 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -8,7 +8,7 @@ import { ChildProcess } from "child_process"; import * as express from "express"; import { Change } from "firebase-functions"; -import { DocumentSnapshot } from "firebase-functions/lib/providers/firestore"; +import { DocumentSnapshot } from "firebase-functions/v1/firestore"; import { FunctionRuntimeBundles, TIMEOUT_LONG, MODULE_ROOT } from "./fixtures"; import { diff --git a/scripts/functions-deploy-tests/functions/fns.js b/scripts/functions-deploy-tests/functions/fns.js index cdbad3c3c25..d5c746331ca 100644 --- a/scripts/functions-deploy-tests/functions/fns.js +++ b/scripts/functions-deploy-tests/functions/fns.js @@ -42,3 +42,6 @@ export const v2tq = v2.tasks.onTaskDispatched(v2TqOpts, () => {}); // export const v2custom = v2.eventarc.onCustomEventPublished("custom.event", () => {}); export const v2secret = v2.pubsub.onMessagePublished({ topic: "foo", secrets: ["TOP"] }, () => {}); export const v2scheduled = v2.scheduler.onSchedule(v2ScheduleOpts, () => {}); +export const v2testlab = v2.testLab.onTestMatrixCompleted(() => {}); +export const v2rc = v2.remoteConfig.onConfigUpdated(() => {}); +export const v2perf = v2.alerts.performance.onThresholdAlertPublished(() => {}); diff --git a/scripts/functions-deploy-tests/functions/package.json b/scripts/functions-deploy-tests/functions/package.json index d000995efab..affb998fa58 100644 --- a/scripts/functions-deploy-tests/functions/package.json +++ b/scripts/functions-deploy-tests/functions/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "firebase-admin": "^11.0.0", - "firebase-functions": "^3.23.0" + "firebase-functions": "^4.1.0" }, "engines": { "node": "16" diff --git a/scripts/functions-deploy-tests/tests.ts b/scripts/functions-deploy-tests/tests.ts index 2f18b924aa9..cd36095d575 100644 --- a/scripts/functions-deploy-tests/tests.ts +++ b/scripts/functions-deploy-tests/tests.ts @@ -15,7 +15,7 @@ import { requireAuth } from "../../src/requireAuth"; const FIREBASE_PROJECT = process.env.GCLOUD_PROJECT || ""; const FIREBASE_DEBUG = process.env.FIREBASE_DEBUG || ""; const FUNCTIONS_DIR = path.join(__dirname, "functions"); -const FNS_COUNT = 17; +const FNS_COUNT = 20; function genRandomId(n = 10): string { const charset = "abcdefghijklmnopqrstuvwxyz"; @@ -143,6 +143,7 @@ describe("firebase deploy", function (this) { memory: "128MB", maxInstances: 42, timeoutSeconds: 42, + preserveExternalChanges: true, }, v2Opts: { memory: "128MiB", @@ -150,6 +151,7 @@ describe("firebase deploy", function (this) { timeoutSeconds: 42, cpu: 2, concurrency: 42, + preserveExternalChanges: true, }, v1TqOpts: { retryConfig: { @@ -260,10 +262,10 @@ describe("firebase deploy", function (this) { } }); - it("skips duplicate deploys functions with runtime options", async () => { + it("skips duplicate deploys functions with runtime options when preserveExternalChanges is set", async () => { const opts: Opts = { - v1Opts: {}, - v2Opts: {}, + v1Opts: { preserveExternalChanges: true }, + v2Opts: { preserveExternalChanges: true }, v1TqOpts: {}, v2TqOpts: {}, v1IdpOpts: {}, @@ -279,10 +281,10 @@ describe("firebase deploy", function (this) { expect(result2.stdout, "deploy result").to.match(/Skipped \(No changes detected\)/); }); - it("leaves existing options when unspecified", async () => { + it("leaves existing options when unspecified and preserveExternalChanges is set", async () => { const opts: Opts = { - v1Opts: {}, - v2Opts: {}, + v1Opts: { preserveExternalChanges: true }, + v2Opts: { preserveExternalChanges: true }, v1TqOpts: {}, v2TqOpts: {}, v1IdpOpts: {}, @@ -352,65 +354,16 @@ describe("firebase deploy", function (this) { // BUGBUG: Setting options to null SHOULD restore their values to default, but this isn't correctly implemented in // the CLI. - it.skip("restores default values if options are explicitly cleared out", async () => { + it.skip("restores default values when unspecified and preserveExternalChanges is not set", async () => { const opts: Opts = { - v1Opts: { - memory: undefined, - maxInstances: undefined, - timeoutSeconds: undefined, - }, - v2Opts: { - memory: undefined, - maxInstances: undefined, - timeoutSeconds: undefined, - cpu: undefined, - concurrency: undefined, - }, - v1TqOpts: { - retryConfig: { - maxAttempts: undefined, - maxRetrySeconds: undefined, - maxBackoffSeconds: undefined, - maxDoublings: undefined, - minBackoffSeconds: undefined, - }, - rateLimits: { - maxDispatchesPerSecond: undefined, - maxConcurrentDispatches: undefined, - }, - }, - v2TqOpts: { - retryConfig: { - maxAttempts: undefined, - maxRetrySeconds: undefined, - maxBackoffSeconds: undefined, - maxDoublings: undefined, - minBackoffSeconds: undefined, - }, - rateLimits: { - maxDispatchesPerSecond: undefined, - maxConcurrentDispatches: undefined, - }, - }, - v1IdpOpts: { - blockingOptions: {}, - }, + v1Opts: {}, + v2Opts: {}, + v1TqOpts: {}, + v2TqOpts: {}, + v1IdpOpts: { blockingOptions: {} }, v2IdpOpts: {}, - v1ScheduleOpts: { - retryCount: undefined, - maxDoublings: undefined, - maxBackoffDuration: undefined, - maxRetryDuration: undefined, - minBackoffDuration: undefined, - }, - v2ScheduleOpts: { - schedule: "every 30 minutes", - retryCount: undefined, - maxDoublings: undefined, - maxBackoffSeconds: undefined, - maxRetrySeconds: undefined, - minBackoffSeconds: undefined, - }, + v1ScheduleOpts: {}, + v2ScheduleOpts: { schedule: "every 30 minutes" }, }; const result = await setOptsAndDeploy(opts); From e1872a4298cc1cbfe60551027dc645f4070ba477 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Fri, 2 Dec 2022 12:13:59 -0500 Subject: [PATCH 0702/1699] Add region warning for emulated database functions (#5143) * adding error message * add changelog entry --- CHANGELOG.md | 1 + src/emulator/functionsEmulator.ts | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d018fea5c2..543bbc97096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ - Fix bug where disabling background triggers did nothing. (#5221) - Fix bug in auth emulator where empty string should throw invalid email instead of missing email. (#3898) - Fix bug in auth emulator in which createdAt was not set for signInWithIdp new users. (#5203) +- Add region warning for emulated database functions (#5143) - Default to --no-localhost when calling login from Google Cloud Workstations - Support the x-goog-api-key header in auth emulator. (#5249) - Fix bug in deploying web frameworks when a predeploy hook was configured in firebase.json (#5199) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index fb47df888fd..296e0015d08 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -589,6 +589,7 @@ export class FunctionsEmulator implements EmulatorInstance { case Constants.SERVICE_REALTIME_DATABASE: added = await this.addRealtimeDatabaseTrigger( this.args.projectId, + definition.id, key, definition.eventTrigger, signature, @@ -745,6 +746,7 @@ export class FunctionsEmulator implements EmulatorInstance { private getV2DatabaseApiAttributes( projectId: string, + id: string, key: string, eventTrigger: EventTrigger, region: string @@ -760,6 +762,15 @@ export class FunctionsEmulator implements EmulatorInstance { throw new FirebaseError("A database reference must be supplied."); } + // TODO(colerogers): yank/change if RTDB emulator ever supports multiple regions + if (region !== "us-central1") { + this.logger.logLabeled( + "WARN", + `functions[${id}]`, + `function region is defined outside the database region, will not trigger.` + ); + } + // The 'namespacePattern' determines that we are using the v2 interface const bundle = JSON.stringify({ name: `projects/${projectId}/locations/${region}/triggers/${key}`, @@ -777,6 +788,7 @@ export class FunctionsEmulator implements EmulatorInstance { async addRealtimeDatabaseTrigger( projectId: string, + id: string, key: string, eventTrigger: EventTrigger, signature: SignatureType, @@ -788,7 +800,7 @@ export class FunctionsEmulator implements EmulatorInstance { const { bundle, apiPath, instance } = signature === "cloudevent" - ? this.getV2DatabaseApiAttributes(projectId, key, eventTrigger, region) + ? this.getV2DatabaseApiAttributes(projectId, id, key, eventTrigger, region) : this.getV1DatabaseApiAttributes(projectId, key, eventTrigger); logger.debug(`addRealtimeDatabaseTrigger[${instance}]`, JSON.stringify(bundle)); From 1ebb9f84e35f016bf55497c8832d156985477310 Mon Sep 17 00:00:00 2001 From: Yuangwang Date: Mon, 5 Dec 2022 10:37:47 -0500 Subject: [PATCH 0703/1699] Fix storage integration tests (#5287) * turn back on integration tests * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test * test --- .github/workflows/node-test.yml | 3 +- npm-shrinkwrap.json | 254 +++++++++++++++++++++++--------- package.json | 2 +- 3 files changed, 186 insertions(+), 73 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index f276fe2381f..5e72bc66f0f 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -89,8 +89,7 @@ jobs: # - npm run test:hosting-rewrites # Long-running test that might conflict across test runs. Run this manually. - npm run test:import-export - npm run test:storage-deploy - # Temporarily disable broken storage emulator integration test. - # - npm run test:storage-emulator-integration + - npm run test:storage-emulator-integration - npm run test:triggers-end-to-end - npm run test:triggers-end-to-end:inspect steps: diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index cac6a02c3f3..503e5045da1 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -147,7 +147,7 @@ "openapi-merge": "^1.0.23", "prettier": "^2.5.1", "proxy": "^1.0.2", - "puppeteer": "^9.0.0", + "puppeteer": "^19.0.0", "sinon": "^9.2.3", "sinon-chai": "^3.6.0", "source-map-support": "^0.5.9", @@ -2755,6 +2755,12 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "node_modules/@types/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/progress/-/progress-2.0.3.tgz", @@ -3022,9 +3028,9 @@ } }, "node_modules/@types/yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, "dependencies": { @@ -5029,6 +5035,22 @@ "node": ">= 0.10" } }, + "node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -5089,6 +5111,15 @@ "node": ">=4.8" } }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5320,9 +5351,9 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "node_modules/devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "version": "0.0.1056733", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1056733.tgz", + "integrity": "sha512-CmTu6SQx2g3TbZzDCAV58+LTxVdKplS7xip0g5oDXpZ+isr0rv5dDP8ToyVRywzPHkCCPKgKgScEcwz4uPWDIA==", "dev": true }, "node_modules/dezalgo": { @@ -6435,9 +6466,9 @@ } }, "node_modules/extract-zip/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -6564,7 +6595,7 @@ "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, "dependencies": { "pend": "~1.2.0" @@ -8052,9 +8083,9 @@ "dev": true }, "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "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==", "dependencies": { "agent-base": "6", "debug": "4" @@ -11282,7 +11313,7 @@ "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, "node_modules/performance-now": { @@ -11729,33 +11760,48 @@ } }, "node_modules/puppeteer": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.0.0.tgz", - "integrity": "sha512-Avu8SKWQRC1JKNMgfpH7d4KzzHOL/A65jRYrjNU46hxnOYGwqe4zZp/JW8qulaH0Pnbm5qyO3EbSKvqBUlfvkg==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.3.0.tgz", + "integrity": "sha512-WJbi/ULaeuFOz7cfMgJlJCBAZiyqIFeQ6os4h5ex3PVTt2qosXgwI9eruFZqFAwJRv8x5pOuMhWR0aSRgyDqEg==", "dev": true, "hasInstallScript": true, "dependencies": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" + "cosmiconfig": "7.0.1", + "devtools-protocol": "0.0.1056733", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "puppeteer-core": "19.3.0" }, "engines": { - "node": ">=10.18.1" + "node": ">=14.1.0" } }, - "node_modules/puppeteer/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "node_modules/puppeteer-core": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.3.0.tgz", + "integrity": "sha512-P8VAAOBnBJo/7DKJnj1b0K9kZBF2D8lkdL94CjJ+DZKCp182LQqYemPI9omUSZkh4bgykzXjZhaVR1qtddTTQg==", + "dev": true, + "dependencies": { + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1056733", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.10.0" + }, + "engines": { + "node": ">=14.1.0" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -11769,12 +11815,33 @@ } } }, - "node_modules/puppeteer/node_modules/ms": { + "node_modules/puppeteer-core/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz", + "integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -14965,7 +15032,7 @@ "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, "dependencies": { "buffer-crc32": "~0.2.3", @@ -17197,6 +17264,12 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/progress/-/progress-2.0.3.tgz", @@ -17463,9 +17536,9 @@ } }, "@types/yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, "requires": { @@ -18948,6 +19021,19 @@ "vary": "^1" } }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, "crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -18991,6 +19077,15 @@ } } }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -19172,9 +19267,9 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "version": "0.0.1056733", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1056733.tgz", + "integrity": "sha512-CmTu6SQx2g3TbZzDCAV58+LTxVdKplS7xip0g5oDXpZ+isr0rv5dDP8ToyVRywzPHkCCPKgKgScEcwz4uPWDIA==", "dev": true }, "dezalgo": { @@ -20017,9 +20112,9 @@ }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -20127,7 +20222,7 @@ "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, "requires": { "pend": "~1.2.0" @@ -21313,9 +21408,9 @@ "dev": true }, "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "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==", "requires": { "agent-base": "6", "debug": "4" @@ -23807,7 +23902,7 @@ "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, "performance-now": { @@ -24162,29 +24257,41 @@ } }, "puppeteer": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.0.0.tgz", - "integrity": "sha512-Avu8SKWQRC1JKNMgfpH7d4KzzHOL/A65jRYrjNU46hxnOYGwqe4zZp/JW8qulaH0Pnbm5qyO3EbSKvqBUlfvkg==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.3.0.tgz", + "integrity": "sha512-WJbi/ULaeuFOz7cfMgJlJCBAZiyqIFeQ6os4h5ex3PVTt2qosXgwI9eruFZqFAwJRv8x5pOuMhWR0aSRgyDqEg==", "dev": true, "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" + "cosmiconfig": "7.0.1", + "devtools-protocol": "0.0.1056733", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "puppeteer-core": "19.3.0" + } + }, + "puppeteer-core": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.3.0.tgz", + "integrity": "sha512-P8VAAOBnBJo/7DKJnj1b0K9kZBF2D8lkdL94CjJ+DZKCp182LQqYemPI9omUSZkh4bgykzXjZhaVR1qtddTTQg==", + "dev": true, + "requires": { + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1056733", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.10.0" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -24195,6 +24302,13 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "ws": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz", + "integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==", + "dev": true, + "requires": {} } } }, @@ -26601,7 +26715,7 @@ "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, "requires": { "buffer-crc32": "~0.2.3", diff --git a/package.json b/package.json index c7d22aaeb87..c13a1334c23 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "openapi-merge": "^1.0.23", "prettier": "^2.5.1", "proxy": "^1.0.2", - "puppeteer": "^9.0.0", + "puppeteer": "^19.0.0", "sinon": "^9.2.3", "sinon-chai": "^3.6.0", "source-map-support": "^0.5.9", From ab11ced04a75353e17c1138177f8295e33082752 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Mon, 5 Dec 2022 16:10:09 -0500 Subject: [PATCH 0704/1699] Fix eventarc emulator (#5304) Convert events from proto to JSON format before delegating to the trigger function. The eventarc admin SDK sends events in proto format: https://github.com/firebase/firebase-admin-node/blob/9fc8e84b8f496f12141e611bf3075e94f633c117/src/eventarc/eventarc-client-internal.ts#L96 --- src/emulator/eventarcEmulator.ts | 3 +- src/emulator/eventarcEmulatorUtils.ts | 62 +++++++++ .../emulators/eventarcEmulatorUtils.spec.ts | 126 ++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/emulator/eventarcEmulatorUtils.ts create mode 100644 src/test/emulators/eventarcEmulatorUtils.spec.ts diff --git a/src/emulator/eventarcEmulator.ts b/src/emulator/eventarcEmulator.ts index e08f9c358c5..b1f487a8914 100644 --- a/src/emulator/eventarcEmulator.ts +++ b/src/emulator/eventarcEmulator.ts @@ -8,6 +8,7 @@ import { EventTrigger } from "./functionsEmulatorShared"; import { CloudEvent } from "./events/types"; import { EmulatorRegistry } from "./registry"; import { FirebaseError } from "../error"; +import { cloudEventFromProtoToJson } from "./eventarcEmulatorUtils"; interface CustomEventTrigger { projectId: string; @@ -123,7 +124,7 @@ export class EventarcEmulator implements EmulatorInstance { .request, NodeJS.ReadableStream>({ method: "POST", path: `/functions/projects/${trigger.projectId}/triggers/${trigger.triggerName}`, - body: JSON.stringify(event), + body: JSON.stringify(cloudEventFromProtoToJson(event)), responseType: "stream", resolveOnHTTPError: true, }) diff --git a/src/emulator/eventarcEmulatorUtils.ts b/src/emulator/eventarcEmulatorUtils.ts new file mode 100644 index 00000000000..3cc06f5a539 --- /dev/null +++ b/src/emulator/eventarcEmulatorUtils.ts @@ -0,0 +1,62 @@ +import { CloudEvent } from "./events/types"; +import { FirebaseError } from "../error"; + +const BUILT_IN_ATTRS: string[] = ["time", "datacontenttype", "subject"]; + +export function cloudEventFromProtoToJson(ce: any): CloudEvent { + if (ce["id"] === undefined) { + throw new FirebaseError("CloudEvent 'id' is required."); + } + if (ce["type"] === undefined) { + throw new FirebaseError("CloudEvent 'type' is required."); + } + if (ce["specVersion"] === undefined) { + throw new FirebaseError("CloudEvent 'specVersion' is required."); + } + if (ce["source"] === undefined) { + throw new FirebaseError("CloudEvent 'source' is required."); + } + const out: CloudEvent = { + id: ce["id"], + type: ce["type"], + specversion: ce["specVersion"], + source: ce["source"], + subject: getOptionalAttribute(ce, "subject", "ceString"), + time: getRequiredAttribute(ce, "time", "ceTimestamp"), + data: getData(ce), + datacontenttype: getRequiredAttribute(ce, "datacontenttype", "ceString"), + }; + for (const attr in ce["attributes"]) { + if (BUILT_IN_ATTRS.includes(attr)) { + continue; + } + out[attr] = getRequiredAttribute(ce, attr, "ceString"); + } + return out; +} + +function getOptionalAttribute(ce: any, attr: string, type: string): string | undefined { + return ce["attributes"][attr][type]; +} + +function getRequiredAttribute(ce: any, attr: string, type: string): string { + const val = ce["attributes"][attr][type]; + if (val === undefined) { + throw new FirebaseError("CloudEvent must contain " + attr + " attribute"); + } + return val; +} + +function getData(ce: any): any { + const contentType = getRequiredAttribute(ce, "datacontenttype", "ceString"); + switch (contentType) { + case "application/json": + return JSON.parse(ce["textData"]); + case "text/plain": + return ce["textData"]; + case undefined: + return undefined; + default: + throw new FirebaseError("Unsupported content type: " + contentType); + } +} diff --git a/src/test/emulators/eventarcEmulatorUtils.spec.ts b/src/test/emulators/eventarcEmulatorUtils.spec.ts new file mode 100644 index 00000000000..d7015a11829 --- /dev/null +++ b/src/test/emulators/eventarcEmulatorUtils.spec.ts @@ -0,0 +1,126 @@ +import { expect } from "chai"; + +import { cloudEventFromProtoToJson } from "../../emulator/eventarcEmulatorUtils"; + +describe("eventarcEmulatorUtils", () => { + describe("cloudEventFromProtoToJson", () => { + it("converts cloud event from proto format", () => { + expect( + cloudEventFromProtoToJson({ + "@type": "type.googleapis.com/io.cloudevents.v1.CloudEvent", + attributes: { + customattr: { + ceString: "custom value", + }, + datacontenttype: { + ceString: "application/json", + }, + time: { + ceTimestamp: "2022-03-16T20:20:42.212Z", + }, + subject: { + ceString: "context", + }, + }, + id: "user-provided-id", + source: "/my/functions", + specVersion: "1.0", + textData: '{"hello":"world"}', + type: "some.custom.event", + }) + ).to.deep.eq({ + type: "some.custom.event", + specversion: "1.0", + subject: "context", + datacontenttype: "application/json", + id: "user-provided-id", + data: { + hello: "world", + }, + source: "/my/functions", + time: "2022-03-16T20:20:42.212Z", + customattr: "custom value", + }); + }); + + it("throws invalid argument when source not set", () => { + expect(() => + cloudEventFromProtoToJson({ + "@type": "type.googleapis.com/io.cloudevents.v1.CloudEvent", + attributes: { + customattr: { + ceString: "custom value", + }, + datacontenttype: { + ceString: "application/json", + }, + time: { + ceTimestamp: "2022-03-16T20:20:42.212Z", + }, + subject: { + ceString: "context", + }, + }, + id: "user-provided-id", + specVersion: "1.0", + textData: '{"hello":"world"}', + type: "some.custom.event", + }) + ).throws("CloudEvent 'source' is required."); + }); + + it("populates converts object data to JSON and sets datacontenttype", () => { + const got = cloudEventFromProtoToJson({ + "@type": "type.googleapis.com/io.cloudevents.v1.CloudEvent", + attributes: { + customattr: { + ceString: "custom value", + }, + datacontenttype: { + ceString: "application/json", + }, + time: { + ceTimestamp: "2022-03-16T20:20:42.212Z", + }, + subject: { + ceString: "context", + }, + }, + id: "user-provided-id", + source: "/my/functions", + specVersion: "1.0", + textData: '{"hello":"world"}', + type: "some.custom.event", + }); + expect(got.datacontenttype).to.deep.eq("application/json"); + expect(got.data).to.deep.eq({ hello: "world" }); + }); + + it("populates string data and sets datacontenttype", () => { + const got = cloudEventFromProtoToJson({ + "@type": "type.googleapis.com/io.cloudevents.v1.CloudEvent", + attributes: { + customattr: { + ceString: "custom value", + }, + datacontenttype: { + ceString: "text/plain", + }, + time: { + ceTimestamp: "2022-03-16T20:20:42.212Z", + }, + subject: { + ceString: "context", + }, + }, + id: "user-provided-id", + source: "/my/functions", + specVersion: "1.0", + textData: "hello world", + type: "some.custom.event", + }); + expect(got.datacontenttype).to.deep.eq("text/plain"); + expect(got.data).to.eq("hello world"); + }); + }); +}); From 57129783400a89a757c88e49012acbde2cb49fa7 Mon Sep 17 00:00:00 2001 From: kazuwombat Date: Tue, 6 Dec 2022 08:02:49 +0900 Subject: [PATCH 0705/1699] Add return type of findAvailableLogFile (#5189) Co-authored-by: Bryan Kendall --- src/bin/firebase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/firebase.ts b/src/bin/firebase.ts index f46fbe7c731..155b6d80b43 100755 --- a/src/bin/firebase.ts +++ b/src/bin/firebase.ts @@ -38,7 +38,7 @@ import * as winston from "winston"; let args = process.argv.slice(2); let cmd: Command; -function findAvailableLogFile() { +function findAvailableLogFile(): string { const candidates = ["firebase-debug.log"]; for (let i = 1; i < 10; i++) { candidates.push(`firebase-debug.${i}.log`); From 4c9214904bb3eb79edb21380e1dc11a6259ab1c8 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 6 Dec 2022 10:27:56 -0800 Subject: [PATCH 0706/1699] Improve handling of bad versions during ext:install (#5305) * Improve handling of bad versions during ext:install * Add changelog * Better variable names --- CHANGELOG.md | 1 + src/deploy/extensions/planner.ts | 1 + src/extensions/extensionsHelper.ts | 16 ++++++++++------ src/test/deploy/extensions/planner.spec.ts | 17 ++++++++--------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 543bbc97096..80f64a9fe86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ - Support the x-goog-api-key header in auth emulator. (#5249) - Fix bug in deploying web frameworks when a predeploy hook was configured in firebase.json (#5199) - Fix bug where function deployments using --only filter sometimes failed deployments. (#5280) +- Fix bug where `ext:install` would sometimes fail if no version was specified. (#5305) diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index c48d04224e7..4aa473370f8 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -215,6 +215,7 @@ export async function resolveVersion(ref: refs.Ref): Promise { } if (!ref.version || ref.version === "latest") { return versions + .filter((ev) => ev.spec.version !== undefined) .map((ev) => ev.spec.version) .sort(semver.compare) .pop()!; diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 1fa1e1dad7e..11b15b3e69d 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -833,14 +833,18 @@ export async function diagnoseAndFixProject(options: any): Promise { * 1. Infer firebase publisher if not provided * 2. Infer "latest" as the version if not provided */ -export async function canonicalizeRefInput(extensionName: string): Promise { - // Infer firebase if publisher ID not provided. - if (extensionName.split("/").length < 2) { - const [extensionID, version] = extensionName.split("@"); - extensionName = `firebase/${extensionID}@${version || "latest"}`; +export async function canonicalizeRefInput(refInput: string): Promise { + let inferredRef = refInput; + // Infer 'firebase' if publisher ID not provided. + if (refInput.split("/").length < 2) { + inferredRef = `firebase/${inferredRef}`; + } + // Infer 'latest' if no version provided. + if (refInput.split("@").length < 2) { + inferredRef = `${inferredRef}@latest`; } // Get the correct version for a given extension reference from the Registry API. - const ref = refs.parse(extensionName); + const ref = refs.parse(inferredRef); ref.version = await resolveVersion(ref); return refs.toExtensionVersionRef(ref); } diff --git a/src/test/deploy/extensions/planner.spec.ts b/src/test/deploy/extensions/planner.spec.ts index 2b151945422..1d4aef34917 100644 --- a/src/test/deploy/extensions/planner.spec.ts +++ b/src/test/deploy/extensions/planner.spec.ts @@ -3,9 +3,9 @@ import * as sinon from "sinon"; import * as planner from "../../../deploy/extensions/planner"; import * as extensionsApi from "../../../extensions/extensionsApi"; -import { ExtensionInstance, ExtensionVersion } from "../../../extensions/types"; +import { ExtensionInstance } from "../../../extensions/types"; -function extensionVersion(version: string): ExtensionVersion { +function extensionVersion(version?: string): any { return { name: `publishers/test/extensions/test/versions/${version}`, ref: `test/test@${version}`, @@ -26,13 +26,12 @@ describe("Extensions Deployment Planner", () => { let listExtensionVersionsStub: sinon.SinonStub; before(() => { - listExtensionVersionsStub = sinon - .stub(extensionsApi, "listExtensionVersions") - .resolves([ - extensionVersion("0.1.0"), - extensionVersion("0.1.1"), - extensionVersion("0.2.0"), - ]); + listExtensionVersionsStub = sinon.stub(extensionsApi, "listExtensionVersions").resolves([ + extensionVersion("0.1.0"), + extensionVersion("0.1.1"), + extensionVersion("0.2.0"), + extensionVersion(), // Explicitly test that this doesn't break on bad data + ]); }); after(() => { From ff9497ee14a0631e22ef54a48e8908c89bed6a58 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Tue, 6 Dec 2022 15:46:27 -0500 Subject: [PATCH 0707/1699] Added support for CF3v2 functions in the Extensions emulator (#5306) --- src/emulator/extensions/validation.ts | 2 + src/extensions/billingMigrationHelper.ts | 3 +- src/extensions/displayExtensionInfo.ts | 7 +- src/extensions/emulator/specHelper.ts | 6 +- src/extensions/emulator/triggerHelper.ts | 101 ++++++++++++++---- src/extensions/extensionsHelper.ts | 3 - src/extensions/types.ts | 34 +++++- src/extensions/utils.ts | 23 +++- .../emulators/extensions/validation.spec.ts | 7 ++ .../extensions/emulator/triggerHelper.spec.ts | 89 +++++++++++++++ 10 files changed, 242 insertions(+), 33 deletions(-) diff --git a/src/emulator/extensions/validation.ts b/src/emulator/extensions/validation.ts index 158ba0232e2..0a7d84d3528 100644 --- a/src/emulator/extensions/validation.ts +++ b/src/emulator/extensions/validation.ts @@ -80,6 +80,8 @@ export function checkForUnemulatedTriggerTypes( return !shouldStart(options, Emulators.AUTH); case Constants.SERVICE_STORAGE: return !shouldStart(options, Emulators.STORAGE); + case Constants.SERVICE_EVENTARC: + return !shouldStart(options, Emulators.EVENTARC); default: return true; } diff --git a/src/extensions/billingMigrationHelper.ts b/src/extensions/billingMigrationHelper.ts index 16c7d1a0ef4..6152861e1b2 100644 --- a/src/extensions/billingMigrationHelper.ts +++ b/src/extensions/billingMigrationHelper.ts @@ -7,6 +7,7 @@ import { ExtensionSpec } from "./types"; import { logPrefix } from "./extensionsHelper"; import { promptOnce } from "../prompt"; import * as utils from "../utils"; +import { getResourceRuntime } from "./utils"; marked.setOptions({ renderer: new TerminalRenderer(), @@ -41,7 +42,7 @@ function hasRuntime(spec: ExtensionSpec, runtime: string): boolean { const specVersion = spec.specVersion || defaultSpecVersion; const defaultRuntime = defaultRuntimes[specVersion]; const resources = spec.resources || []; - return resources.some((r) => runtime === (r.properties?.runtime || defaultRuntime)); + return resources.some((r) => runtime === (getResourceRuntime(r) || defaultRuntime)); } /** diff --git a/src/extensions/displayExtensionInfo.ts b/src/extensions/displayExtensionInfo.ts index f996762cca7..381d03b548b 100644 --- a/src/extensions/displayExtensionInfo.ts +++ b/src/extensions/displayExtensionInfo.ts @@ -7,7 +7,7 @@ import * as utils from "../utils"; import { logPrefix } from "./extensionsHelper"; import { logger } from "../logger"; import { FirebaseError } from "../error"; -import { Api, ExtensionSpec, Role, Resource } from "./types"; +import { Api, ExtensionSpec, Role, Resource, FUNCTIONS_RESOURCE_TYPE } from "./types"; import * as iam from "../gcp/iam"; import { SECRET_ROLE, usesSecrets } from "./secretsUtils"; @@ -113,7 +113,10 @@ function displayApis(apis: Api[]): string { } function usesTasks(spec: ExtensionSpec): boolean { - return spec.resources.some((r: Resource) => r.properties?.taskQueueTrigger !== undefined); + return spec.resources.some( + (r: Resource) => + r.type === FUNCTIONS_RESOURCE_TYPE && r.properties?.taskQueueTrigger !== undefined + ); } function impliedRoles(spec: ExtensionSpec): Role[] { diff --git a/src/extensions/emulator/specHelper.ts b/src/extensions/emulator/specHelper.ts index f7eda1891c3..139a1c2c60e 100644 --- a/src/extensions/emulator/specHelper.ts +++ b/src/extensions/emulator/specHelper.ts @@ -5,12 +5,14 @@ import * as fs from "fs-extra"; import { ExtensionSpec, Resource } from "../types"; import { FirebaseError } from "../../error"; import { substituteParams } from "../extensionsHelper"; +import { getResourceRuntime } from "../utils"; import { parseRuntimeVersion } from "../../emulator/functionsEmulatorUtils"; const SPEC_FILE = "extension.yaml"; const POSTINSTALL_FILE = "POSTINSTALL.md"; const validFunctionTypes = [ "firebaseextensions.v1beta.function", + "firebaseextensions.v1beta.v2function", "firebaseextensions.v1beta.scheduledFunction", ]; @@ -95,8 +97,8 @@ export function getFunctionProperties(resources: Resource[]) { export function getNodeVersion(resources: Resource[]): number { const invalidRuntimes: string[] = []; const versions = resources.map((r: Resource) => { - if (r.properties?.runtime) { - const runtimeName = r.properties?.runtime as string; + if (getResourceRuntime(r)) { + const runtimeName = getResourceRuntime(r) as string; const runtime = parseRuntimeVersion(runtimeName); if (!runtime) { invalidRuntimes.push(runtimeName); diff --git a/src/extensions/emulator/triggerHelper.ts b/src/extensions/emulator/triggerHelper.ts index 914ef71ac00..192d117208f 100644 --- a/src/extensions/emulator/triggerHelper.ts +++ b/src/extensions/emulator/triggerHelper.ts @@ -4,8 +4,14 @@ import { } from "../../emulator/functionsEmulatorShared"; import { EmulatorLogger } from "../../emulator/emulatorLogger"; import { Emulators } from "../../emulator/types"; -import { Resource } from "../../extensions/types"; +import { + Resource, + FUNCTIONS_RESOURCE_TYPE, + FUNCTIONS_V2_RESOURCE_TYPE, +} from "../../extensions/types"; +import * as backend from "../../deploy/functions/backend"; import * as proto from "../../gcp/proto"; +import { FirebaseError } from "../../error"; /** * Convert a Resource into a ParsedTriggerDefinition @@ -13,29 +19,78 @@ import * as proto from "../../gcp/proto"; export function functionResourceToEmulatedTriggerDefintion( resource: Resource ): ParsedTriggerDefinition { - const etd: ParsedTriggerDefinition = { - name: resource.name, - entryPoint: resource.name, - platform: "gcfv1", - }; - const properties = resource.properties || {}; - proto.convertIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration); - proto.convertIfPresent(etd, properties, "regions", "location", (str: string) => [str]); - proto.copyIfPresent(etd, properties, "availableMemoryMb"); - if (properties.httpsTrigger) { - etd.httpsTrigger = properties.httpsTrigger; + const resourceType = resource.type; + if (resource.type === FUNCTIONS_RESOURCE_TYPE) { + const etd: ParsedTriggerDefinition = { + name: resource.name, + entryPoint: resource.name, + platform: "gcfv1", + }; + const properties = resource.properties || {}; + proto.convertIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration); + proto.convertIfPresent(etd, properties, "regions", "location", (str: string) => [str]); + proto.copyIfPresent(etd, properties, "availableMemoryMb"); + if (properties.httpsTrigger) { + etd.httpsTrigger = properties.httpsTrigger; + } + if (properties.eventTrigger) { + etd.eventTrigger = { + eventType: properties.eventTrigger.eventType, + resource: properties.eventTrigger.resource, + service: getServiceFromEventType(properties.eventTrigger.eventType), + }; + } else { + EmulatorLogger.forEmulator(Emulators.FUNCTIONS).log( + "WARN", + `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.` + ); + } + return etd; } - if (properties.eventTrigger) { - etd.eventTrigger = { - eventType: properties.eventTrigger.eventType, - resource: properties.eventTrigger.resource, - service: getServiceFromEventType(properties.eventTrigger.eventType), + if (resource.type === FUNCTIONS_V2_RESOURCE_TYPE) { + const etd: ParsedTriggerDefinition = { + name: resource.name, + entryPoint: resource.name, + platform: "gcfv2", }; - } else { - EmulatorLogger.forEmulator(Emulators.FUNCTIONS).log( - "WARN", - `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.` - ); + const properties = resource.properties || {}; + proto.convertIfPresent(etd, properties, "regions", "location", (str: string) => [str]); + if (properties.serviceConfig) { + proto.copyIfPresent(etd, properties.serviceConfig, "timeoutSeconds"); + proto.convertIfPresent( + etd, + properties.serviceConfig, + "availableMemoryMb", + "availableMemory", + (mem: string) => parseInt(mem) as backend.MemoryOptions + ); + } + if (properties.eventTrigger) { + etd.eventTrigger = { + eventType: properties.eventTrigger.eventType, + service: getServiceFromEventType(properties.eventTrigger.eventType), + }; + proto.copyIfPresent(etd.eventTrigger, properties.eventTrigger, "channel"); + if (properties.eventTrigger.eventFilters) { + const eventFilters: Record = {}; + const eventFilterPathPatterns: Record = {}; + for (const filter of properties.eventTrigger.eventFilters) { + if (filter.operator === undefined) { + eventFilters[filter.attribute] = filter.value; + } else if (filter.operator === "match-path-pattern") { + eventFilterPathPatterns[filter.attribute] = filter.value; + } + } + etd.eventTrigger.eventFilters = eventFilters; + etd.eventTrigger.eventFilterPathPatterns = eventFilterPathPatterns; + } + } else { + EmulatorLogger.forEmulator(Emulators.FUNCTIONS).log( + "WARN", + `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.` + ); + } + return etd; } - return etd; + throw new FirebaseError("Unexpected resource type " + resourceType); } diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 11b15b3e69d..51f66fe0359 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -85,9 +85,6 @@ export const AUTOPOULATED_PARAM_PLACEHOLDERS = { DATABASE_INSTANCE: "project-id-default-rtdb", DATABASE_URL: "https://project-id-default-rtdb.firebaseio.com", }; -export const resourceTypeToNiceName: Record = { - "firebaseextensions.v1beta.function": "Cloud Function", -}; export type ReleaseStage = "stable" | "alpha" | "beta" | "rc"; /** diff --git a/src/extensions/types.ts b/src/extensions/types.ts index 82abaeb524f..10396365836 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -160,9 +160,41 @@ export interface FunctionResourceProperties { }; } +export const FUNCTIONS_V2_RESOURCE_TYPE = "firebaseextensions.v1beta.v2function"; +export interface FunctionV2ResourceProperties { + type: typeof FUNCTIONS_V2_RESOURCE_TYPE; + properties?: { + location?: string; + sourceDirectory?: string; + buildConfig?: { + runtime?: Runtime; + }; + serviceConfig?: { + availableMemory?: string; + timeoutSeconds?: number; + minInstanceCount?: number; + maxInstanceCount?: number; + }; + eventTrigger?: { + eventType: string; + triggerRegion?: string; + channel?: string; + pubsubTopic?: string; + retryPolicy?: string; + eventFilters?: FunctionV2EventFilter[]; + }; + }; +} + +export interface FunctionV2EventFilter { + attribute: string; + value: string; + operator?: string; +} + // Union of all valid property types so we can have a strongly typed "property" // field depending on the actual value of "type" -type ResourceProperties = FunctionResourceProperties; +type ResourceProperties = FunctionResourceProperties | FunctionV2ResourceProperties; export type Resource = ResourceProperties & { name: string; diff --git a/src/extensions/utils.ts b/src/extensions/utils.ts index a656cefb8ed..7ffc31526b6 100644 --- a/src/extensions/utils.ts +++ b/src/extensions/utils.ts @@ -1,5 +1,10 @@ import { promptOnce } from "../prompt"; -import { ParamOption } from "./types"; +import { + ParamOption, + Resource, + FUNCTIONS_RESOURCE_TYPE, + FUNCTIONS_V2_RESOURCE_TYPE, +} from "./types"; import { RegistryEntry } from "./resolveSource"; // Modified version of the once function from prompt, to return as a joined string. @@ -67,3 +72,19 @@ export function formatTimestamp(timestamp: string): string { const withoutMs = timestamp.split(".")[0]; return withoutMs.replace("T", " "); } + +/** + * Returns the runtime for the resource. The resource may be v1 or v2 function, + * etc, and this utility will do its best to identify the runtime specified for + * this resource. + */ +export function getResourceRuntime(resource: Resource): string | undefined { + switch (resource.type) { + case FUNCTIONS_RESOURCE_TYPE: + return resource.properties?.runtime; + case FUNCTIONS_V2_RESOURCE_TYPE: + return resource.properties?.buildConfig?.runtime; + default: + return undefined; + } +} diff --git a/src/test/emulators/extensions/validation.spec.ts b/src/test/emulators/extensions/validation.spec.ts index c19e7972c08..8be9e7eebd4 100644 --- a/src/test/emulators/extensions/validation.spec.ts +++ b/src/test/emulators/extensions/validation.spec.ts @@ -144,6 +144,7 @@ describe("ExtensionsEmulator validation", () => { const shouldStartStub = sandbox.stub(controller, "shouldStart"); shouldStartStub.withArgs(sinon.match.any, Emulators.STORAGE).returns(true); shouldStartStub.withArgs(sinon.match.any, Emulators.DATABASE).returns(true); + shouldStartStub.withArgs(sinon.match.any, Emulators.EVENTARC).returns(true); shouldStartStub.withArgs(sinon.match.any, Emulators.FIRESTORE).returns(false); shouldStartStub.withArgs(sinon.match.any, Emulators.AUTH).returns(false); }); @@ -220,6 +221,12 @@ describe("ExtensionsEmulator validation", () => { eventType: "providers/google.firebase.database/eventTypes/ref.write", }, }), + getTestParsedTriggerDefinition({ + eventTrigger: { + eventType: "test.custom.event", + channel: "projects/foo/locations/us-central1/channels/firebase", + }, + }), ], want: [], }, diff --git a/src/test/extensions/emulator/triggerHelper.spec.ts b/src/test/extensions/emulator/triggerHelper.spec.ts index 6b392a83466..b2fb3d90ffa 100644 --- a/src/test/extensions/emulator/triggerHelper.spec.ts +++ b/src/test/extensions/emulator/triggerHelper.spec.ts @@ -134,5 +134,94 @@ describe("triggerHelper", () => { expect(result).to.eql(expected); }); + + it("should handle v2 custom event triggers", () => { + const testResource: Resource = { + name: "test-resource", + entryPoint: "functionName", + type: "firebaseextensions.v1beta.v2function", + properties: { + eventTrigger: { + eventType: "test.custom.event", + channel: "projects/foo/locations/bar/channels/baz", + }, + }, + }; + const expected = { + platform: "gcfv2", + entryPoint: "test-resource", + name: "test-resource", + eventTrigger: { + service: "", + channel: "projects/foo/locations/bar/channels/baz", + eventType: "test.custom.event", + }, + }; + + const result = triggerHelper.functionResourceToEmulatedTriggerDefintion(testResource); + + expect(result).to.eql(expected); + }); + + it("should handle fully packed v2 triggers", () => { + const testResource: Resource = { + name: "test-resource", + entryPoint: "functionName", + type: "firebaseextensions.v1beta.v2function", + properties: { + buildConfig: { + runtime: "node16", + }, + location: "us-cental1", + serviceConfig: { + availableMemory: "100MB", + minInstanceCount: 1, + maxInstanceCount: 10, + timeoutSeconds: 66, + }, + eventTrigger: { + eventType: "test.custom.event", + channel: "projects/foo/locations/bar/channels/baz", + pubsubTopic: "pubsub.topic", + eventFilters: [ + { + attribute: "basic", + value: "attr", + }, + { + attribute: "mattern", + value: "patch", + operator: "match-path-pattern", + }, + ], + retryPolicy: "RETRY", + triggerRegion: "us-cental1", + }, + }, + }; + const expected = { + platform: "gcfv2", + entryPoint: "test-resource", + name: "test-resource", + availableMemoryMb: 100, + timeoutSeconds: 66, + eventTrigger: { + service: "", + channel: "projects/foo/locations/bar/channels/baz", + eventType: "test.custom.event", + eventFilters: { + basic: "attr", + }, + eventFilterPathPatterns: { + mattern: "patch", + }, + }, + regions: ["us-cental1"], + }; + + const result = triggerHelper.functionResourceToEmulatedTriggerDefintion(testResource); + + expect(result).to.eql(expected); + }); }); }); From 78c70b253fab222235eb9e063c435aefa6996a0f Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 6 Dec 2022 13:32:09 -0800 Subject: [PATCH 0708/1699] [firebase-release] Removed change log and reset repo after 11.17.0 release --- CHANGELOG.md | 10 +--------- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80f64a9fe86..8b137891791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1 @@ -- Fix bug where disabling background triggers did nothing. (#5221) -- Fix bug in auth emulator where empty string should throw invalid email instead of missing email. (#3898) -- Fix bug in auth emulator in which createdAt was not set for signInWithIdp new users. (#5203) -- Add region warning for emulated database functions (#5143) -- Default to --no-localhost when calling login from Google Cloud Workstations -- Support the x-goog-api-key header in auth emulator. (#5249) -- Fix bug in deploying web frameworks when a predeploy hook was configured in firebase.json (#5199) -- Fix bug where function deployments using --only filter sometimes failed deployments. (#5280) -- Fix bug where `ext:install` would sometimes fail if no version was specified. (#5305) + diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 503e5045da1..80682016da2 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.16.1", + "version": "11.17.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.16.1", + "version": "11.17.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index c13a1334c23..51d0349eb30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.16.1", + "version": "11.17.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From f83fc6e544273826d205cce489fd55f31870deb6 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 6 Dec 2022 13:40:10 -0800 Subject: [PATCH 0709/1699] [firebase-release] Removed change log and reset repo after 11.17.0 release, fixed formatting --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b137891791..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ - From 27def0a80fce714b0e64aaca7303bd685ce808f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Fabricio=20Urbina=20Gonz=C3=A1lez?= Date: Tue, 6 Dec 2022 17:15:50 -0600 Subject: [PATCH 0710/1699] Add support for Firestore TTL (#5267) * Add support for Firestore TTL * Add support for Firestore TTL * Remove unnecessary TTL check * Remove unnecessary TTL check * Add support for Firestore TTL --- CHANGELOG.md | 1 + src/firestore/README.md | 3 + src/firestore/indexes-api.ts | 14 +++ src/firestore/indexes-sort.ts | 8 ++ src/firestore/indexes-spec.ts | 1 + src/firestore/indexes.ts | 44 +++++++-- src/firestore/util.ts | 7 ++ src/firestore/validator.ts | 10 ++ src/test/firestore/indexes.spec.ts | 148 +++++++++++++++++++++++++++++ 9 files changed, 230 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..237eed9fca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Add support for Firestore TTL (#5267) diff --git a/src/firestore/README.md b/src/firestore/README.md index 882c08504ab..bdc86baed8f 100644 --- a/src/firestore/README.md +++ b/src/firestore/README.md @@ -61,9 +61,12 @@ The schema for one object in the `fieldOverrides` array is as follows. Optional Note that Cloud Firestore document fields can only be indexed in one [mode](https://firebase.google.com/docs/firestore/query-data/index-overview#index_modes), thus a field object cannot contain both the `order` and `arrayConfig` properties. +For more information about time-to-live (TTL) policies review the [official documention](https://cloud.google.com/firestore/docs/ttl). + ```javascript collectionGroup: string // Labeled "Collection ID" in the Firebase console fieldPath: string + ttl?: boolean // Set specified field to have TTL policy and be eligible for deletion indexes: array // Set empty array to disable indexes on this collectionGroup + fieldPath queryScope: string // One of "COLLECTION", "COLLECTION_GROUP" order?: string // One of "ASCENDING", "DESCENDING"; excludes arrayConfig property diff --git a/src/firestore/indexes-api.ts b/src/firestore/indexes-api.ts index 39f3556130e..d8127f7a75d 100644 --- a/src/firestore/indexes-api.ts +++ b/src/firestore/indexes-api.ts @@ -30,6 +30,12 @@ export enum State { NEEDS_REPAIR = "NEEDS_REPAIR", } +export enum StateTtl { + CREATING = "CREATING", + ACTIVE = "ACTIVE", + NEEDS_REPAIR = "NEEDS_REPAIR", +} + /** * An Index as it is represented in the Firestore v1beta2 indexes API. */ @@ -49,6 +55,13 @@ export interface IndexField { arrayConfig?: ArrayConfig; } +/** + * TTL policy configuration for a field + */ +export interface TtlConfig { + state: StateTtl; +} + /** * Represents a single field in the database. * @@ -58,6 +71,7 @@ export interface IndexField { export interface Field { name: string; indexConfig: IndexConfig; + ttlConfig?: TtlConfig; } /** diff --git a/src/firestore/indexes-sort.ts b/src/firestore/indexes-sort.ts index be2179bbe3a..8539eb002cf 100644 --- a/src/firestore/indexes-sort.ts +++ b/src/firestore/indexes-sort.ts @@ -92,6 +92,7 @@ export function compareApiField(a: API.Field, b: API.Field): number { * Comparisons: * 1) The collection group. * 2) The field path. + * 3) The ttl. * 3) The list of indexes. */ export function compareFieldOverride(a: Spec.FieldOverride, b: Spec.FieldOverride): number { @@ -99,6 +100,13 @@ export function compareFieldOverride(a: Spec.FieldOverride, b: Spec.FieldOverrid return a.collectionGroup.localeCompare(b.collectionGroup); } + // The ttl override can be undefined, we only guarantee that true values will + // come last since those overrides should be executed after disabling TTL per collection. + const compareTtl = Number(!!a.ttl) - Number(!!b.ttl); + if (compareTtl) { + return compareTtl; + } + if (a.fieldPath !== b.fieldPath) { return a.fieldPath.localeCompare(b.fieldPath); } diff --git a/src/firestore/indexes-spec.ts b/src/firestore/indexes-spec.ts index a85ea6ee31b..6fb10f23f7e 100644 --- a/src/firestore/indexes-spec.ts +++ b/src/firestore/indexes-spec.ts @@ -21,6 +21,7 @@ export interface Index { export interface FieldOverride { collectionGroup: string; fieldPath: string; + ttl?: boolean; indexes: FieldIndex[]; } diff --git a/src/firestore/indexes.ts b/src/firestore/indexes.ts index c19774e03c9..711bd674c2b 100644 --- a/src/firestore/indexes.ts +++ b/src/firestore/indexes.ts @@ -140,7 +140,10 @@ export class FirestoreIndexes { } } - for (const field of fieldOverridesToDeploy) { + // Disabling TTL must be executed first in case another field is enabled for + // the same collection in the same deployment. + const sortedFieldOverridesToDeploy = fieldOverridesToDeploy.sort(sort.compareFieldOverride); + for (const field of sortedFieldOverridesToDeploy) { const exists = existingFieldOverrides.some((x) => this.fieldMatchesSpec(x, field)); if (exists) { logger.debug(`Skipping existing field override: ${JSON.stringify(field)}`); @@ -195,7 +198,7 @@ export class FirestoreIndexes { */ async listFieldOverrides(project: string): Promise { const parent = `projects/${project}/databases/(default)/collectionGroups/-`; - const url = `/${parent}/fields?filter=indexConfig.usesAncestorConfig=false`; + const url = `/${parent}/fields?filter=indexConfig.usesAncestorConfig=false OR ttlConfig:*`; const res = await this.apiClient.get<{ fields?: API.Field[] }>(url); const fields = res.body.fields; @@ -236,6 +239,7 @@ export class FirestoreIndexes { return { collectionGroup: parsedName.collectionGroupId, fieldPath: parsedName.fieldPath, + ttl: !!field.ttlConfig, indexes: fieldIndexes.map((index) => { const firstField = index.fields[0]; @@ -339,6 +343,10 @@ export class FirestoreIndexes { validator.assertHas(field, "fieldPath"); validator.assertHas(field, "indexes"); + if (typeof field.ttl !== "undefined") { + validator.assertType("ttl", field.ttl, "boolean"); + } + field.indexes.forEach((index: any) => { validator.assertHasOneOf(index, ["arrayConfig", "order"]); @@ -379,23 +387,33 @@ export class FirestoreIndexes { }; }); - const data = { + let data = { indexConfig: { indexes, }, }; - await this.apiClient.patch(url, data); + if (spec.ttl) { + data = Object.assign(data, { + ttlConfig: {}, + }); + } + + if (typeof spec.ttl !== "undefined") { + await this.apiClient.patch(url, data); + } else { + await this.apiClient.patch(url, data, { queryParams: { updateMask: "indexConfig" } }); + } } /** - * Delete an existing index on the specified project. + * Delete an existing field overrides on the specified project. */ deleteField(field: API.Field): Promise { const url = field.name; const data = {}; - return this.apiClient.patch(`/${url}`, data, { queryParams: { updateMask: "indexConfig" } }); + return this.apiClient.patch(`/${url}`, data); } /** @@ -471,6 +489,16 @@ export class FirestoreIndexes { return false; } + if (typeof spec.ttl !== "undefined" && util.booleanXOR(!!field.ttlConfig, spec.ttl)) { + return false; + } else if (!!field.ttlConfig && typeof spec.ttl === "undefined") { + utils.logLabeledBullet( + "firestore", + `there are TTL field overrides for collection ${spec.collectionGroup} defined in your project that are not present in your ` + + "firestore indexes file. The TTL policy won't be deleted since is not specified as false." + ); + } + const fieldIndexes = field.indexConfig.indexes || []; if (fieldIndexes.length !== spec.indexes.length) { return false; @@ -619,6 +647,10 @@ export class FirestoreIndexes { } else { result += " (no indexes)"; } + const fieldTtl = field.ttlConfig; + if (fieldTtl) { + result += ` TTL(${fieldTtl.state})`; + } return result; } diff --git a/src/firestore/util.ts b/src/firestore/util.ts index 669eb166307..d084246f4b6 100644 --- a/src/firestore/util.ts +++ b/src/firestore/util.ts @@ -55,3 +55,10 @@ export function parseFieldName(name: string): FieldName { fieldPath: m[3], }; } + +/** + * Performs XOR operator between two boolean values + */ +export function booleanXOR(a: boolean, b: boolean): boolean { + return !!(Number(a) - Number(b)); +} diff --git a/src/firestore/validator.ts b/src/firestore/validator.ts index 7647ab13318..87a3169c141 100644 --- a/src/firestore/validator.ts +++ b/src/firestore/validator.ts @@ -39,3 +39,13 @@ export function assertEnum(obj: any, prop: string, valid: any[]): void { throw new FirebaseError(`Field "${prop}" must be one of ${valid.join(", ")}: ${objString}`); } } + +/** + * Throw an error if the value of the property 'prop' differs against type + * guard. + */ +export function assertType(prop: string, propValue: any, type: string): void { + if (typeof propValue !== type) { + throw new FirebaseError(`Property "${prop}" must be of type ${type}`); + } +} diff --git a/src/test/firestore/indexes.spec.ts b/src/test/firestore/indexes.spec.ts index 8866678bfb2..ab8a899ddd3 100644 --- a/src/test/firestore/indexes.spec.ts +++ b/src/test/firestore/indexes.spec.ts @@ -200,6 +200,85 @@ describe("IndexSpecMatching", () => { expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(true); }); + it("should identify a positive field spec match with ttl specified as false", () => { + const apiField = { + name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123", + indexConfig: { + indexes: [ + { + queryScope: "COLLECTION", + fields: [{ fieldPath: "abc123", order: "ASCENDING" }], + }, + { + queryScope: "COLLECTION", + fields: [{ fieldPath: "abc123", arrayConfig: "CONTAINS" }], + }, + ], + }, + } as API.Field; + + const specField = { + collectionGroup: "collection", + fieldPath: "abc123", + ttl: false, + indexes: [ + { order: "ASCENDING", queryScope: "COLLECTION" }, + { arrayConfig: "CONTAINS", queryScope: "COLLECTION" }, + ], + } as Spec.FieldOverride; + + expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(true); + }); + + it("should identify a positive ttl field spec match", () => { + const apiField = { + name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/fieldTtl", + indexConfig: { + indexes: [ + { + queryScope: "COLLECTION", + fields: [{ fieldPath: "fieldTtl", order: "ASCENDING" }], + }, + ], + }, + ttlConfig: { + state: "ACTIVE", + }, + } as API.Field; + + const specField = { + collectionGroup: "collection", + fieldPath: "fieldTtl", + ttl: true, + indexes: [{ order: "ASCENDING", queryScope: "COLLECTION" }], + } as Spec.FieldOverride; + + expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(true); + }); + + it("should identify a negative ttl field spec match", () => { + const apiField = { + name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/fieldTtl", + indexConfig: { + indexes: [ + { + queryScope: "COLLECTION", + fields: [{ fieldPath: "fieldTtl", order: "ASCENDING" }], + }, + ], + }, + } as API.Field; + + const specField = { + collectionGroup: "collection", + fieldPath: "fieldTtl", + ttl: true, + indexes: [{ order: "ASCENDING", queryScope: "COLLECTION" }], + } as Spec.FieldOverride; + + expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(false); + }); + it("should match a field spec with all indexes excluded", () => { const apiField = { name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123", @@ -215,6 +294,25 @@ describe("IndexSpecMatching", () => { expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(true); }); + it("should match a field spec with only ttl", () => { + const apiField = { + name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/ttlField", + ttlConfig: { + state: "ACTIVE", + }, + indexConfig: {}, + } as API.Field; + + const specField = { + collectionGroup: "collection", + fieldPath: "ttlField", + ttl: true, + indexes: [], + } as Spec.FieldOverride; + + expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(true); + }); + it("should identify a negative field spec match", () => { const apiField = { name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123", @@ -244,6 +342,27 @@ describe("IndexSpecMatching", () => { // The second spec contains "DESCENDING" where the first contains "ASCENDING" expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(false); }); + + it("should identify a negative field spec match with ttl as false", () => { + const apiField = { + name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/fieldTtl", + ttlConfig: { + state: "ACTIVE", + }, + indexConfig: {}, + } as API.Field; + + const specField = { + collectionGroup: "collection", + fieldPath: "fieldTtl", + ttl: false, + indexes: [], + } as Spec.FieldOverride; + + // The second spec contains "false" for ttl where the first contains "true" + // for ttl + expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(false); + }); }); describe("IndexSorting", () => { @@ -360,6 +479,35 @@ describe("IndexSorting", () => { expect([b, a, d, c].sort(sort.compareFieldOverride)).to.eql([a, b, c, d]); }); + it("should sort ttl true to be last in an array of Spec field overrides", () => { + // Sorts first because of collectionGroup + const a: Spec.FieldOverride = { + collectionGroup: "collectionA", + fieldPath: "fieldA", + ttl: false, + indexes: [], + }; + const b: Spec.FieldOverride = { + collectionGroup: "collectionA", + fieldPath: "fieldB", + ttl: true, + indexes: [], + }; + const c: Spec.FieldOverride = { + collectionGroup: "collectionB", + fieldPath: "fieldA", + ttl: false, + indexes: [], + }; + const d: Spec.FieldOverride = { + collectionGroup: "collectionB", + fieldPath: "fieldB", + ttl: true, + indexes: [], + }; + expect([b, a, d, c].sort(sort.compareFieldOverride)).to.eql([a, b, c, d]); + }); + it("should correctly sort an array of API indexes", () => { // Sorts first because of collectionGroup const a: API.Index = { From 97ce5505af95ea9e3cffcdf8b2d460c68786cde7 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 7 Dec 2022 10:22:37 -0800 Subject: [PATCH 0711/1699] Load secrets when emulating functions with --inspect-function flag (#5308) * Load secrets when emulating functions with --inspect-function flag * add changeloag --- CHANGELOG.md | 1 + src/emulator/extensionsEmulator.ts | 2 +- src/emulator/functionsEmulator.ts | 84 +++++++++++-------- src/test/emulators/extensionsEmulator.spec.ts | 2 +- 4 files changed, 51 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 237eed9fca2..94af501af7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Add support for Firestore TTL (#5267) +- Fix bug where secrets were not loaded when emulating functions with `--inpsect-functions`. (#4605) diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 04233bfe57d..c1fd847d7dc 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -234,7 +234,7 @@ export class ExtensionsEmulator implements EmulatorInstance { const emulatableBackend: EmulatableBackend = { functionsDir, env: nonSecretEnv, - codebase: "", + codebase: instance.instanceId, // Give each extension its own codebase name so that they don't share workerPools. secretEnv: secretEnvVariables, predefinedTriggers: extensionTriggers, nodeMajorVersion: nodeMajorVersion, diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 296e0015d08..44ff3151ee3 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -653,10 +653,22 @@ export class FunctionsEmulator implements EmulatorInstance { this.logger.logLabeled("SUCCESS", `functions[${definition.id}]`, msg); } } - - // In debug mode, we eagerly start a runtime process to allow debuggers to attach + // In debug mode, we eagerly start the runtime processes to allow debuggers to attach // before invoking a function. if (this.args.debugPort) { + // Since we're about to start a runtime to be shared by all the functions in this codebase, + // we need to make sure it has all the secrets used by any function in the codebase. + emulatableBackend.secretEnv = Object.values( + toSetup.reduce( + (acc: Record, curr: EmulatedTriggerDefinition) => { + for (const secret of curr.secretEnvironmentVariables || []) { + acc[secret.key] = secret; + } + return acc; + }, + {} + ) + ); await this.startRuntime(emulatableBackend); } } @@ -1206,42 +1218,42 @@ export class FunctionsEmulator implements EmulatorInstance { ); } } + // Note - if trigger is undefined, we are loading in 'sequential' mode. + // In that case, we need to load all secrets for that codebase. + const secrets: backend.SecretEnvVar[] = + trigger?.secretEnvironmentVariables || backend.secretEnv; + const accesses = secrets + .filter((s) => !secretEnvs[s.key]) + .map(async (s) => { + this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.secret}@latest`); + const value = await accessSecretVersion( + this.getProjectId(), + s.secret, + s.version ?? "latest" + ); + return [s.key, value]; + }); + const accessResults = await allSettled(accesses); - if (trigger) { - const secrets: backend.SecretEnvVar[] = trigger.secretEnvironmentVariables || []; - const accesses = secrets - .filter((s) => !secretEnvs[s.key]) - .map(async (s) => { - this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.secret}@latest`); - const value = await accessSecretVersion( - this.getProjectId(), - s.secret, - s.version ?? "latest" - ); - return [s.key, value]; - }); - const accessResults = await allSettled(accesses); - - const errs: string[] = []; - for (const result of accessResults) { - if (result.status === "rejected") { - errs.push(result.reason as string); - } else { - const [k, v] = result.value; - secretEnvs[k] = v; - } + const errs: string[] = []; + for (const result of accessResults) { + if (result.status === "rejected") { + errs.push(result.reason as string); + } else { + const [k, v] = result.value; + secretEnvs[k] = v; } + } - if (errs.length > 0) { - this.logger.logLabeled( - "ERROR", - "functions", - "Unable to access secret environment variables from Google Cloud Secret Manager. " + - "Make sure the credential used for the Functions Emulator have access " + - `or provide override values in ${secretPath}:\n\t` + - errs.join("\n\t") - ); - } + if (errs.length > 0) { + this.logger.logLabeled( + "ERROR", + "functions", + "Unable to access secret environment variables from Google Cloud Secret Manager. " + + "Make sure the credential used for the Functions Emulator have access " + + `or provide override values in ${secretPath}:\n\t` + + errs.join("\n\t") + ); } return secretEnvs; @@ -1280,7 +1292,6 @@ export class FunctionsEmulator implements EmulatorInstance { "See https://yarnpkg.com/getting-started/migration#step-by-step for more information." ); } - const runtimeEnv = this.getRuntimeEnvs(backend, trigger); const secretEnvs = await this.resolveSecretEnvs(backend, trigger); const socketPath = getTemporarySocketPath(); @@ -1307,6 +1318,7 @@ export class FunctionsEmulator implements EmulatorInstance { instanceId: backend.extensionInstanceId, ref: backend.extensionVersion?.ref, }; + const pool = this.workerPools[backend.codebase]; const worker = pool.addWorker(trigger?.id, runtime, extensionLogInfo); await worker.waitForSocketReady(); diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts index 2a4ff08dfaa..59231fd3636 100644 --- a/src/test/emulators/extensionsEmulator.spec.ts +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -119,7 +119,7 @@ describe("Extensions Emulator", () => { ], extension: TEST_EXTENSION, extensionVersion: TEST_EXTENSION_VERSION, - codebase: "", + codebase: "ext-test", }, }, ]; From d7f0186256a213697bcf205600abcc6dab5159b2 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 8 Dec 2022 22:46:14 -0500 Subject: [PATCH 0712/1699] Warning for custom build scripts in project's package.json (#5240) Warn if the build command in the source directory's package.json contains anything other than the default build command. Direct users towards the Express.js / custom integration --- CHANGELOG.md | 1 + src/frameworks/angular/index.ts | 4 +++ src/frameworks/next/index.ts | 5 +++ src/frameworks/nuxt/index.ts | 6 ++++ src/frameworks/utils.ts | 22 +++++++++++++ src/frameworks/vite/index.ts | 6 ++++ src/test/frameworks/utils.spec.ts | 53 +++++++++++++++++++++++++++++++ 7 files changed, 97 insertions(+) create mode 100644 src/frameworks/utils.ts create mode 100644 src/test/frameworks/utils.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 94af501af7f..52890686b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Add support for Firestore TTL (#5267) - Fix bug where secrets were not loaded when emulating functions with `--inpsect-functions`. (#4605) +- Warn if a web framework's package.json contains anything other than the framework default build command. diff --git a/src/frameworks/angular/index.ts b/src/frameworks/angular/index.ts index db5ccbf0fb8..3d24a8f6023 100644 --- a/src/frameworks/angular/index.ts +++ b/src/frameworks/angular/index.ts @@ -14,12 +14,14 @@ import { } from ".."; import { promptOnce } from "../../prompt"; import { proxyRequestHandler } from "../../hosting/proxy"; +import { warnIfCustomBuildScript } from "../utils"; export const name = "Angular"; export const support = SupportLevel.Experimental; export const type = FrameworkType.Framework; const CLI_COMMAND = join("node_modules", ".bin", process.platform === "win32" ? "ng.cmd" : "ng"); +const DEFAULT_BUILD_SCRIPT = ["ng build"]; export async function discover(dir: string): Promise { if (!(await pathExists(join(dir, "package.json")))) return; @@ -57,6 +59,8 @@ export async function build(dir: string): Promise { if (!success) throw new Error(error); }; + await warnIfCustomBuildScript(dir, name, DEFAULT_BUILD_SCRIPT); + if (!browserTarget) throw new Error("No build target..."); if (prerenderTarget) { diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index e13ba65b00e..884bdbb5cc9 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -22,6 +22,7 @@ import { IncomingMessage, ServerResponse } from "http"; import { logger } from "../../logger"; import { FirebaseError } from "../../error"; import { fileExistsSync } from "../../fsutils"; +import { warnIfCustomBuildScript } from "../utils"; // Next.js's exposed interface is incomplete here // TODO see if there's a better way to grab this @@ -45,6 +46,8 @@ const CLI_COMMAND = join( process.platform === "win32" ? "next.cmd" : "next" ); +const DEFAULT_BUILD_SCRIPT = ["next build"]; + export const name = "Next.js"; export const support = SupportLevel.Experimental; export const type = FrameworkType.MetaFramework; @@ -73,6 +76,8 @@ export async function discover(dir: string) { export async function build(dir: string): Promise { const { default: nextBuild } = relativeRequire(dir, "next/dist/build"); + await warnIfCustomBuildScript(dir, name, DEFAULT_BUILD_SCRIPT); + const reactVersion = getReactVersion(dir); if (reactVersion && gte(reactVersion, "18.0.0")) { // This needs to be set for Next build to succeed with React 18 diff --git a/src/frameworks/nuxt/index.ts b/src/frameworks/nuxt/index.ts index aba1129bcc7..3fce4387193 100644 --- a/src/frameworks/nuxt/index.ts +++ b/src/frameworks/nuxt/index.ts @@ -3,11 +3,14 @@ import { readFile } from "fs/promises"; import { basename, join } from "path"; import { gte } from "semver"; import { BuildResult, findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; +import { warnIfCustomBuildScript } from "../utils"; export const name = "Nuxt"; export const support = SupportLevel.Experimental; export const type = FrameworkType.Toolchain; +const DEFAULT_BUILD_SCRIPT = ["nuxt build"]; + export async function discover(dir: string) { if (!(await pathExists(join(dir, "package.json")))) return; const nuxtDependency = findDependency("nuxt", { cwd: dir, depth: 0, omitDev: false }); @@ -23,6 +26,9 @@ export async function discover(dir: string) { export async function build(root: string): Promise { const { buildNuxt } = await relativeRequire(root, "@nuxt/kit"); const nuxtApp = await getNuxtApp(root); + + await warnIfCustomBuildScript(root, name, DEFAULT_BUILD_SCRIPT); + await buildNuxt(nuxtApp); return { wantsBackend: true }; } diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts new file mode 100644 index 00000000000..d45174a03b1 --- /dev/null +++ b/src/frameworks/utils.ts @@ -0,0 +1,22 @@ +import { readFile } from "fs/promises"; +import { join } from "path"; + +/** + * Prints a warning if the build script in package.json + * contains anything other than allowedBuildScripts. + */ +export async function warnIfCustomBuildScript( + dir: string, + framework: string, + defaultBuildScripts: string[] +): Promise { + const packageJsonBuffer = await readFile(join(dir, "package.json")); + const packageJson = JSON.parse(packageJsonBuffer.toString()); + const buildScript = packageJson.scripts?.build; + + if (buildScript && !defaultBuildScripts.includes(buildScript)) { + console.warn( + `\nWARNING: Your package.json contains a custom build that is being ignored. Only the ${framework} default build script (e.g, "${defaultBuildScripts[0]}") is respected. If you have a more advanced build process you should build a custom integration https://firebase.google.com/docs/hosting/express\n` + ); + } +} diff --git a/src/frameworks/vite/index.ts b/src/frameworks/vite/index.ts index d68c4177c4b..6586c1ff588 100644 --- a/src/frameworks/vite/index.ts +++ b/src/frameworks/vite/index.ts @@ -5,6 +5,7 @@ import { join } from "path"; import { findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; import { proxyRequestHandler } from "../../hosting/proxy"; import { promptOnce } from "../../prompt"; +import { warnIfCustomBuildScript } from "../utils"; export const name = "Vite"; export const support = SupportLevel.Experimental; @@ -16,6 +17,8 @@ const CLI_COMMAND = join( process.platform === "win32" ? "vite.cmd" : "vite" ); +export const DEFAULT_BUILD_SCRIPT = ["vite build", "tsc && vite build"]; + export const initViteTemplate = (template: string) => async (setup: any) => await init(setup, template); @@ -61,6 +64,9 @@ export async function discover(dir: string, plugin?: string, npmDependency?: str export async function build(root: string) { const { build } = relativeRequire(root, "vite"); + + await warnIfCustomBuildScript(root, name, DEFAULT_BUILD_SCRIPT); + await build({ root }); } diff --git a/src/test/frameworks/utils.spec.ts b/src/test/frameworks/utils.spec.ts new file mode 100644 index 00000000000..9d0c15c00cd --- /dev/null +++ b/src/test/frameworks/utils.spec.ts @@ -0,0 +1,53 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import * as fs from "fs"; + +import { warnIfCustomBuildScript } from "../../frameworks/utils"; + +describe("Frameworks utils", () => { + describe("warnIfCustomBuildScript", () => { + const framework = "Next.js"; + let sandbox: sinon.SinonSandbox; + let consoleLogSpy: sinon.SinonSpy; + const packageJson = { + scripts: { + build: "", + }, + }; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + consoleLogSpy = sandbox.spy(console, "warn"); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should not print warning when a default build script is found.", async () => { + const buildScript = "next build"; + const defaultBuildScripts = ["next build"]; + packageJson.scripts.build = buildScript; + + sandbox.stub(fs.promises, "readFile").resolves(JSON.stringify(packageJson)); + + await warnIfCustomBuildScript("fakedir/", framework, defaultBuildScripts); + + expect(consoleLogSpy.callCount).to.equal(0); + }); + + it("should print warning when a custom build script is found.", async () => { + const buildScript = "echo 'Custom build script' && next build"; + const defaultBuildScripts = ["next build"]; + packageJson.scripts.build = buildScript; + + sandbox.stub(fs.promises, "readFile").resolves(JSON.stringify(packageJson)); + + await warnIfCustomBuildScript("fakedir/", framework, defaultBuildScripts); + + expect(consoleLogSpy).to.be.calledOnceWith( + `\nWARNING: Your package.json contains a custom build that is being ignored. Only the ${framework} default build script (e.g, "${defaultBuildScripts[0]}") is respected. If you have a more advanced build process you should build a custom integration https://firebase.google.com/docs/hosting/express\n` + ); + }); + }); +}); From cbe579059ef7fdb8e61783a926a2406d9b3c6f3e Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 8 Dec 2022 23:26:54 -0500 Subject: [PATCH 0713/1699] Handle Next.js rewrites/redirects/headers (#5212) * Handle Next.js rewrites/redirects/headers incompatible with `firebase.json` in Cloud Functions * Filter out Next.js prerendered routes that matches rewrites/redirects/headers rules from SSG content directory --- CHANGELOG.md | 2 + src/frameworks/next/index.ts | 141 +++++++-- src/frameworks/next/interfaces.ts | 31 ++ src/frameworks/next/utils.ts | 114 +++++++ src/frameworks/utils.ts | 21 +- src/test/frameworks/next/helpers/headers.ts | 292 ++++++++++++++++++ src/test/frameworks/next/helpers/index.ts | 4 + src/test/frameworks/next/helpers/paths.ts | 90 ++++++ src/test/frameworks/next/helpers/redirects.ts | 107 +++++++ src/test/frameworks/next/helpers/rewrites.ts | 85 +++++ src/test/frameworks/next/utils.spec.ts | 145 +++++++++ src/test/frameworks/utils.spec.ts | 23 ++ 12 files changed, 1020 insertions(+), 35 deletions(-) create mode 100644 src/frameworks/next/interfaces.ts create mode 100644 src/frameworks/next/utils.ts create mode 100644 src/test/frameworks/next/helpers/headers.ts create mode 100644 src/test/frameworks/next/helpers/index.ts create mode 100644 src/test/frameworks/next/helpers/paths.ts create mode 100644 src/test/frameworks/next/helpers/redirects.ts create mode 100644 src/test/frameworks/next/helpers/rewrites.ts create mode 100644 src/test/frameworks/next/utils.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 52890686b4b..6149438f5a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ - Add support for Firestore TTL (#5267) - Fix bug where secrets were not loaded when emulating functions with `--inpsect-functions`. (#4605) +- Handle Next.js rewrites/redirects/headers incompatible with `firebase.json` in Cloud Functions (#5212) +- Filter out Next.js prerendered routes that matches rewrites/redirects/headers rules from SSG content directory (#5212) - Warn if a web framework's package.json contains anything other than the framework default build command. diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index 884bdbb5cc9..6a71d5dbfe0 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -1,7 +1,6 @@ import { execSync } from "child_process"; import { readFile, mkdir, copyFile } from "fs/promises"; import { dirname, join } from "path"; -import type { Header, Rewrite, Redirect } from "next/dist/lib/load-custom-routes"; import type { NextConfig } from "next"; import { copy, mkdirp, pathExists } from "fs-extra"; import { pathToFileURL, parse } from "url"; @@ -22,24 +21,17 @@ import { IncomingMessage, ServerResponse } from "http"; import { logger } from "../../logger"; import { FirebaseError } from "../../error"; import { fileExistsSync } from "../../fsutils"; +import { + cleanEscapedChars, + getNextjsRewritesToUse, + isHeaderSupportedByFirebase, + isRedirectSupportedByFirebase, + isRewriteSupportedByFirebase, +} from "./utils"; +import type { Manifest } from "./interfaces"; +import { readJSON } from "../utils"; import { warnIfCustomBuildScript } from "../utils"; -// Next.js's exposed interface is incomplete here -// TODO see if there's a better way to grab this -interface Manifest { - distDir?: string; - basePath?: string; - headers?: (Header & { regex: string })[]; - redirects?: (Redirect & { regex: string })[]; - rewrites?: - | (Rewrite & { regex: string })[] - | { - beforeFiles?: (Rewrite & { regex: string })[]; - afterFiles?: (Rewrite & { regex: string })[]; - fallback?: (Rewrite & { regex: string })[]; - }; -} - const CLI_COMMAND = join( "node_modules", ".bin", @@ -140,27 +132,56 @@ export async function build(dir: string): Promise { } } - const manifestBuffer = await readFile(join(dir, distDir, "routes-manifest.json")); - const manifest: Manifest = JSON.parse(manifestBuffer.toString()); + const manifest = await readJSON(join(dir, distDir, "routes-manifest.json")); + const { headers: nextJsHeaders = [], redirects: nextJsRedirects = [], rewrites: nextJsRewrites = [], } = manifest; - const headers = nextJsHeaders.map(({ source, headers }) => ({ source, headers })); + + const isEveryHeaderSupported = nextJsHeaders.every(isHeaderSupportedByFirebase); + if (!isEveryHeaderSupported) wantsBackend = true; + + const headers = nextJsHeaders.filter(isHeaderSupportedByFirebase).map(({ source, headers }) => ({ + // clean up unnecessary escaping + source: cleanEscapedChars(source), + headers, + })); + + const isEveryRedirectSupported = nextJsRedirects.every(isRedirectSupportedByFirebase); + if (!isEveryRedirectSupported) wantsBackend = true; + const redirects = nextJsRedirects - .filter(({ internal }: any) => !internal) - .map(({ source, destination, statusCode: type }) => ({ source, destination, type })); - const nextJsRewritesToUse = Array.isArray(nextJsRewrites) - ? nextJsRewrites - : nextJsRewrites.beforeFiles || []; + .filter(isRedirectSupportedByFirebase) + .map(({ source, destination, statusCode: type }) => ({ + // clean up unnecessary escaping + source: cleanEscapedChars(source), + destination, + type, + })); + + const nextJsRewritesToUse = getNextjsRewritesToUse(nextJsRewrites); + + // rewrites.afterFiles / rewrites.fallback are not supported by firebase.json + if ( + !Array.isArray(nextJsRewrites) && + (nextJsRewrites.afterFiles?.length || nextJsRewrites.fallback?.length) + ) { + wantsBackend = true; + } else { + const isEveryRewriteSupported = nextJsRewritesToUse.every(isRewriteSupportedByFirebase); + if (!isEveryRewriteSupported) wantsBackend = true; + } + + // Can we change i18n into Firebase settings? const rewrites = nextJsRewritesToUse - .map(({ source, destination, has }) => { - // Can we change i18n into Firebase settings? - if (has) return undefined; - return { source, destination }; - }) - .filter((it) => it); + .filter(isRewriteSupportedByFirebase) + .map(({ source, destination }) => ({ + // clean up unnecessary escaping + source: cleanEscapedChars(source), + destination, + })); return { wantsBackend, headers, redirects, rewrites }; } @@ -215,10 +236,47 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin } } - const prerenderManifestBuffer = await readFile( - join(sourceDir, distDir, "prerender-manifest.json") + const [prerenderManifest, routesManifest] = await Promise.all([ + readJSON( + join( + sourceDir, + distDir, + "prerender-manifest.json" // TODO: get this from next/constants + ) + ), + readJSON( + join( + sourceDir, + distDir, + "routes-manifest.json" // TODO: get this from next/constants + ) + ), + ]); + + const { redirects = [], rewrites = [], headers = [] } = routesManifest; + + const rewritesToUse = getNextjsRewritesToUse(rewrites); + const rewritesNotSupportedByFirebase = rewritesToUse.filter( + (rewrite) => !isRewriteSupportedByFirebase(rewrite) + ); + const rewritesRegexesNotSupportedByFirebase = rewritesNotSupportedByFirebase.map( + (rewrite) => new RegExp(rewrite.regex) + ); + + const redirectsNotSupportedByFirebase = redirects.filter( + (redirect) => !isRedirectSupportedByFirebase(redirect) + ); + const redirectsRegexesNotSupportedByFirebase = redirectsNotSupportedByFirebase.map( + (redirect) => new RegExp(redirect.regex) + ); + + const headersNotSupportedByFirebase = headers.filter( + (header) => !isHeaderSupportedByFirebase(header) ); - const prerenderManifest = JSON.parse(prerenderManifestBuffer.toString()); + const headersRegexesNotSupportedByFirebase = headersNotSupportedByFirebase.map( + (header) => new RegExp(header.regex) + ); + for (const path in prerenderManifest.routes) { if (prerenderManifest.routes[path]) { // Skip ISR in the deploy to hosting @@ -227,6 +285,21 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin continue; } + const routeMatchUnsupportedRewrite = rewritesRegexesNotSupportedByFirebase.some( + (rewriteRegex) => rewriteRegex.test(path) + ); + if (routeMatchUnsupportedRewrite) continue; + + const routeMatchUnsupportedRedirect = redirectsRegexesNotSupportedByFirebase.some( + (redirectRegex) => redirectRegex.test(path) + ); + if (routeMatchUnsupportedRedirect) continue; + + const routeMatchUnsupportedHeader = headersRegexesNotSupportedByFirebase.some( + (headerRegex) => headerRegex.test(path) + ); + if (routeMatchUnsupportedHeader) continue; + // TODO(jamesdaniels) explore oppertunity to simplify this now that we // are defaulting cleanURLs to true for frameworks diff --git a/src/frameworks/next/interfaces.ts b/src/frameworks/next/interfaces.ts new file mode 100644 index 00000000000..e012402b9fb --- /dev/null +++ b/src/frameworks/next/interfaces.ts @@ -0,0 +1,31 @@ +import type { Header, Rewrite, Redirect } from "next/dist/lib/load-custom-routes"; + +export interface RoutesManifestRewrite extends Rewrite { + regex: string; +} + +export interface RoutesManifestRewriteObject { + beforeFiles?: RoutesManifestRewrite[]; + afterFiles?: RoutesManifestRewrite[]; + fallback?: RoutesManifestRewrite[]; +} + +export interface RoutesManifestHeader extends Header { + regex: string; +} + +// Next.js's exposed interface is incomplete here +// TODO see if there's a better way to grab this +// TODO: rename to RoutesManifest as Next.js has other types of manifests +export interface Manifest { + distDir?: string; + basePath?: string; + headers?: RoutesManifestHeader[]; + redirects?: Array< + Redirect & { + regex: string; + internal?: boolean; + } + >; + rewrites?: RoutesManifestRewrite[] | RoutesManifestRewriteObject; +} diff --git a/src/frameworks/next/utils.ts b/src/frameworks/next/utils.ts new file mode 100644 index 00000000000..040f06adedb --- /dev/null +++ b/src/frameworks/next/utils.ts @@ -0,0 +1,114 @@ +import type { Header, Redirect, Rewrite } from "next/dist/lib/load-custom-routes"; +import type { Manifest, RoutesManifestRewrite } from "./interfaces"; +import { isUrl } from "../utils"; + +/** + * Whether the given path has a regex or not. + * According to the Next.js documentation: + * ```md + * To match a regex path you can wrap the regex in parentheses + * after a parameter, for example /post/:slug(\\d{1,}) will match /post/123 + * but not /post/abc. + * ``` + * See: https://nextjs.org/docs/api-reference/next.config.js/redirects#regex-path-matching + */ +export function pathHasRegex(path: string): boolean { + // finds parentheses that are not preceded by double backslashes + return /(? b); +} + +/** + * Whether a Next.js rewrite is supported by `firebase.json`. + * + * See: https://firebase.google.com/docs/hosting/full-config#rewrites + * + * Next.js unsupported rewrites includes: + * - Rewrites with the `has` property that is used by Next.js for Header, + * Cookie, and Query Matching. + * - https://nextjs.org/docs/api-reference/next.config.js/rewrites#header-cookie-and-query-matching + * + * - Rewrites using regex for path matching. + * - https://nextjs.org/docs/api-reference/next.config.js/rewrites#regex-path-matching + * + * - Rewrites to external URLs + */ +export function isRewriteSupportedByFirebase(rewrite: Rewrite): boolean { + return !("has" in rewrite || pathHasRegex(rewrite.source) || isUrl(rewrite.destination)); +} + +/** + * Whether a Next.js redirect is supported by `firebase.json`. + * + * See: https://firebase.google.com/docs/hosting/full-config#redirects + * + * Next.js unsupported redirects includes: + * - Redirects with the `has` property that is used by Next.js for Header, + * Cookie, and Query Matching. + * - https://nextjs.org/docs/api-reference/next.config.js/redirects#header-cookie-and-query-matching + * + * - Redirects using regex for path matching. + * - https://nextjs.org/docs/api-reference/next.config.js/redirects#regex-path-matching + * + * - Next.js internal redirects + */ +export function isRedirectSupportedByFirebase(redirect: Redirect): boolean { + return !("has" in redirect || pathHasRegex(redirect.source) || "internal" in redirect); +} + +/** + * Whether a Next.js custom header is supported by `firebase.json`. + * + * See: https://firebase.google.com/docs/hosting/full-config#headers + * + * Next.js unsupported headers includes: + * - Custom header with the `has` property that is used by Next.js for Header, + * Cookie, and Query Matching. + * - https://nextjs.org/docs/api-reference/next.config.js/headers#header-cookie-and-query-matching + * + * - Custom header using regex for path matching. + * - https://nextjs.org/docs/api-reference/next.config.js/headers#regex-path-matching + */ +export function isHeaderSupportedByFirebase(header: Header): boolean { + return !("has" in header || pathHasRegex(header.source)); +} + +/** + * Get which Next.js rewrites will be used before checking supported items individually. + * + * Next.js rewrites can be arrays or objects: + * - For arrays, all supported items can be used. + * - For objects only `beforeFiles` can be used. + * + * See: https://nextjs.org/docs/api-reference/next.config.js/rewrites + */ +export function getNextjsRewritesToUse( + nextJsRewrites: Manifest["rewrites"] +): RoutesManifestRewrite[] { + if (Array.isArray(nextJsRewrites)) { + return nextJsRewrites; + } + + if (nextJsRewrites?.beforeFiles) { + return nextJsRewrites.beforeFiles; + } + + return []; +} diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index d45174a03b1..2024df5e1b6 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -1,5 +1,24 @@ -import { readFile } from "fs/promises"; +import { readJSON as originalReadJSON } from "fs-extra"; +import type { ReadOptions } from "fs-extra"; import { join } from "path"; +import { readFile } from "fs/promises"; + +/** + * Whether the given string starts with http:// or https:// + */ +export function isUrl(url: string): boolean { + return /^https?:\/\//.test(url); +} + +/** + * add type to readJSON + */ +export function readJSON( + file: string, + options?: ReadOptions | BufferEncoding | string +): Promise { + return originalReadJSON(file, options) as Promise; +} /** * Prints a warning if the build script in package.json diff --git a/src/test/frameworks/next/helpers/headers.ts b/src/test/frameworks/next/helpers/headers.ts new file mode 100644 index 00000000000..8f8bb447006 --- /dev/null +++ b/src/test/frameworks/next/helpers/headers.ts @@ -0,0 +1,292 @@ +import type { RoutesManifestHeader } from "../../../../frameworks/next/interfaces"; +import { supportedPaths, unsupportedPaths } from "./paths"; + +export const supportedHeaders: RoutesManifestHeader[] = [ + ...supportedPaths.map((path) => ({ + source: path, + regex: "", + headers: [ + { + key: "x-path", + value: ":path", + }, + { + key: "some:path", + value: "hi", + }, + { + key: "x-test", + value: "some:value*", + }, + { + key: "x-test-2", + value: "value*", + }, + { + key: "x-test-3", + value: ":value?", + }, + { + key: "x-test-4", + value: ":value+", + }, + { + key: "x-test-5", + value: "something https:", + }, + { + key: "x-test-6", + value: ":hello(world)", + }, + { + key: "x-test-7", + value: "hello(world)", + }, + { + key: "x-test-8", + value: "hello{1,}", + }, + { + key: "x-test-9", + value: ":hello{1,2}", + }, + { + key: "content-security-policy", + value: + "default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com/:path", + }, + ], + })), + { + regex: "", + source: "/add-header", + headers: [ + { + key: "x-custom-header", + value: "hello world", + }, + { + key: "x-another-header", + value: "hello again", + }, + ], + }, + { + regex: "", + source: "/my-other-header/:path", + headers: [ + { + key: "x-path", + value: ":path", + }, + { + key: "some:path", + value: "hi", + }, + { + key: "x-test", + value: "some:value*", + }, + { + key: "x-test-2", + value: "value*", + }, + { + key: "x-test-3", + value: ":value?", + }, + { + key: "x-test-4", + value: ":value+", + }, + { + key: "x-test-5", + value: "something https:", + }, + { + key: "x-test-6", + value: ":hello(world)", + }, + { + key: "x-test-7", + value: "hello(world)", + }, + { + key: "x-test-8", + value: "hello{1,}", + }, + { + key: "x-test-9", + value: ":hello{1,2}", + }, + { + key: "content-security-policy", + value: + "default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com/:path", + }, + ], + }, + { + regex: "", + source: "/without-params/url", + headers: [ + { + key: "x-origin", + value: "https://example.com", + }, + ], + }, + { + regex: "", + source: "/with-params/url/:path*", + headers: [ + { + key: "x-url", + value: "https://example.com/:path*", + }, + ], + }, + { + regex: "", + source: "/with-params/url2/:path*", + headers: [ + { + key: "x-url", + value: "https://example.com:8080?hello=:path*", + }, + ], + }, + { + regex: "", + source: "/:path*", + headers: [ + { + key: "x-something", + value: "applied-everywhere", + }, + ], + }, + { + regex: "", + source: "/catchall-header/:path*", + headers: [ + { + key: "x-value", + value: ":path*", + }, + ], + }, +]; + +export const unsupportedHeaders: RoutesManifestHeader[] = [ + ...unsupportedPaths.map((path) => ({ + source: path, + regex: "", + headers: [ + { + key: "x-custom-header", + value: "hello world", + }, + { + key: "x-another-header", + value: "hello again", + }, + ], + })), + { + regex: "", + source: "/named-pattern/:path(.*)", + headers: [ + { + key: "x-something", + value: "value=:path", + }, + { + key: "path-:path", + value: "end", + }, + ], + }, + + { + regex: "", + source: "/my-headers/(.*)", + headers: [ + { + key: "x-first-header", + value: "first", + }, + { + key: "x-second-header", + value: "second", + }, + ], + }, + + { + regex: "", + source: "/has-header-1", + has: [ + { + type: "header", + key: "x-my-header", + value: "(?.*)", + }, + ], + headers: [ + { + key: "x-another", + value: "header", + }, + ], + }, + { + regex: "", + source: "/has-header-2", + has: [ + { + type: "query", + key: "my-query", + }, + ], + headers: [ + { + key: "x-added", + value: "value", + }, + ], + }, + { + regex: "", + source: "/has-header-3", + has: [ + { + type: "cookie", + key: "loggedIn", + value: "true", + }, + ], + headers: [ + { + key: "x-is-user", + value: "yuuuup", + }, + ], + }, + { + regex: "", + source: "/has-header-4", + has: [ + { + type: "host", + value: "example.com", + }, + ], + headers: [ + { + key: "x-is-host", + value: "yuuuup", + }, + ], + }, +]; diff --git a/src/test/frameworks/next/helpers/index.ts b/src/test/frameworks/next/helpers/index.ts new file mode 100644 index 00000000000..83772e4ea98 --- /dev/null +++ b/src/test/frameworks/next/helpers/index.ts @@ -0,0 +1,4 @@ +export * from "./paths"; +export * from "./headers"; +export * from "./redirects"; +export * from "./rewrites"; diff --git a/src/test/frameworks/next/helpers/paths.ts b/src/test/frameworks/next/helpers/paths.ts new file mode 100644 index 00000000000..2b5c3e3f881 --- /dev/null +++ b/src/test/frameworks/next/helpers/paths.ts @@ -0,0 +1,90 @@ +export const pathsWithRegex = [ + "/(.*)", + "/post/:slug(\\d{1,})", + "/:path((?!another-page$).*)", + "/api-hello-regex/:first(.*)", + "/unnamed-params/nested/(.*)/:test/(.*)", +] as const; + +export const pathsWithEscapedChars = [ + `/post\\(someStringBetweenParentheses\\)/:slug`, + `/english\\(default\\)/:slug`, + `/post/\\(es\\?cap\\Wed\\*p\\{ar\\}en\\:th\\eses\\)`, +] as const; + +export const pathsWithRegexAndEscapedChars = [ + `/post/\\(escapedparentheses\\)/:slug(\\d{1,})`, + `/post/\\(es\\?cap\\Wed\\*p\\{ar\\}en\\:th\\eses\\)/:slug(\\d{1,})`, +] as const; + +export const pathsAsGlobs = [ + "/specific/:path*", + "/another/:path*", + "/about", + "/", + "/old-blog/:path*", + "/blog/:path*", + "/to-websocket", + "/to-nowhere", + "/rewriting-to-auto-export", + "/rewriting-to-another-auto-export/:path*", + "/to-another", + "/another/one", + "/nav", + "/404", + "/hello-world", + "/static/hello.txt", + "/another", + "/multi-rewrites", + "/first", + "/hello", + "/second", + "/hello-again", + "/to-hello", + "/hello", + "/blog/post-1", + "/blog/post-2", + "/test/:path", + "/:path", + "/test-overwrite/:something/:another", + "/params/this-should-be-the-value", + "/params/:something", + "/with-params", + "/query-rewrite/:section/:name", + "/hidden/_next/:path*", + "/_next/:path*", + "/proxy-me/:path*", + "/api-hello", + "/api/hello", + "/api/hello?name=:first*", + "/api-hello-param/:name", + "/api/hello?hello=:name", + "/api-dynamic-param/:name", + "/api/dynamic/:name?hello=:name", + "/:path/post-321", + "/with-params", + "/with-params", + "/catchall-rewrite/:path*", + "/with-params", + "/catchall-query/:path*", + "/has-rewrite-1", + "/has-rewrite-2", + "/has-rewrite-3", + "/has-rewrite-4", + "/has-rewrite-5", + "/:hasParam", + "/has-rewrite-6", + "/with-params", + "/has-rewrite-7", + "/has-rewrite-8", + "/blog-catchall/:post", + "/missing-rewrite-1", + "/with-params", + "/missing-rewrite-2", + "/with-params", + "/missing-rewrite-3", + "/overridden/:path*", +] as const; + +export const supportedPaths = [...pathsWithEscapedChars, ...pathsAsGlobs] as const; +export const unsupportedPaths = [...pathsWithRegex, ...pathsWithRegexAndEscapedChars] as const; diff --git a/src/test/frameworks/next/helpers/redirects.ts b/src/test/frameworks/next/helpers/redirects.ts new file mode 100644 index 00000000000..d67394951f2 --- /dev/null +++ b/src/test/frameworks/next/helpers/redirects.ts @@ -0,0 +1,107 @@ +import type { Manifest } from "../../../../frameworks/next/interfaces"; +import { supportedPaths, unsupportedPaths } from "./paths"; + +export const supportedRedirects: NonNullable = supportedPaths.map( + (path) => ({ + source: path, + destination: `${path}/redirect`, + regex: "", + statusCode: 301, + }) +); + +export const unsupportedRedirects: NonNullable = [ + ...unsupportedPaths.map((path) => ({ + source: path, + destination: `/${path}/redirect`, + regex: "", + statusCode: 301, + })), + { + source: "/has-redirect-1", + has: [ + { + type: "header", + key: "x-my-header", + value: "(?.*)", + }, + ], + destination: "/another?myHeader=:myHeader", + permanent: false, + regex: "", + }, + { + source: "/has-redirect-2", + has: [ + { + type: "query", + key: "my-query", + }, + ], + destination: "/another?value=:myquery", + permanent: false, + regex: "", + }, + { + source: "/has-redirect-3", + has: [ + { + type: "cookie", + key: "loggedIn", + value: "true", + }, + ], + destination: "/another?authorized=1", + permanent: false, + regex: "", + }, + { + source: "/has-redirect-4", + has: [ + { + type: "host", + value: "example.com", + }, + ], + destination: "/another?host=1", + permanent: false, + regex: "", + }, + { + source: "/:path/has-redirect-5", + has: [ + { + type: "header", + key: "x-test-next", + }, + ], + destination: "/somewhere", + permanent: false, + regex: "", + }, + { + source: "/has-redirect-6", + has: [ + { + type: "host", + value: "(?.*)-test.example.com", + }, + ], + destination: "https://:subdomain.example.com/some-path/end?a=b", + permanent: false, + regex: "", + }, + { + source: "/has-redirect-7", + has: [ + { + type: "query", + key: "hello", + value: "(?.*)", + }, + ], + destination: "/somewhere?value=:hello", + permanent: false, + regex: "", + }, +]; diff --git a/src/test/frameworks/next/helpers/rewrites.ts b/src/test/frameworks/next/helpers/rewrites.ts new file mode 100644 index 00000000000..cc55dde9669 --- /dev/null +++ b/src/test/frameworks/next/helpers/rewrites.ts @@ -0,0 +1,85 @@ +import type { + RoutesManifestRewrite, + RoutesManifestRewriteObject, +} from "../../../../frameworks/next/interfaces"; +import { supportedPaths, unsupportedPaths } from "./paths"; + +export const supportedRewritesArray: RoutesManifestRewrite[] = supportedPaths.map((path) => ({ + source: path, + destination: `${path}/rewrite`, + regex: "", +})); + +export const unsupportedRewritesArray: RoutesManifestRewrite[] = [ + ...unsupportedPaths.map((path) => ({ + source: path, + destination: `/${path}/rewrite`, + regex: "", + })), + // external http URL + { + source: "/:path*", + destination: "http://firebase.google.com", + regex: "", + }, + // external https URL + { + source: "/:path*", + destination: "https://firebase.google.com", + regex: "", + }, + // with has + { + source: "/specific/:path*", + destination: "/some/specific/:path", + regex: "", + has: [ + { type: "query", key: "overrideMe" }, + { + type: "header", + key: "x-rewrite-me", + }, + ], + }, + // with has + { + source: "/specific/:path*", + destination: "/some/specific/:path", + regex: "", + has: [ + { + type: "query", + key: "page", + // the page value will not be available in the + // destination since value is provided and doesn't + // use a named capture group e.g. (?home) + value: "home", + }, + ], + }, + // with has + { + source: "/specific/:path*", + destination: "/some/specific/:path", + regex: "", + has: [ + { + type: "cookie", + key: "authorized", + value: "true", + }, + ], + }, +]; + +export const supportedRewritesObject: RoutesManifestRewriteObject = { + afterFiles: unsupportedRewritesArray, // should be ignored, only beforeFiles is used + beforeFiles: supportedRewritesArray, + fallback: unsupportedRewritesArray, // should be ignored, only beforeFiles is used +}; + +export const unsupportedRewritesObject: RoutesManifestRewriteObject = { + afterFiles: unsupportedRewritesArray, // should be ignored, only beforeFiles is used + beforeFiles: unsupportedRewritesArray, + fallback: unsupportedRewritesArray, // should be ignored, only beforeFiles is used +}; diff --git a/src/test/frameworks/next/utils.spec.ts b/src/test/frameworks/next/utils.spec.ts new file mode 100644 index 00000000000..06c0e995b25 --- /dev/null +++ b/src/test/frameworks/next/utils.spec.ts @@ -0,0 +1,145 @@ +import { expect } from "chai"; + +import { + pathHasRegex, + cleanEscapedChars, + isRewriteSupportedByFirebase, + isRedirectSupportedByFirebase, + isHeaderSupportedByFirebase, + getNextjsRewritesToUse, +} from "../../../frameworks/next/utils"; +import { + pathsAsGlobs, + pathsWithEscapedChars, + pathsWithRegex, + pathsWithRegexAndEscapedChars, + supportedHeaders, + supportedRedirects, + supportedRewritesArray, + supportedRewritesObject, + unsupportedHeaders, + unsupportedRedirects, + unsupportedRewritesArray, +} from "./helpers"; + +describe("Next.js utils", () => { + describe("pathHasRegex", () => { + it("should identify regex", () => { + for (const path of pathsWithRegex) { + expect(pathHasRegex(path)).to.be.true; + } + }); + + it("should not identify escaped parentheses as regex", () => { + for (const path of pathsWithEscapedChars) { + expect(pathHasRegex(path)).to.be.false; + } + }); + + it("should identify regex along with escaped chars", () => { + for (const path of pathsWithRegexAndEscapedChars) { + expect(pathHasRegex(path)).to.be.true; + } + }); + + it("should not identify globs as regex", () => { + for (const path of pathsAsGlobs) { + expect(pathHasRegex(path)).to.be.false; + } + }); + }); + + describe("cleanEscapedChars", () => { + it("should clean escaped chars", () => { + // path containing all escaped chars + const testPath = "/\\(\\)\\{\\}\\:\\+\\?\\*/:slug"; + + expect(testPath.includes("\\(")).to.be.true; + expect(cleanEscapedChars(testPath).includes("\\(")).to.be.false; + + expect(testPath.includes("\\)")).to.be.true; + expect(cleanEscapedChars(testPath).includes("\\)")).to.be.false; + + expect(testPath.includes("\\{")).to.be.true; + expect(cleanEscapedChars(testPath).includes("\\{")).to.be.false; + + expect(testPath.includes("\\}")).to.be.true; + expect(cleanEscapedChars(testPath).includes("\\}")).to.be.false; + + expect(testPath.includes("\\:")).to.be.true; + expect(cleanEscapedChars(testPath).includes("\\:")).to.be.false; + + expect(testPath.includes("\\+")).to.be.true; + expect(cleanEscapedChars(testPath).includes("\\+")).to.be.false; + + expect(testPath.includes("\\?")).to.be.true; + expect(cleanEscapedChars(testPath).includes("\\?")).to.be.false; + + expect(testPath.includes("\\*")).to.be.true; + expect(cleanEscapedChars(testPath).includes("\\*")).to.be.false; + }); + }); + + describe("isRewriteSupportedByFirebase", () => { + it("should allow supported rewrites", () => { + for (const rewrite of supportedRewritesArray) { + expect(isRewriteSupportedByFirebase(rewrite)).to.be.true; + } + }); + + it("should disallow unsupported rewrites", () => { + for (const rewrite of unsupportedRewritesArray) { + expect(isRewriteSupportedByFirebase(rewrite)).to.be.false; + } + }); + }); + + describe("isRedirectSupportedByFirebase", () => { + it("should allow supported redirects", () => { + for (const redirect of supportedRedirects) { + expect(isRedirectSupportedByFirebase(redirect)).to.be.true; + } + }); + + it("should disallow unsupported redirects", () => { + for (const redirect of unsupportedRedirects) { + expect(isRedirectSupportedByFirebase(redirect)).to.be.false; + } + }); + }); + + describe("isHeaderSupportedByFirebase", () => { + it("should allow supported headers", () => { + for (const header of supportedHeaders) { + expect(isHeaderSupportedByFirebase(header)).to.be.true; + } + }); + + it("should disallow unsupported headers", () => { + for (const header of unsupportedHeaders) { + expect(isHeaderSupportedByFirebase(header)).to.be.false; + } + }); + }); + + describe("getNextjsRewritesToUse", () => { + it("should use only beforeFiles", () => { + if (!supportedRewritesObject?.beforeFiles?.length) { + throw new Error("beforeFiles must have rewrites"); + } + + const rewritesToUse = getNextjsRewritesToUse(supportedRewritesObject); + + for (const [i, rewrite] of supportedRewritesObject.beforeFiles.entries()) { + expect(rewrite.source).to.equal(rewritesToUse[i].source); + expect(rewrite.destination).to.equal(rewritesToUse[i].destination); + } + }); + + it("should return all rewrites if in array format", () => { + const rewritesToUse = getNextjsRewritesToUse(supportedRewritesArray); + + expect(rewritesToUse).to.have.length(supportedRewritesArray.length); + }); + }); +}); diff --git a/src/test/frameworks/utils.spec.ts b/src/test/frameworks/utils.spec.ts index 9d0c15c00cd..ebb675aadbc 100644 --- a/src/test/frameworks/utils.spec.ts +++ b/src/test/frameworks/utils.spec.ts @@ -3,8 +3,31 @@ import * as sinon from "sinon"; import * as fs from "fs"; import { warnIfCustomBuildScript } from "../../frameworks/utils"; +import { isUrl } from "../../frameworks/utils"; describe("Frameworks utils", () => { + describe("isUrl", () => { + it("should identify http URL", () => { + expect(isUrl("http://firebase.google.com")).to.be.true; + }); + + it("should identify https URL", () => { + expect(isUrl("https://firebase.google.com")).to.be.true; + }); + + it("should ignore URL within path", () => { + expect(isUrl("path/?url=https://firebase.google.com")).to.be.false; + }); + + it("should ignore path starting with http but without protocol", () => { + expect(isUrl("httpendpoint/foo/bar")).to.be.false; + }); + + it("should ignore path starting with https but without protocol", () => { + expect(isUrl("httpsendpoint/foo/bar")).to.be.false; + }); + }); + describe("warnIfCustomBuildScript", () => { const framework = "Next.js"; let sandbox: sinon.SinonSandbox; From 934e4e51e1ddacb9eb0acbccd86e37f7da04a7ee Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Fri, 9 Dec 2022 15:32:11 -0800 Subject: [PATCH 0714/1699] Update the download_emulators default to true. The user has indicated they want to use the emulators we should download them right away to speed up their use and enable the suite to work better offline. (#5123) --- src/init/features/emulators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init/features/emulators.ts b/src/init/features/emulators.ts index 84e6ee97ad9..3c8bf3ec245 100644 --- a/src/init/features/emulators.ts +++ b/src/init/features/emulators.ts @@ -95,7 +95,7 @@ export async function doSetup(setup: any, config: any) { name: "download", type: "confirm", message: "Would you like to download the emulators now?", - default: false, + default: true, }, ]); } From 26b97ba19b3996fc118e7a86f58841a059ae86c5 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 9 Dec 2022 16:14:38 -0800 Subject: [PATCH 0715/1699] Add support for nodejs18 (in preview). (#5319) --- CHANGELOG.md | 1 + schema/firebase-config.json | 6 ++++-- src/deploy/functions/runtimes/index.ts | 3 ++- .../functions/runtimes/node/parseRuntimeAndValidateSDK.ts | 1 + src/firebaseConfig.ts | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6149438f5a5..cc6969380cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Handle Next.js rewrites/redirects/headers incompatible with `firebase.json` in Cloud Functions (#5212) - Filter out Next.js prerendered routes that matches rewrites/redirects/headers rules from SSG content directory (#5212) - Warn if a web framework's package.json contains anything other than the framework default build command. +- Add support for nodejs18 for Cloud Functions for Firebase (#5319) diff --git a/schema/firebase-config.json b/schema/firebase-config.json index 6a20dd6d500..839508468e6 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -388,7 +388,8 @@ "nodejs10", "nodejs12", "nodejs14", - "nodejs16" + "nodejs16", + "nodejs18" ], "type": "string" }, @@ -442,7 +443,8 @@ "nodejs10", "nodejs12", "nodejs14", - "nodejs16" + "nodejs16", + "nodejs18" ], "type": "string" }, diff --git a/src/deploy/functions/runtimes/index.ts b/src/deploy/functions/runtimes/index.ts index 537d8141238..68bfb4125a0 100644 --- a/src/deploy/functions/runtimes/index.ts +++ b/src/deploy/functions/runtimes/index.ts @@ -5,7 +5,7 @@ import * as validate from "../validate"; import { FirebaseError } from "../../../error"; /** Supported runtimes for new Cloud Functions. */ -const RUNTIMES: string[] = ["nodejs10", "nodejs12", "nodejs14", "nodejs16"]; +const RUNTIMES: string[] = ["nodejs10", "nodejs12", "nodejs14", "nodejs16", "nodejs18"]; // Experimental runtimes are part of the Runtime type, but are in a // different list to help guard against some day accidentally iterating over // and printing a hidden runtime to the user. @@ -33,6 +33,7 @@ const MESSAGE_FRIENDLY_RUNTIMES: Record = { nodejs12: "Node.js 12", nodejs14: "Node.js 14", nodejs16: "Node.js 16", + nodejs18: "Node.js 18", go113: "Go 1.13", }; diff --git a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts index f1eeecefb6b..048d74f58ee 100644 --- a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts +++ b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts @@ -16,6 +16,7 @@ const ENGINE_RUNTIMES: Record Date: Mon, 12 Dec 2022 21:13:19 +0000 Subject: [PATCH 0716/1699] 11.18.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 80682016da2..2f24ab8996f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.17.0", + "version": "11.18.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.17.0", + "version": "11.18.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 51d0349eb30..35937a21d6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.17.0", + "version": "11.18.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 482f53a95c5f101e4826aff53432fb8017223ccb Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 12 Dec 2022 21:13:33 +0000 Subject: [PATCH 0717/1699] [firebase-release] Removed change log and reset repo after 11.18.0 release --- CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6969380cf..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +0,0 @@ -- Add support for Firestore TTL (#5267) -- Fix bug where secrets were not loaded when emulating functions with `--inpsect-functions`. (#4605) -- Handle Next.js rewrites/redirects/headers incompatible with `firebase.json` in Cloud Functions (#5212) -- Filter out Next.js prerendered routes that matches rewrites/redirects/headers rules from SSG content directory (#5212) -- Warn if a web framework's package.json contains anything other than the framework default build command. -- Add support for nodejs18 for Cloud Functions for Firebase (#5319) From 270f41944b98523e52168e1c0827c6690b894c07 Mon Sep 17 00:00:00 2001 From: Claudio F Date: Mon, 12 Dec 2022 17:28:29 -0500 Subject: [PATCH 0718/1699] Add sharp library to Next.js build (#5238) Checking for /app dir and nextImage to add the sharp library to the Next.js build --- CHANGELOG.md | 1 + src/frameworks/next/index.ts | 13 +++++ src/frameworks/next/interfaces.ts | 14 +++++ src/frameworks/next/utils.ts | 40 ++++++++++++- src/test/frameworks/next/utils.spec.ts | 80 ++++++++++++++++++++++++++ 5 files changed, 147 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..b0c8713f3bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Add sharp NPM module to Cloud Functions when using Next.js Image Optimization diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index 6a71d5dbfe0..53888503ed9 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -31,6 +31,7 @@ import { import type { Manifest } from "./interfaces"; import { readJSON } from "../utils"; import { warnIfCustomBuildScript } from "../utils"; +import { usesAppDirRouter, usesNextImage, hasUnoptimizedImage } from "./utils"; const CLI_COMMAND = join( "node_modules", @@ -370,6 +371,18 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: st await mkdir(join(destDir, "public")); await copy(join(sourceDir, "public"), join(destDir, "public")); } + + // Add the `sharp` library if `/app` folder exists (i.e. Next.js 13+) + // or usesNextImage in `export-marker.json` is set to true. + // As of (10/2021) the new Next.js 13 route is in beta, and usesNextImage is always being set to false + // if the image component is used in pages coming from the new `/app` routes. + if ( + !(await hasUnoptimizedImage(sourceDir, distDir)) && + (usesAppDirRouter(sourceDir) || (await usesNextImage(sourceDir, distDir))) + ) { + packageJson.dependencies["sharp"] = "latest"; + } + await mkdirp(join(destDir, distDir)); await copy(join(sourceDir, distDir), join(destDir, distDir)); return { packageJson, frameworksEntry: "next.js" }; diff --git a/src/frameworks/next/interfaces.ts b/src/frameworks/next/interfaces.ts index e012402b9fb..4a88ac9f2d5 100644 --- a/src/frameworks/next/interfaces.ts +++ b/src/frameworks/next/interfaces.ts @@ -29,3 +29,17 @@ export interface Manifest { >; rewrites?: RoutesManifestRewrite[] | RoutesManifestRewriteObject; } + +export interface ExportMarker { + version: number; + hasExportPathMap: boolean; + exportTrailingSlash: boolean; + isNextImageImported: boolean; +} + +export interface ImageManifest { + version: number; + images: { + unoptimized: boolean; + }; +} diff --git a/src/frameworks/next/utils.ts b/src/frameworks/next/utils.ts index 040f06adedb..0d3cb4afbb1 100644 --- a/src/frameworks/next/utils.ts +++ b/src/frameworks/next/utils.ts @@ -1,6 +1,9 @@ +import { existsSync } from "fs"; +import { join } from "path"; import type { Header, Redirect, Rewrite } from "next/dist/lib/load-custom-routes"; import type { Manifest, RoutesManifestRewrite } from "./interfaces"; -import { isUrl } from "../utils"; +import { isUrl, readJSON } from "../utils"; +import type { ExportMarker, ImageManifest } from "./interfaces"; /** * Whether the given path has a regex or not. @@ -112,3 +115,38 @@ export function getNextjsRewritesToUse( return []; } + +/** + * Check if `/app` directory is used in the Next.js project. + * @param sourceDir location of the source directory + * @return true if app directory is used in the Next.js project + */ +export function usesAppDirRouter(sourceDir: string): boolean { + const appPathRoutesManifestPath = join(sourceDir, "app-path-routes-manifest.json"); + return existsSync(appPathRoutesManifestPath); +} +/** + * Check if the project is using the next/image component based on the export-marker.json file. + * @param sourceDir location of the source directory + * @return true if the Next.js project uses the next/image component + */ +export async function usesNextImage(sourceDir: string, distDir: string): Promise { + const exportMarker = await readJSON(join(sourceDir, distDir, "export-marker.json")); + return exportMarker.isNextImageImported; +} + +/** + * Check if Next.js is forced to serve the source image as-is instead of being oprimized + * by setting `unoptimized: true` in next.config.js. + * https://nextjs.org/docs/api-reference/next/image#unoptimized + * + * @param sourceDir location of the source directory + * @param distDir location of the dist directory + * @return true if image optimization is disabled + */ +export async function hasUnoptimizedImage(sourceDir: string, distDir: string): Promise { + const imageManifest = await readJSON( + join(sourceDir, distDir, "images-manifest.json") + ); + return imageManifest.images.unoptimized; +} diff --git a/src/test/frameworks/next/utils.spec.ts b/src/test/frameworks/next/utils.spec.ts index 06c0e995b25..f8cecfc6870 100644 --- a/src/test/frameworks/next/utils.spec.ts +++ b/src/test/frameworks/next/utils.spec.ts @@ -1,4 +1,7 @@ import { expect } from "chai"; +import * as fs from "fs"; +import * as fsExtra from "fs-extra"; +import * as sinon from "sinon"; import { pathHasRegex, @@ -7,6 +10,9 @@ import { isRedirectSupportedByFirebase, isHeaderSupportedByFirebase, getNextjsRewritesToUse, + usesAppDirRouter, + usesNextImage, + hasUnoptimizedImage, } from "../../../frameworks/next/utils"; import { pathsAsGlobs, @@ -142,4 +148,78 @@ describe("Next.js utils", () => { expect(rewritesToUse).to.have.length(supportedRewritesArray.length); }); }); + + describe("usesAppDirRouter", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should return false when app dir doesn't exist", () => { + sandbox.stub(fs, "existsSync").returns(false); + expect(usesAppDirRouter("")).to.be.false; + }); + + it("should return true when app dir does exist", () => { + sandbox.stub(fs, "existsSync").returns(true); + expect(usesAppDirRouter("")).to.be.true; + }); + }); + + describe("usesNextImage", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should return true when export marker has isNextImageImported", async () => { + sandbox.stub(fsExtra, "readJSON").resolves({ + isNextImageImported: true, + }); + expect(await usesNextImage("", "")).to.be.true; + }); + + it("should return false when export marker has !isNextImageImported", async () => { + sandbox.stub(fsExtra, "readJSON").resolves({ + isNextImageImported: false, + }); + expect(await usesNextImage("", "")).to.be.false; + }); + }); + + describe("hasUnoptimizedImage", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should return true when images manfiest indicates unoptimized", async () => { + sandbox.stub(fsExtra, "readJSON").resolves({ + images: { unoptimized: true }, + }); + expect(await hasUnoptimizedImage("", "")).to.be.true; + }); + + it("should return true when images manfiest indicates !unoptimized", async () => { + sandbox.stub(fsExtra, "readJSON").resolves({ + images: { unoptimized: false }, + }); + expect(await hasUnoptimizedImage("", "")).to.be.false; + }); + }); }); From 891c2f851627e89c759cbc64a4df57e7bd1f11b9 Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Mon, 12 Dec 2022 19:07:30 -0500 Subject: [PATCH 0719/1699] Creates eventarc channel in before deploying custom event function (#5226) * Ensure eventarc channels exist on deploy * create channel before deploying custom event fn * add unit tests * minor comment edits * remove confusing comment about channel name Co-authored-by: Thomas Bouldin --- src/api.ts | 1 + src/deploy/functions/release/fabricator.ts | 45 +++++++- src/deploy/functions/release/reporter.ts | 1 + src/gcp/eventarc.ts | 94 ++++++++++++++++ .../functions/release/fabricator.spec.ts | 100 +++++++++++++++++- 5 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/gcp/eventarc.ts diff --git a/src/api.ts b/src/api.ts index f4e698173b7..0112b2eac56 100644 --- a/src/api.ts +++ b/src/api.ts @@ -58,6 +58,7 @@ export const dynamicLinksKey = utils.envOverride( "FIREBASE_DYNAMIC_LINKS_KEY", "AIzaSyB6PtY5vuiSB8MNgt20mQffkOlunZnHYiQ" ); +export const eventarcOrigin = utils.envOverride("EVENTARC_URL", "https://eventarc.googleapis.com"); export const firebaseApiOrigin = utils.envOverride( "FIREBASE_API_URL", "https://firebase.googleapis.com" diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 310cf82dcb6..9c13eaca235 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -6,7 +6,7 @@ import { SourceTokenScraper } from "./sourceTokenScraper"; import { Timer } from "./timer"; import { assertExhaustive } from "../../../functional"; import { getHumanFriendlyRuntimeName } from "../runtimes"; -import { functionsOrigin, functionsV2Origin } from "../../../api"; +import { eventarcOrigin, functionsOrigin, functionsV2Origin } from "../../../api"; import { logger } from "../../../logger"; import * as args from "../args"; import * as backend from "../backend"; @@ -14,6 +14,7 @@ import * as cloudtasks from "../../../gcp/cloudtasks"; import * as deploymentTool from "../../../deploymentTool"; import * as gcf from "../../../gcp/cloudfunctions"; import * as gcfV2 from "../../../gcp/cloudfunctionsv2"; +import * as eventarc from "../../../gcp/eventarc"; import * as helper from "../functionsDeployHelper"; import * as planner from "./planner"; import * as poller from "../../../operation-poller"; @@ -41,6 +42,13 @@ const gcfV2PollerOptions: Omit = { + apiOrigin: eventarcOrigin, + apiVersion: "v1", + masterTimeout: 25 * 60 * 1_000, // 25 minutes is the maximum build time for a function + maxBackoff: 10_000, +}; + export interface FabricatorArgs { executor: Executor; functionExecutor: Executor; @@ -296,6 +304,37 @@ export class Fabricator { .catch(rethrowAs(endpoint, "create topic")); } + // Like Pub/Sub, GCF requires a channel to exist before allowing the function + // to be created. Like Pub/Sub we currently only support setting the name + // of a channel, so we can do this once during createFunction alone. But if + // Eventarc adds new features that we indulge in (e.g. 2P event providers) + // things will get much more complicated. We'll have to make sure we keep + // up to date on updates, and we will also have to worry about channels leftover + // after deletion possibly incurring bills due to events still being sent. + const channel = apiFunction.eventTrigger?.channel; + if (channel) { + await this.executor + .run(async () => { + try { + const op: { name: string } = await eventarc.createChannel({ name: channel }); + return await poller.pollOperation({ + ...eventarcPollerOptions, + pollerName: `create-${channel}-${endpoint.region}-${endpoint.id}`, + operationResourceName: op.name, + }); + } catch (err: any) { + // if error status is 409, the channel already exists and we can deploy safely + if (err.status === 409) { + return; + } + throw new FirebaseError("Unexpected error creating Eventarc channel", { + original: err as Error, + }); + } + }) + .catch(rethrowAs(endpoint, "upsert eventarc channel")); + } + const resultFunction = await this.functionExecutor .run(async () => { const op: { name: string } = await gcfV2.createFunction(apiFunction); @@ -571,6 +610,10 @@ export class Fabricator { } else if (backend.isBlockingTriggered(endpoint)) { await this.unregisterBlockingTrigger(endpoint); } + // N.B. Like Pub/Sub topics, we don't delete Eventarc channels because we + // don't know if there are any subscriers or not. If we start supporting 2P + // channels, we might need to revist this or else the events will still get + // published and the customer will still get charged. } async upsertScheduleV1(endpoint: backend.Endpoint & backend.ScheduleTriggered): Promise { diff --git a/src/deploy/functions/release/reporter.ts b/src/deploy/functions/release/reporter.ts index 246717c0651..61611c0a7ba 100644 --- a/src/deploy/functions/release/reporter.ts +++ b/src/deploy/functions/release/reporter.ts @@ -25,6 +25,7 @@ export type OperationType = | "upsert schedule" | "delete schedule" | "upsert task queue" + | "upsert eventarc channel" | "disable task queue" | "create topic" | "delete topic" diff --git a/src/gcp/eventarc.ts b/src/gcp/eventarc.ts new file mode 100644 index 00000000000..4ba27f75a1a --- /dev/null +++ b/src/gcp/eventarc.ts @@ -0,0 +1,94 @@ +import { Client } from "../apiv2"; +import { eventarcOrigin } from "../api"; +import { last } from "lodash"; +import { fieldMasks } from "./proto"; + +export const API_VERSION = "v1"; + +export interface Channel { + name: string; + + /** Server-assigned uinique identifier. Format is a UUID4 */ + uid?: string; + + createTime?: string; + updateTime?: string; + + /** If set, the channel will grant publish permissions to the 2P provider. */ + provider?: string; + + // BEGIN oneof transport + pubsubTopic?: string; + // END oneof transport + + state?: "PENDING" | "ACTIVE" | "INACTIVE"; + + /** When the channel is `PENDING`, this token must be sent to the provider */ + activationToken?: string; + + cryptoKeyName?: string; +} + +interface OperationMetadata { + createTime: string; + target: string; + verb: string; + requestedCancellation: boolean; + apiVersion: string; +} + +interface Operation { + name: string; + metadata: OperationMetadata; + done: boolean; +} + +const client = new Client({ + urlPrefix: eventarcOrigin, + auth: true, + apiVersion: API_VERSION, +}); + +/** + * Gets a Channel. + */ +export async function getChannel(name: string): Promise { + const res = await client.get(name); + if (res.status === 404) { + return undefined; + } + return res.body; +} + +/** + * Creates a channel. + */ +export async function createChannel(channel: Channel): Promise { + // const body: Partial = cloneDeep(channel); + const pathParts = channel.name.split("/"); + + const res = await client.post(pathParts.slice(0, -1).join("/"), channel, { + queryParams: { channelId: last(pathParts)! }, + }); + return res.body; +} + +/** + * Updates a channel to match the new spec. + * Only set fields are updated. + */ +export async function updateChannel(channel: Channel): Promise { + const res = await client.put(channel.name, channel, { + queryParams: { + updateMask: fieldMasks(channel).join(","), + }, + }); + return res.body; +} + +/** + * Deletes a channel. + */ +export async function deleteChannel(name: string): Promise { + await client.delete(name); +} diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 1e96655ba5e..6636b7371a1 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -6,6 +6,7 @@ import * as reporter from "../../../../deploy/functions/release/reporter"; import * as executor from "../../../../deploy/functions/release/executor"; import * as gcfNSV2 from "../../../../gcp/cloudfunctionsv2"; import * as gcfNS from "../../../../gcp/cloudfunctions"; +import * as eventarcNS from "../../../../gcp/eventarc"; import * as pollerNS from "../../../../operation-poller"; import * as pubsubNS from "../../../../gcp/pubsub"; import * as schedulerNS from "../../../../gcp/cloudscheduler"; @@ -24,6 +25,7 @@ describe("Fabricator", () => { // Stub all GCP APIs to make sure this test is hermetic let gcf: sinon.SinonStubbedInstance; let gcfv2: sinon.SinonStubbedInstance; + let eventarc: sinon.SinonStubbedInstance; let poller: sinon.SinonStubbedInstance; let pubsub: sinon.SinonStubbedInstance; let scheduler: sinon.SinonStubbedInstance; @@ -35,6 +37,7 @@ describe("Fabricator", () => { beforeEach(() => { gcf = sinon.stub(gcfNS); gcfv2 = sinon.stub(gcfNSV2); + eventarc = sinon.stub(eventarcNS); poller = sinon.stub(pollerNS); pubsub = sinon.stub(pubsubNS); scheduler = sinon.stub(schedulerNS); @@ -58,6 +61,10 @@ describe("Fabricator", () => { gcfv2.createFunction.rejects(new Error("unexpected gcfv2.createFunction")); gcfv2.updateFunction.rejects(new Error("unexpected gcfv2.updateFunction")); gcfv2.deleteFunction.rejects(new Error("unexpected gcfv2.deleteFunction")); + eventarc.createChannel.rejects(new Error("unexpected eventarc.createChannel")); + eventarc.deleteChannel.rejects(new Error("unexpected eventarc.deleteChannel")); + eventarc.getChannel.rejects(new Error("unexpected eventarc.getChannel")); + eventarc.updateChannel.rejects(new Error("unexpected eventarc.updateChannel")); run.getIamPolicy.rejects(new Error("unexpected run.getIamPolicy")); run.setIamPolicy.rejects(new Error("unexpected run.setIamPolicy")); run.setInvokerCreate.rejects(new Error("unexpected run.setInvokerCreate")); @@ -481,6 +488,97 @@ describe("Fabricator", () => { ); }); + it("handles already existing eventarc channels", async () => { + eventarc.createChannel.callsFake(({ name }) => { + expect(name).to.equal("channel"); + const err = new Error("Already exists"); + (err as any).status = 409; + return Promise.reject(err); + }); + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + + const ep = endpoint( + { + eventTrigger: { + eventType: "custom.test.event", + channel: "channel", + retry: false, + }, + }, + { + platform: "gcfv2", + } + ); + + await fab.createV2Function(ep); + expect(eventarc.createChannel).to.have.been.called; + expect(gcfv2.createFunction).to.have.been.called; + }); + + it("creates channels if necessary", async () => { + const channelName = "channel"; + eventarc.createChannel.callsFake(({ name }) => { + expect(name).to.equal(channelName); + return Promise.resolve({ + name: "op-resource-name", + metadata: { + createTime: "", + target: "", + verb: "", + requestedCancellation: false, + apiVersion: "", + }, + done: false, + }); + }); + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + + const ep = endpoint( + { + eventTrigger: { + eventType: "custom.test.event", + channel: channelName, + retry: false, + }, + }, + { + platform: "gcfv2", + } + ); + + await fab.createV2Function(ep); + expect(eventarc.createChannel).to.have.been.calledOnceWith({ name: channelName }); + expect(poller.pollOperation).to.have.been.called; + }); + + it("wraps errors thrown while creating channels", async () => { + eventarc.createChannel.callsFake(() => { + const err = new Error("🤷â€â™‚ï¸"); + (err as any).status = 400; + return Promise.reject(err); + }); + + const ep = endpoint( + { + eventTrigger: { + eventType: "custom.test.event", + channel: "channel", + retry: false, + }, + }, + { + platform: "gcfv2", + } + ); + + await expect(fab.createV2Function(ep)).to.eventually.be.rejectedWith( + reporter.DeploymentError, + "upsert eventarc channel" + ); + }); + it("throws on create function failure", async () => { gcfv2.createFunction.rejects(new Error("Server failure")); @@ -1168,7 +1266,7 @@ describe("Fabricator", () => { await fab.setTrigger(endpoint({ httpsTrigger: {} })); }); - it("does nothing for event triggers", async () => { + it("does nothing for event triggers without channels", async () => { // all APIs throw by default const ep = endpoint({ eventTrigger: { From f8e2975fdf3c1f12682b187976630b1428865db2 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 13 Dec 2022 07:58:02 -0800 Subject: [PATCH 0720/1699] Add support for internal testing-only commands. (#5289) * Add support for internal testing-only commands. * Word smith-ing. --- src/commands/index.ts | 3 +++ src/experiments.ts | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/commands/index.ts b/src/commands/index.ts index 8554f94e727..81c84d6c220 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -143,6 +143,9 @@ export function load(client: any): any { client.hosting.sites.get = loadCommand("hosting-sites-get"); client.hosting.sites.list = loadCommand("hosting-sites-list"); client.init = loadCommand("init"); + if (experiments.isEnabled("internaltesting")) { + client.internaltesting = {}; + } client.login = loadCommand("login"); client.login.add = loadCommand("login-add"); client.login.ci = loadCommand("login-ci"); diff --git a/src/experiments.ts b/src/experiments.ts index 21014746d44..e335807e0ee 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -97,11 +97,17 @@ export const ALL_EXPERIMENTS = experiments({ "if any service exceeds 500 tags, but it is theoretically possible that a project " + "exceeds the region-wide limit of tags and an old site version fails", }, - // Access experiments crossservicerules: { shortDescription: "Allow Firebase Rules to reference resources in other services", }, + internaltesting: { + shortDescription: "Exposes Firebase CLI commands intended for internal testing purposes.", + fullDescription: + "Exposes Firebase CLI commands intended for internal testing purposes. " + + "These commands are not meant for public consumption and may break or disappear " + + "without a notice.", + }, }); export type ExperimentName = keyof typeof ALL_EXPERIMENTS; From 2e886917169dbab975e3c3e2a30c5dec3dd2590e Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 13 Dec 2022 10:09:41 -0800 Subject: [PATCH 0721/1699] Attempt to improve stability of triggers-end-to-end test (#5323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm run test:triggers-end-to-end` test target has been flakey. This PR attempts to improve the situation with couple of changes: 1) Add diagnostic messages to the Functions Emulator to better understand which functions are hanging. 2) Increase shutdown timeout (5s âž¡ï¸ 7s) to give more time for Functions Emulator to shutdown gracefully 3) Reduce the scope of a test target that triggers tens of functions at once - the coverage for the specific feature is still reasonable imo. Runs of test target on this branch has better success rate, though it can still be improved. --- scripts/triggers-end-to-end-tests/tests.ts | 56 ++++++++++++---------- src/emulator/functionsEmulator.ts | 14 ++++-- src/emulator/workQueue.ts | 24 ++++++---- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/scripts/triggers-end-to-end-tests/tests.ts b/scripts/triggers-end-to-end-tests/tests.ts index fc0ee330e21..58b768d3509 100755 --- a/scripts/triggers-end-to-end-tests/tests.ts +++ b/scripts/triggers-end-to-end-tests/tests.ts @@ -22,7 +22,7 @@ const ADMIN_CREDENTIAL = { */ const TEST_SETUP_TIMEOUT = 80000; const EMULATORS_WRITE_DELAY_MS = 5000; -const EMULATORS_SHUTDOWN_DELAY_MS = 5000; +const EMULATORS_SHUTDOWN_DELAY_MS = 7000; const EMULATOR_TEST_TIMEOUT = EMULATORS_WRITE_DELAY_MS * 2; /* @@ -435,7 +435,7 @@ describe("function triggers", () => { test.resetCounts(); }); - it("should disable all background triggers", async function (this) { + it("should disable background triggers", async function (this) { this.timeout(TEST_SETUP_TIMEOUT); const response = await test.disableBackgroundTriggers(); @@ -444,26 +444,27 @@ describe("function triggers", () => { await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); await Promise.all([ - test.writeToRtdb(), - test.writeToFirestore(), - test.writeToPubsub(), + // TODO(danielylee): Trying to respond to all triggers at once often results in Functions + // Emulator hanging indefinitely. Only triggering 1 trigger for now. Re-enable other triggers + // once the root cause is identified. + // test.writeToRtdb(), + // test.writeToFirestore(), + // test.writeToPubsub(), + // test.writeToDefaultStorage(), test.writeToAuth(), - test.writeToDefaultStorage(), ]); await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS * 2)); - expect(test.rtdbTriggerCount).to.equal(0); - expect(test.rtdbV2TriggerCount).to.eq(0); - expect(test.firestoreTriggerCount).to.equal(0); - expect(test.pubsubTriggerCount).to.equal(0); - expect(test.pubsubV2TriggerCount).to.equal(0); + // expect(test.rtdbTriggerCount).to.equal(0); + // expect(test.rtdbV2TriggerCount).to.eq(0); + // expect(test.firestoreTriggerCount).to.equal(0); + // expect(test.pubsubTriggerCount).to.equal(0); + // expect(test.pubsubV2TriggerCount).to.equal(0); expect(test.authTriggerCount).to.equal(0); - expect(test.storageFinalizedTriggerCount).to.equal(0); - expect(test.storageV2FinalizedTriggerCount).to.equal(0); }); - it("should re-enable all background triggers", async function (this) { + it("should re-enable background triggers", async function (this) { this.timeout(TEST_SETUP_TIMEOUT); const response = await test.enableBackgroundTriggers(); @@ -472,23 +473,26 @@ describe("function triggers", () => { await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS)); await Promise.all([ - test.writeToRtdb(), - test.writeToFirestore(), - test.writeToPubsub(), + // TODO(danielylee): Trying to respond to all triggers at once often results in Functions + // Emulator hanging indefinitely. Only triggering 1 trigger for now. Re-enable other triggers + // once the root cause is identified. + // test.writeToRtdb(), + // test.writeToFirestore(), + // test.writeToPubsub(), + // test.writeToDefaultStorage(), test.writeToAuth(), - test.writeToDefaultStorage(), ]); await new Promise((resolve) => setTimeout(resolve, EMULATORS_WRITE_DELAY_MS * 3)); - - expect(test.rtdbTriggerCount).to.equal(1); - expect(test.rtdbV2TriggerCount).to.eq(1); - expect(test.firestoreTriggerCount).to.equal(1); - expect(test.pubsubTriggerCount).to.equal(1); - expect(test.pubsubV2TriggerCount).to.equal(1); + // TODO(danielylee): Trying to respond to all triggers at once often results in Functions + // Emulator hanging indefinitely. Only triggering 1 trigger for now. Re-enable other triggers + // once the root cause is identified. + // expect(test.rtdbTriggerCount).to.equal(1); + // expect(test.rtdbV2TriggerCount).to.eq(1); + // expect(test.firestoreTriggerCount).to.equal(1); + // expect(test.pubsubTriggerCount).to.equal(1); + // expect(test.pubsubV2TriggerCount).to.equal(1); expect(test.authTriggerCount).to.equal(1); - expect(test.storageFinalizedTriggerCount).to.equal(1); - expect(test.storageV2FinalizedTriggerCount).to.equal(1); }); }); }); diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 44ff3151ee3..6f6f70b6167 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -41,7 +41,7 @@ import { EmulatorLogger, Verbosity } from "./emulatorLogger"; import { RuntimeWorker, RuntimeWorkerPool } from "./functionsRuntimeWorker"; import { PubsubEmulator } from "./pubsubEmulator"; import { FirebaseError } from "../error"; -import { WorkQueue } from "./workQueue"; +import { WorkQueue, Work } from "./workQueue"; import { allSettled, connectableHostname, createDestroyer, debounce } from "../utils"; import { getCredentialPathAsync } from "../defaultCredentials"; import { @@ -269,9 +269,11 @@ export class FunctionsEmulator implements EmulatorInstance { const listBackendsRoute = `/backends`; const httpsHandler: express.RequestHandler = (req, res) => { - this.workQueue.submit(() => { + const work: Work = () => { return this.handleHttpsTrigger(req, res); - }); + }; + work.type = `${req.path}-${new Date().toISOString()}`; + this.workQueue.submit(work); }; const multicastHandler: express.RequestHandler = (req: express.Request, res) => { @@ -291,7 +293,7 @@ export class FunctionsEmulator implements EmulatorInstance { const { host, port } = this.getInfo(); triggers.forEach((triggerId) => { - this.workQueue.submit(() => { + const work: Work = () => { return new Promise((resolve, reject) => { const trigReq = http.request({ host: connectableHostname(host), @@ -305,7 +307,9 @@ export class FunctionsEmulator implements EmulatorInstance { trigReq.end(); resolve(); }); - }); + }; + work.type = `${triggerId}-${new Date().toISOString()}`; + this.workQueue.submit(work); }); res.json({ status: "multicast_acknowledged" }); }; diff --git a/src/emulator/workQueue.ts b/src/emulator/workQueue.ts index ec9bfbb39a4..bcfeb44a973 100644 --- a/src/emulator/workQueue.ts +++ b/src/emulator/workQueue.ts @@ -4,7 +4,10 @@ import { FirebaseError } from "../error"; import { EmulatorLogger } from "./emulatorLogger"; import { Emulators, FunctionsExecutionMode } from "./types"; -type Work = () => Promise; +export type Work = { + type?: string; + (): Promise; +}; /** * Queue for doing async work that can either run all work concurrently @@ -23,7 +26,7 @@ export class WorkQueue { private logger = EmulatorLogger.forEmulator(Emulators.FUNCTIONS); private queue: Array = []; - private workRunningCount: number = 0; + private running: Array = []; private notifyQueue: () => void = () => { // Noop by default, will be set by .start() when queue is empty. }; @@ -74,11 +77,11 @@ export class WorkQueue { } // If we have too many jobs out, wait until something finishes. - if (this.workRunningCount >= this.maxParallelWork) { + if (this.running.length >= this.maxParallelWork) { this.logger.logLabeled( "DEBUG", "work-queue", - `waiting for work to finish (running=${this.workRunningCount})` + `waiting for work to finish (running=${this.running})` ); await new Promise((res) => { this.notifyWorkFinish = res; @@ -99,7 +102,7 @@ export class WorkQueue { this.stopped = true; } - async flush(timeoutMs: number = 60000) { + async flush(timeoutMs = 60000) { if (!this.isWorking()) { return; } @@ -125,8 +128,10 @@ export class WorkQueue { getState() { return { + queuedWork: this.queue.map((work) => work.type), queueLength: this.queue.length, - workRunningCount: this.workRunningCount, + runningWork: this.running, + workRunningCount: this.running.length, }; } @@ -138,7 +143,7 @@ export class WorkQueue { private async runNext() { const next = this.queue.shift(); if (next) { - this.workRunningCount++; + this.running.push(next.type || "anonymous"); this.logState(); try { @@ -146,7 +151,10 @@ export class WorkQueue { } catch (e: any) { this.logger.log("DEBUG", e); } finally { - this.workRunningCount--; + const index = this.running.indexOf(next.type || "anonymous"); + if (index !== -1) { + this.running.splice(index, 1); + } this.notifyWorkFinish(); this.logState(); } From 87c71fac2ba08715ddfc13fa4fc260f25afb90e2 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 13 Dec 2022 10:51:23 -0800 Subject: [PATCH 0722/1699] Add new command for discoverying function triggers. (#5213) * Add support for internal testing-only commands. * Word smith-ing. * Add new internal command for discovering function triggers in a project. * Remove duplicate command file. * Add command to command index. * Address PR comments. --- src/commands/index.ts | 2 + .../internaltesting-functions-discover.ts | 26 +++++ src/deploy/functions/args.ts | 6 +- src/deploy/functions/params.ts | 4 +- src/deploy/functions/prepare.ts | 106 ++++++++++++------ 5 files changed, 105 insertions(+), 39 deletions(-) create mode 100644 src/commands/internaltesting-functions-discover.ts diff --git a/src/commands/index.ts b/src/commands/index.ts index 81c84d6c220..957c666aead 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -145,6 +145,8 @@ export function load(client: any): any { client.init = loadCommand("init"); if (experiments.isEnabled("internaltesting")) { client.internaltesting = {}; + client.internaltesting.functions = {}; + client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover"); } client.login = loadCommand("login"); client.login.add = loadCommand("login-add"); diff --git a/src/commands/internaltesting-functions-discover.ts b/src/commands/internaltesting-functions-discover.ts new file mode 100644 index 00000000000..fca67443b2f --- /dev/null +++ b/src/commands/internaltesting-functions-discover.ts @@ -0,0 +1,26 @@ +import { Command } from "../command"; +import { Options } from "../options"; +import { logger } from "../logger"; +import { loadCodebases } from "../deploy/functions/prepare"; +import { normalizeAndValidate } from "../functions/projectConfig"; +import { getProjectAdminSdkConfigOrCached } from "../emulator/adminSdkConfig"; +import { needProjectId } from "../projectUtils"; +import { FirebaseError } from "../error"; + +export const command = new Command("internaltesting:functions:discover") + .description("discover function triggers defined in the current project directory") + .action(async (options: Options) => { + const projectId = needProjectId(options); + const fnConfig = normalizeAndValidate(options.config.src.functions); + const firebaseConfig = await getProjectAdminSdkConfigOrCached(projectId); + if (!firebaseConfig) { + throw new FirebaseError( + "Admin SDK config unexpectedly undefined - have you run firebase init?" + ); + } + const builds = await loadCodebases(fnConfig, options, firebaseConfig, { + firebase: firebaseConfig, + }); + logger.info(JSON.stringify(builds, null, 2)); + return builds; + }); diff --git a/src/deploy/functions/args.ts b/src/deploy/functions/args.ts index ee02675bfd7..1138b9d9491 100644 --- a/src/deploy/functions/args.ts +++ b/src/deploy/functions/args.ts @@ -52,8 +52,8 @@ export interface Context { } export interface FirebaseConfig { - locationId: string; + locationId?: string; projectId: string; - storageBucket: string; - databaseURL: string; + storageBucket?: string; + databaseURL?: string; } diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 9059607ba41..ef67bcb72a5 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -343,7 +343,7 @@ export async function resolveParams( function populateDefaultParams(config: FirebaseConfig): Record { const defaultParams: Record = {}; - if (config.databaseURL !== "") { + if (config.databaseURL && config.databaseURL !== "") { defaultParams["DATABASE_URL"] = new ParamValue(config.databaseURL, true, { string: true, boolean: false, @@ -360,7 +360,7 @@ function populateDefaultParams(config: FirebaseConfig): Record = {}; - for (const codebase of codebases) { - logLabeledBullet("functions", `preparing codebase ${clc.bold(codebase)} for deployment`); - + for (const [codebase, wantBuild] of Object.entries(wantBuilds)) { const config = configForCodebase(context.config, codebase); - const sourceDirName = config.source; - if (!sourceDirName) { - throw new FirebaseError( - `No functions code detected at default location (./functions), and no functions source defined in firebase.json` - ); - } - const sourceDir = options.config.path(sourceDirName); - const delegateContext: runtimes.DelegateContext = { - projectId, - sourceDir, - projectDir: options.config.projectDir, - runtime: config.runtime || "", - }; - const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext); - logger.debug(`Validating ${runtimeDelegate.name} source`); - await runtimeDelegate.validate(); - logger.debug(`Building ${runtimeDelegate.name} source`); - await runtimeDelegate.build(); - const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); const userEnvOpt: functionsEnv.UserEnvsOpts = { - functionsSource: sourceDir, + functionsSource: options.config.path(config.source), projectId: projectId, projectAlias: options.projectAlias, }; const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt); const envs = { ...userEnvs, ...firebaseEnvs }; - const wantBuild: build.Build = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs); + const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend( wantBuild, firebaseConfig, @@ -171,10 +165,11 @@ export async function prepare( } } - // ===Phase 1.5. Before proceeding further, let's make sure that we don't have conflicting function names. + // ===Phase 2.5. Before proceeding further, let's make sure that we don't have conflicting function names. validate.endpointsAreUnique(wantBackends); - // ===Phase 2. Prepare source for upload. + // ===Phase 3. Prepare source for upload. + context.sources = {}; for (const [codebase, wantBackend] of Object.entries(wantBackends)) { const config = configForCodebase(context.config, codebase); const sourceDirName = config.source; @@ -199,7 +194,7 @@ export async function prepare( context.sources[codebase] = source; } - // ===Phase 3. Fill in details and validate endpoints. We run the check for ALL endpoints - we think it's useful for + // ===Phase 4. Fill in details and validate endpoints. We run the check for ALL endpoints - we think it's useful for // validations to fail even for endpoints that aren't being deployed so any errors are caught early. payload.functions = {}; const haveBackends = groupEndpointsByCodebase( @@ -230,7 +225,7 @@ export async function prepare( const codebaseCnt = Object.keys(payload.functions).length; void track("functions_codebase_deploy_count", codebaseCnt >= 5 ? "5+" : codebaseCnt.toString()); - // ===Phase 4. Enable APIs required by the deploying backends. + // ===Phase 5. Enable APIs required by the deploying backends. const wantBackend = backend.merge(...Object.values(wantBackends)); const haveBackend = backend.merge(...Object.values(haveBackends)); @@ -265,7 +260,7 @@ export async function prepare( await Promise.all(generateServiceAccounts); } - // ===Phase 5. Ask for user prompts for things might warrant user attentions. + // ===Phase 6. Ask for user prompts for things might warrant user attentions. // We limit the scope endpoints being deployed. const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => { return endpointMatchesAnyFilter(endpoint, context.filters); @@ -273,7 +268,7 @@ export async function prepare( await promptForFailurePolicies(options, matchingBackend, haveBackend); await promptForMinInstances(options, matchingBackend, haveBackend); - // ===Phase 6. Finalize preparation by "fixing" all extraneous environment issues like IAM policies. + // ===Phase 7. Finalize preparation by "fixing" all extraneous environment issues like IAM policies. // We limit the scope endpoints being deployed. await backend.checkAvailability(context, matchingBackend); await ensureServiceAgentRoles(projectId, projectNumber, matchingBackend, haveBackend); @@ -281,7 +276,7 @@ export async function prepare( await ensure.secretAccess(projectId, matchingBackend, haveBackend); /** - * ===Phase 7 Generates the hashes for each of the functions now that secret versions have been resolved. + * ===Phase 8 Generates the hashes for each of the functions now that secret versions have been resolved. * This must be called after `await validate.secretsAreValid`. */ updateEndpointTargetedStatus(wantBackends, context.filters || []); @@ -422,3 +417,46 @@ export function resolveCpuAndConcurrency(want: backend.Backend): void { } } } + +/** + * Exported for use by an internal command (internaltesting:functions:discover) only. + * + * @internal + */ +export async function loadCodebases( + config: ValidatedConfig, + options: Options, + firebaseConfig: args.FirebaseConfig, + runtimeConfig: Record, + filters?: EndpointFilter[] +): Promise> { + const codebases = targetCodebases(config, filters); + const projectId = needProjectId(options); + + const wantBuilds: Record = {}; + for (const codebase of codebases) { + const codebaseConfig = configForCodebase(config, codebase); + const sourceDirName = codebaseConfig.source; + if (!sourceDirName) { + throw new FirebaseError( + `No functions code detected at default location (./functions), and no functions source defined in firebase.json` + ); + } + const sourceDir = options.config.path(sourceDirName); + const delegateContext: runtimes.DelegateContext = { + projectId, + sourceDir, + projectDir: options.config.projectDir, + runtime: codebaseConfig.runtime || "", + }; + const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext); + logger.debug(`Validating ${runtimeDelegate.name} source`); + await runtimeDelegate.validate(); + logger.debug(`Building ${runtimeDelegate.name} source`); + await runtimeDelegate.build(); + + const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); + wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs); + } + return wantBuilds; +} From 7bcbf8e7fb32cafd542843bc2720a607c6685f72 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 13 Dec 2022 10:53:23 -0800 Subject: [PATCH 0723/1699] Add integration test for discovering functions from a project repository. (#5214) * Add support for internal testing-only commands. * Word smith-ing. * Add new internal command for discovering function triggers in a project. * Remove duplicate command file. * Add command to command index. * Address PR comments. * Add integration test for discovering functions from a project repository. * Add integration test for discovering functions from a project repository. * Update test to use new internal command. --- .github/workflows/node-test.yml | 2 + package.json | 1 + .../fixtures/codebases/firebase.json | 26 ++++++ .../fixtures/codebases/install.sh | 6 ++ .../fixtures/codebases/v1/index.js | 5 ++ .../fixtures/codebases/v1/package.json | 9 ++ .../fixtures/codebases/v2/index.js | 5 ++ .../fixtures/codebases/v2/package.json | 10 +++ .../fixtures/esm/firebase.json | 1 + .../fixtures/esm/functions/index.js | 11 +++ .../fixtures/esm/functions/package.json | 10 +++ .../fixtures/esm/install.sh | 5 ++ .../fixtures/simple/firebase.json | 1 + .../fixtures/simple/functions/index.js | 10 +++ .../fixtures/simple/functions/package.json | 9 ++ .../fixtures/simple/install.sh | 5 ++ scripts/functions-discover-tests/run.sh | 15 ++++ scripts/functions-discover-tests/tests.ts | 87 +++++++++++++++++++ 18 files changed, 218 insertions(+) create mode 100644 scripts/functions-discover-tests/fixtures/codebases/firebase.json create mode 100755 scripts/functions-discover-tests/fixtures/codebases/install.sh create mode 100644 scripts/functions-discover-tests/fixtures/codebases/v1/index.js create mode 100644 scripts/functions-discover-tests/fixtures/codebases/v1/package.json create mode 100644 scripts/functions-discover-tests/fixtures/codebases/v2/index.js create mode 100644 scripts/functions-discover-tests/fixtures/codebases/v2/package.json create mode 100644 scripts/functions-discover-tests/fixtures/esm/firebase.json create mode 100644 scripts/functions-discover-tests/fixtures/esm/functions/index.js create mode 100644 scripts/functions-discover-tests/fixtures/esm/functions/package.json create mode 100755 scripts/functions-discover-tests/fixtures/esm/install.sh create mode 100644 scripts/functions-discover-tests/fixtures/simple/firebase.json create mode 100644 scripts/functions-discover-tests/fixtures/simple/functions/index.js create mode 100644 scripts/functions-discover-tests/fixtures/simple/functions/package.json create mode 100755 scripts/functions-discover-tests/fixtures/simple/install.sh create mode 100755 scripts/functions-discover-tests/run.sh create mode 100644 scripts/functions-discover-tests/tests.ts diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 5e72bc66f0f..9783bd01ec9 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -85,6 +85,7 @@ jobs: - npm run test:emulator - npm run test:extensions-emulator - npm run test:frameworks + - npm run test:functions-discover - npm run test:hosting # - npm run test:hosting-rewrites # Long-running test that might conflict across test runs. Run this manually. - npm run test:import-export @@ -142,6 +143,7 @@ jobs: - npm run test:emulator # - npm run test:import-export # Fails becuase port 4000 is taken after first run - hub not shhutting down? # - npm run test:extensions-emulator # Fails due to cannot find module sharp (not waiting for npm install?) + - npm run test:functions-discover - npm run test:triggers-end-to-end - npm run test:triggers-end-to-end:inspect - npm run test:storage-deploy diff --git a/package.json b/package.json index 35937a21d6e..bdfc2de6125 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "test:extensions-emulator": "bash ./scripts/extensions-emulator-tests/run.sh", "test:frameworks": "bash ./scripts/frameworks-tests/run.sh", "test:functions-deploy": "bash ./scripts/functions-deploy-tests/run.sh", + "test:functions-discover": "bash ./scripts/functions-discover-tests/run.sh", "test:hosting": "bash ./scripts/hosting-tests/run.sh", "test:hosting-rewrites": "bash ./scripts/hosting-tests/rewrites-tests/run.sh", "test:import-export": "bash ./scripts/emulator-import-export-tests/run.sh", diff --git a/scripts/functions-discover-tests/fixtures/codebases/firebase.json b/scripts/functions-discover-tests/fixtures/codebases/firebase.json new file mode 100644 index 00000000000..14ded1f8d5a --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/codebases/firebase.json @@ -0,0 +1,26 @@ +{ + "functions": [ + { + "source": "v1", + "codebase": "v1", + "runtime": "nodejs16", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ] + }, + { + "source": "v2", + "codebase": "v2", + "runtime": "nodejs16", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ] + } + ] +} \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/codebases/install.sh b/scripts/functions-discover-tests/fixtures/codebases/install.sh new file mode 100755 index 00000000000..b15011a2b4d --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/codebases/install.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euxo pipefail # bash strict mode +IFS=$'\n\t' + +(cd v1 && npm i) +(cd v2 && npm i) diff --git a/scripts/functions-discover-tests/fixtures/codebases/v1/index.js b/scripts/functions-discover-tests/fixtures/codebases/v1/index.js new file mode 100644 index 00000000000..f5d2f549a3c --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/codebases/v1/index.js @@ -0,0 +1,5 @@ +const functions = require("firebase-functions"); + +exports.hellov1 = functions.https.onRequest((request, response) => { + response.send("Hello from Firebase!"); +}); diff --git a/scripts/functions-discover-tests/fixtures/codebases/v1/package.json b/scripts/functions-discover-tests/fixtures/codebases/v1/package.json new file mode 100644 index 00000000000..adf0b75f8b1 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/codebases/v1/package.json @@ -0,0 +1,9 @@ +{ + "name": "codebase-v1", + "dependencies": { + "firebase-functions": "^4.0.0" + }, + "engines": { + "node": "16" + } +} diff --git a/scripts/functions-discover-tests/fixtures/codebases/v2/index.js b/scripts/functions-discover-tests/fixtures/codebases/v2/index.js new file mode 100644 index 00000000000..99f5c72ba74 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/codebases/v2/index.js @@ -0,0 +1,5 @@ +import { onRequest } from "firebase-functions/v2/https"; + +export const hellov2 = onRequest((request, response) => { + response.send("Hello from Firebase!"); +}); diff --git a/scripts/functions-discover-tests/fixtures/codebases/v2/package.json b/scripts/functions-discover-tests/fixtures/codebases/v2/package.json new file mode 100644 index 00000000000..77e5ecf8296 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/codebases/v2/package.json @@ -0,0 +1,10 @@ +{ + "name": "codebase-v2", + "type": "module", + "dependencies": { + "firebase-functions": "^4.0.0" + }, + "engines": { + "node": "16" + } +} diff --git a/scripts/functions-discover-tests/fixtures/esm/firebase.json b/scripts/functions-discover-tests/fixtures/esm/firebase.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/esm/firebase.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/esm/functions/index.js b/scripts/functions-discover-tests/fixtures/esm/functions/index.js new file mode 100644 index 00000000000..e4e1f5e78de --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/esm/functions/index.js @@ -0,0 +1,11 @@ +import * as functions from "firebase-functions"; +import { onRequest } from "firebase-functions/v2/https"; + +export const hellov1 = functions.https.onRequest((request, response) => { + functions.logger.info("Hello logs!", { structuredData: true }); + response.send("Hello from Firebase!"); +}); + +export const hellov2 = onRequest((request, response) => { + response.send("Hello from Firebase!"); +}); diff --git a/scripts/functions-discover-tests/fixtures/esm/functions/package.json b/scripts/functions-discover-tests/fixtures/esm/functions/package.json new file mode 100644 index 00000000000..8dae4f69724 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/esm/functions/package.json @@ -0,0 +1,10 @@ +{ + "name": "esm", + "type": "module", + "dependencies": { + "firebase-functions": "^4.0.0" + }, + "engines": { + "node": "16" + } +} diff --git a/scripts/functions-discover-tests/fixtures/esm/install.sh b/scripts/functions-discover-tests/fixtures/esm/install.sh new file mode 100755 index 00000000000..21c35208cf2 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/esm/install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -euxo pipefail # bash strict mode +IFS=$'\n\t' + +cd functions && npm i diff --git a/scripts/functions-discover-tests/fixtures/simple/firebase.json b/scripts/functions-discover-tests/fixtures/simple/firebase.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/simple/firebase.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/simple/functions/index.js b/scripts/functions-discover-tests/fixtures/simple/functions/index.js new file mode 100644 index 00000000000..cf0342ca53a --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/simple/functions/index.js @@ -0,0 +1,10 @@ +const functions = require("firebase-functions"); +const { onRequest } = require("firebase-functions/v2/https"); + +exports.hellov1 = functions.https.onRequest((request, response) => { + response.send("Hello from Firebase!"); +}); + +exports.hellov2 = onRequest((request, response) => { + response.send("Hello from Firebase!"); +}); diff --git a/scripts/functions-discover-tests/fixtures/simple/functions/package.json b/scripts/functions-discover-tests/fixtures/simple/functions/package.json new file mode 100644 index 00000000000..c72ddec5aa1 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/simple/functions/package.json @@ -0,0 +1,9 @@ +{ + "name": "simple", + "dependencies": { + "firebase-functions": "^4.0.0" + }, + "engines": { + "node": "16" + } +} diff --git a/scripts/functions-discover-tests/fixtures/simple/install.sh b/scripts/functions-discover-tests/fixtures/simple/install.sh new file mode 100755 index 00000000000..21c35208cf2 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/simple/install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -euxo pipefail # bash strict mode +IFS=$'\n\t' + +cd functions && npm i diff --git a/scripts/functions-discover-tests/run.sh b/scripts/functions-discover-tests/run.sh new file mode 100755 index 00000000000..d5b26c2d79b --- /dev/null +++ b/scripts/functions-discover-tests/run.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euxo pipefail # bash strict mode +IFS=$'\n\t' + +# Globally link the CLI for the testing framework +./scripts/npm-link.sh + +# Unlock internal commands for discovering functions in a project. +firebase experiments:enable internaltesting + +for dir in ./scripts/functions-discover-tests/fixtures/*; do + (cd $dir && ./install.sh) +done + +mocha scripts/functions-discover-tests/tests.ts \ No newline at end of file diff --git a/scripts/functions-discover-tests/tests.ts b/scripts/functions-discover-tests/tests.ts new file mode 100644 index 00000000000..ec15ddc2984 --- /dev/null +++ b/scripts/functions-discover-tests/tests.ts @@ -0,0 +1,87 @@ +import * as path from "path"; + +import { expect } from "chai"; +import { CLIProcess } from "../integration-helpers/cli"; + +const FIXTURES = path.join(__dirname, "fixtures"); +const FIREBASE_PROJECT = "demo-project"; + +interface Testcase { + name: string; + projectDir: string; + expects: { + codebase: string; + endpoints: string[]; + }[]; +} + +describe("Function discovery test", function (this) { + this.timeout(1000_000); + + before(() => { + expect(FIREBASE_PROJECT).to.exist.and.not.be.empty; + }); + + const testCases: Testcase[] = [ + { + name: "simple", + projectDir: "simple", + expects: [ + { + codebase: "default", + endpoints: ["hellov1", "hellov2"], + }, + ], + }, + { + name: "esm", + projectDir: "esm", + expects: [ + { + codebase: "default", + endpoints: ["hellov1", "hellov2"], + }, + ], + }, + { + name: "codebases", + projectDir: "codebases", + expects: [ + { + codebase: "v1", + endpoints: ["hellov1"], + }, + { + codebase: "v2", + endpoints: ["hellov2"], + }, + ], + }, + ]; + + for (const tc of testCases) { + it(`discovers functions in a ${tc.name} project`, async () => { + const cli = new CLIProcess("default", path.join(FIXTURES, tc.projectDir)); + + let output: any; + await cli.start( + "internaltesting:functions:discover", + FIREBASE_PROJECT, + ["--json"], + (data: any) => { + output = JSON.parse(data); + return true; + } + ); + expect(output.status).to.equal("success"); + for (const e of tc.expects) { + const endpoints = output.result?.[e.codebase]?.endpoints; + expect(endpoints).to.be.an("object").that.is.not.empty; + expect(Object.keys(endpoints)).to.have.length(e.endpoints.length); + expect(Object.keys(endpoints)).to.include.members(e.endpoints); + } + + await cli.stop(); + }); + } +}); From 355e9b986ed4c947757666c08b128cf22e5cb092 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 13 Dec 2022 11:12:23 -0800 Subject: [PATCH 0724/1699] Fix bug where function discovery failed on projects using yarn workspaces. (#5215) --- .../fixtures/yarn-workspaces/firebase.json | 5 ++ .../fixtures/yarn-workspaces/install.sh | 5 ++ .../fixtures/yarn-workspaces/package.json | 5 ++ .../yarn-workspaces/packages/a/index.js | 1 + .../yarn-workspaces/packages/a/package.json | 4 + .../packages/functions/index.js | 11 +++ .../packages/functions/package.json | 12 +++ scripts/functions-discover-tests/run.sh | 3 + scripts/functions-discover-tests/tests.ts | 10 +++ src/deploy/functions/runtimes/node/index.ts | 4 +- .../functions/runtimes/node/versioning.ts | 77 +++++++++++++------ 11 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 scripts/functions-discover-tests/fixtures/yarn-workspaces/firebase.json create mode 100755 scripts/functions-discover-tests/fixtures/yarn-workspaces/install.sh create mode 100644 scripts/functions-discover-tests/fixtures/yarn-workspaces/package.json create mode 100644 scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/index.js create mode 100644 scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/package.json create mode 100644 scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/index.js create mode 100644 scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/package.json diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/firebase.json b/scripts/functions-discover-tests/fixtures/yarn-workspaces/firebase.json new file mode 100644 index 00000000000..8189bc54b80 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/firebase.json @@ -0,0 +1,5 @@ +{ + "functions": { + "source": "packages/functions" + } +} \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/install.sh b/scripts/functions-discover-tests/fixtures/yarn-workspaces/install.sh new file mode 100755 index 00000000000..9e1c5a2ab0d --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -euxo pipefail # bash strict mode +IFS=$'\n\t' + +yarn install \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/package.json b/scripts/functions-discover-tests/fixtures/yarn-workspaces/package.json new file mode 100644 index 00000000000..01514ba5512 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/package.json @@ -0,0 +1,5 @@ +{ + "name": "yarn-workspace", + "private": true, + "workspaces": ["packages/functions", "packages/a"] +} \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/index.js b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/index.js new file mode 100644 index 00000000000..f0dc690cd82 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/index.js @@ -0,0 +1 @@ +exports.msg = "Hello world!"; diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/package.json b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/package.json new file mode 100644 index 00000000000..2330fc5f4c0 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/package.json @@ -0,0 +1,4 @@ +{ + "name": "a", + "version": "0.0.1" +} diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/index.js b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/index.js new file mode 100644 index 00000000000..53008415880 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/index.js @@ -0,0 +1,11 @@ +const functions = require("firebase-functions"); +const { onRequest } = require("firebase-functions/v2/https"); +const { msg } = require("a"); + +exports.hellov1 = functions.https.onRequest((request, response) => { + response.send(msg); +}); + +exports.hellov2 = onRequest((request, response) => { + response.send(msg); +}); diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/package.json b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/package.json new file mode 100644 index 00000000000..b2e02c8e3f7 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/package.json @@ -0,0 +1,12 @@ +{ + "name": "simple", + "version": "0.0.1", + "dependencies": { + "firebase-functions": "4.0.0", + "firebase-admin": "^11.2.0", + "a": "latest" + }, + "engines": { + "node": "16" + } +} diff --git a/scripts/functions-discover-tests/run.sh b/scripts/functions-discover-tests/run.sh index d5b26c2d79b..0cd21daaec0 100755 --- a/scripts/functions-discover-tests/run.sh +++ b/scripts/functions-discover-tests/run.sh @@ -8,6 +8,9 @@ IFS=$'\n\t' # Unlock internal commands for discovering functions in a project. firebase experiments:enable internaltesting +# Install yarn +npm i -g yarn + for dir in ./scripts/functions-discover-tests/fixtures/*; do (cd $dir && ./install.sh) done diff --git a/scripts/functions-discover-tests/tests.ts b/scripts/functions-discover-tests/tests.ts index ec15ddc2984..769a0e5ec04 100644 --- a/scripts/functions-discover-tests/tests.ts +++ b/scripts/functions-discover-tests/tests.ts @@ -57,6 +57,16 @@ describe("Function discovery test", function (this) { }, ], }, + { + name: "yarn-workspaces", + projectDir: "yarn-workspaces", + expects: [ + { + codebase: "default", + endpoints: ["hellov1", "hellov2"], + }, + ], + }, ]; for (const tc of testCases) { diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 8405aa84aa5..84cec2703a8 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -65,9 +65,9 @@ export class Delegate { // Using a caching interface because we (may/will) eventually depend on the SDK version // to decide whether to use the JS export method of discovery or the HTTP container contract // method of discovery. - _sdkVersion = ""; + _sdkVersion: string | undefined = undefined; get sdkVersion() { - if (!this._sdkVersion) { + if (this._sdkVersion === undefined) { this._sdkVersion = versioning.getFunctionsSDKVersion(this.sourceDir) || ""; } return this._sdkVersion; diff --git a/src/deploy/functions/runtimes/node/versioning.ts b/src/deploy/functions/runtimes/node/versioning.ts index 93630ed2d73..1ea2fd7de48 100644 --- a/src/deploy/functions/runtimes/node/versioning.ts +++ b/src/deploy/functions/runtimes/node/versioning.ts @@ -1,4 +1,6 @@ -import * as _ from "lodash"; +import * as fs from "fs"; +import * as path from "path"; + import * as clc from "colorette"; import * as semver from "semver"; import * as spawn from "cross-spawn"; @@ -7,17 +9,6 @@ import * as utils from "../../../../utils"; import { logger } from "../../../../logger"; import { track } from "../../../../track"; -interface NpmListResult { - name: string; - dependencies: { - "firebase-functions": { - version: string; - from: string; - resolved: string; - }; - }; -} - interface NpmShowResult { "dist-tags": { latest: string; @@ -34,30 +25,66 @@ export const FUNCTIONS_SDK_VERSION_TOO_OLD_WARNING = clc.bold("npm i --save firebase-functions@latest") + " in the functions folder."; +/** + * Exported for testing purposes only. + * + * @internal + */ +export function findModuleVersion(name: string, resolvedPath: string): string | undefined { + let searchPath = path.dirname(resolvedPath); + // eslint-disable-next-line no-constant-condition + while (true) { + if (searchPath === "/" || path.basename(searchPath) === "node_modules") { + logger.debug( + `Failed to find version of module ${name}: reached end of search path ${searchPath}` + ); + return; + } + const maybePackageJson = path.join(searchPath, "package.json"); + if (fs.existsSync(maybePackageJson)) { + const pkg = require(maybePackageJson); + if (pkg.name === name) { + return pkg.version; + } + logger.debug( + `Failed to find version of module ${name}: instead found ${pkg.name} at ${searchPath}` + ); + return; + } + searchPath = path.dirname(searchPath); + } +} + /** * Returns the version of firebase-functions SDK specified by package.json and package-lock.json. * @param sourceDir Source directory of functions code * @return version string (e.g. "3.1.2"), or void if firebase-functions is not in package.json * or if we had trouble getting the version. */ -export function getFunctionsSDKVersion(sourceDir: string): string | void { +export function getFunctionsSDKVersion(sourceDir: string): string | undefined { try { - const child = spawn.sync("npm", ["list", "firebase-functions", "--json=true"], { - cwd: sourceDir, - encoding: "utf8", - }); - if (child.error) { - logger.debug("getFunctionsSDKVersion encountered error:", child.error.stack); - return; - } - const output: NpmListResult = JSON.parse(child.stdout); - return _.get(output, ["dependencies", "firebase-functions", "version"]); + return findModuleVersion( + "firebase-functions", + // Find the entry point of the firebase-function module. require.resolve works for project directories using + // npm, yarn (1), or yarn (1) workspaces. Does not support yarn (2) since GCF doesn't support it anyway: + // https://issuetracker.google.com/issues/213632942. + require.resolve("firebase-functions", { paths: [sourceDir] }) + ); } catch (e: any) { + if (e.code === "MODULE_NOT_FOUND") { + utils.logLabeledWarning( + "functions", + "Couldn't find firebase-functions package in your source code. Have you run 'npm install'?" + ); + } logger.debug("getFunctionsSDKVersion encountered error:", e); return; } } +/** + * Get latest version of the Firebase Functions SDK. + */ export function getLatestSDKVersion(): string | undefined { const child = spawn.sync("npm", ["show", "firebase-functions", "--json=true"], { encoding: "utf8", @@ -70,10 +97,10 @@ export function getLatestSDKVersion(): string | undefined { return; } const output: NpmShowResult = JSON.parse(child.stdout); - if (_.isEmpty(output)) { + if (Object.keys(output).length === 0) { return; } - return _.get(output, ["dist-tags", "latest"]); + return output["dist-tags"]?.["latest"]; } /** From dab23ed8d490fa144bacd596644a362f1d8d3e06 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Tue, 13 Dec 2022 16:51:33 -0500 Subject: [PATCH 0725/1699] Add in user envs to emulator (#5330) * adding in user envs * add changelog * modifying comment * add period --- CHANGELOG.md | 3 ++- src/emulator/functionsEmulator.ts | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0c8713f3bc..196e95c51fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ -- Add sharp NPM module to Cloud Functions when using Next.js Image Optimization +- Add sharp NPM module to Cloud Functions when using Next.js Image Optimization. +- Adds user-defined env vars into the functions emulator (#5330). diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 6f6f70b6167..e04caae8367 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -461,8 +461,8 @@ export class FunctionsEmulator implements EmulatorInstance { await runtimeDelegate.validate(); logger.debug(`Building ${runtimeDelegate.name} source`); await runtimeDelegate.build(); - logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`); - // Don't include user envs when parsing triggers, but we need some of the options for handling params + + // Don't include user envs when parsing triggers. Do include user envs when resolving parameter values const firebaseConfig = this.getFirebaseConfig(); const environment = { ...this.getSystemEnvs(), @@ -475,12 +475,13 @@ export class FunctionsEmulator implements EmulatorInstance { projectId: this.args.projectId, projectAlias: this.args.projectAlias, }; + const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt); const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment); const resolution = await resolveBackend( discoveredBuild, JSON.parse(firebaseConfig), userEnvOpt, - environment + userEnvs ); const discoveredBackend = resolution.backend; const endpoints = backend.allEndpoints(discoveredBackend); From 3d8922282b018ba127f2ccecdbf649927bd2e212 Mon Sep 17 00:00:00 2001 From: Leonardo Ortiz Date: Tue, 13 Dec 2022 19:15:55 -0300 Subject: [PATCH 0726/1699] Paths that have middleware should route through Cloud Functions (#5320) Skip moving files that might match middleware to the code generated hosting directory. We don't want these on the CDN, they should passed through to the backing Cloud Function. * Skip copying files to hosting that match middleware * Next export succeeds even when there's middleware, it's no longer a usable shortcut * If there is middleware, we need a Cloud Function * Console logg reasons why a Cloud Function is needed * Bumping the next.js devDep * Adding types where we didn't have any before * Cleaning up the public directory generation function --- CHANGELOG.md | 4 +- npm-shrinkwrap.json | 415 +++++++++++++++--- package.json | 2 +- .../hosting/middleware.ts | 12 + .../hosting/pages/about/me.tsx | 7 + src/frameworks/index.ts | 10 +- src/frameworks/next/constants.ts | 18 + src/frameworks/next/index.ts | 395 +++++++++-------- src/frameworks/next/interfaces.ts | 7 +- src/frameworks/next/utils.ts | 83 +++- src/test/frameworks/next/helpers/images.ts | 50 +++ src/test/frameworks/next/helpers/index.ts | 2 + .../frameworks/next/helpers/middleware.ts | 30 ++ src/test/frameworks/next/utils.spec.ts | 108 ++++- 14 files changed, 863 insertions(+), 280 deletions(-) create mode 100644 scripts/webframeworks-deploy-tests/hosting/middleware.ts create mode 100644 scripts/webframeworks-deploy-tests/hosting/pages/about/me.tsx create mode 100644 src/frameworks/next/constants.ts create mode 100644 src/test/frameworks/next/helpers/images.ts create mode 100644 src/test/frameworks/next/helpers/middleware.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 196e95c51fa..06dd776ac68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,4 @@ -- Add sharp NPM module to Cloud Functions when using Next.js Image Optimization. +- Add sharp NPM module to Cloud Functions when using Next.js Image Optimization (#5238) - Adds user-defined env vars into the functions emulator (#5330). +- Support Next.js Middleware (#5320) +- Log the reason for a Cloud Function if needed in Next.js (#5320) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2f24ab8996f..c4cb36e76d3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -140,7 +140,7 @@ "google-discovery-to-swagger": "^2.1.0", "googleapis": "^105.0.0", "mocha": "^9.1.3", - "next": "^12.3.0", + "next": "^13.0.2", "nock": "^13.0.5", "node-mocks-http": "^1.11.0", "nyc": "^15.1.0", @@ -2084,15 +2084,63 @@ } }, "node_modules/@next/env": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.1.tgz", - "integrity": "sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.2.tgz", + "integrity": "sha512-Qb6WPuRriGIQ19qd6NBxpcrFOfj8ziN7l9eZUfwff5gl4zLXluqtuZPddYZM/oWjN53ZYcuRXzL+oowKyJeYtA==", "dev": true }, + "node_modules/@next/swc-android-arm-eabi": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.2.tgz", + "integrity": "sha512-X54UQCTFyOGnJP//Z71dPPlp4BCYcQL2ncikKXQcPzVpqPs4C3m+tKC8ivBNH6edAXkppwsLRz1/yQwgSZ9Swg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-android-arm64": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.2.tgz", + "integrity": "sha512-1P00Kv8uKaLubqo7JzPrTqgFAzSOmfb8iwqJrOb9in5IvTRtNGlkR4hU0sXzqbQNM/+SaYxze6Z5ry1IDyb/cQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.2.tgz", + "integrity": "sha512-1zGIOkInkOLRv0QQGZ+3wffYsyKI4vIy62LYTvDWUn7TAYqnmXwougp9NSLqDeagLwgsv2URrykyAFixA/YqxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-darwin-x64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz", - "integrity": "sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.2.tgz", + "integrity": "sha512-ECDAjoMP1Y90cARaelS6X+k6BQx+MikAYJ8f/eaJrLur44NIOYc9HA/dgcTp5jenguY4yT8V+HCquLjAVle6fA==", "cpu": [ "x64" ], @@ -2105,6 +2153,150 @@ "node": ">= 10" } }, + "node_modules/@next/swc-freebsd-x64": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.2.tgz", + "integrity": "sha512-2DcL/ofQdBnQX3IoI9sjlIAyLCD1oZoUBuhrhWbejvBQjutWrI0JTEv9uG69WcxWhVMm3BCsjv8GK2/68OKp7A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm-gnueabihf": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.2.tgz", + "integrity": "sha512-Y3OQF1CSBSWW2vGkmvOIuOUNqOq8qX7f1ZpcKUVWP3/Uq++DZmVi9d18lgnSe1I3QFqc+nXWyun9ljsN83j0sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.2.tgz", + "integrity": "sha512-mNyzwsFF6kwZYEjnGicx9ksDZYEZvyzEc1BtCu8vdZi/v8UeixQwCiAT6FyYX9uxMPEkzk8qiU0t0u9gvltsKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.2.tgz", + "integrity": "sha512-M6SdYjWgRrY3tJBxz0663zCRPTu5BRONmxlftKWWHv9LjAJ59neTLaGj4rp0A08DkJglZIoCkLOzLrzST6TGag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.2.tgz", + "integrity": "sha512-pi63RoxvG4ES1KS06Zpm0MATVIXTs/TIbLbdckeLoM40u1d3mQl/+hSSrLRSxzc2OtyL8fh92sM4gkJrQXAMAw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.2.tgz", + "integrity": "sha512-9Pv91gfYnDONgjtRm78n64b/c54+azeHtlnqBLTnIFWSMBDRl1/WDkhKWIj3fBGPLimtK7Tko3ULR3og9RRUPw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.2.tgz", + "integrity": "sha512-Nvewe6YZaizAkGHHprbMkYqQulBjZCHKBGKeFPwoPtOA+a2Qi4pZzc/qXFyC5/2A6Z0mr2U1zg9rd04WBYMwBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.2.tgz", + "integrity": "sha512-ZUBYGZw5G3QrqDpRq1EWi3aHmvPZM8ijK5TFL6UbH16cYQ0JpANmuG2P66KB93Qe/lWWzbeAZk/tj1XqwoCuPA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.2.tgz", + "integrity": "sha512-fA9uW1dm7C0mEYGcKlbmLcVm2sKcye+1kPxh2cM4jVR+kQQMtHWsjIzeSpe2grQLSDan06z4n6hbr8b1c3hA8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4679,6 +4871,12 @@ "node": ">= 10" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "dev": true + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -10202,44 +10400,44 @@ } }, "node_modules/next": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", - "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/next/-/next-13.0.2.tgz", + "integrity": "sha512-uQ5z5e4D9mOe8+upy6bQdYYjo/kk1v3jMW87kTy2TgAyAsEO+CkwRnMgyZ4JoHEnhPZLHwh7dk0XymRNLe1gFw==", "dev": true, "dependencies": { - "@next/env": "12.3.1", + "@next/env": "13.0.2", "@swc/helpers": "0.4.11", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", - "styled-jsx": "5.0.7", + "styled-jsx": "5.1.0", "use-sync-external-store": "1.2.0" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=12.22.0" + "node": ">=14.6.0" }, "optionalDependencies": { - "@next/swc-android-arm-eabi": "12.3.1", - "@next/swc-android-arm64": "12.3.1", - "@next/swc-darwin-arm64": "12.3.1", - "@next/swc-darwin-x64": "12.3.1", - "@next/swc-freebsd-x64": "12.3.1", - "@next/swc-linux-arm-gnueabihf": "12.3.1", - "@next/swc-linux-arm64-gnu": "12.3.1", - "@next/swc-linux-arm64-musl": "12.3.1", - "@next/swc-linux-x64-gnu": "12.3.1", - "@next/swc-linux-x64-musl": "12.3.1", - "@next/swc-win32-arm64-msvc": "12.3.1", - "@next/swc-win32-ia32-msvc": "12.3.1", - "@next/swc-win32-x64-msvc": "12.3.1" + "@next/swc-android-arm-eabi": "13.0.2", + "@next/swc-android-arm64": "13.0.2", + "@next/swc-darwin-arm64": "13.0.2", + "@next/swc-darwin-x64": "13.0.2", + "@next/swc-freebsd-x64": "13.0.2", + "@next/swc-linux-arm-gnueabihf": "13.0.2", + "@next/swc-linux-arm64-gnu": "13.0.2", + "@next/swc-linux-arm64-musl": "13.0.2", + "@next/swc-linux-x64-gnu": "13.0.2", + "@next/swc-linux-x64-musl": "13.0.2", + "@next/swc-win32-arm64-msvc": "13.0.2", + "@next/swc-win32-ia32-msvc": "13.0.2", + "@next/swc-win32-x64-msvc": "13.0.2" }, "peerDependencies": { "fibers": ">= 3.1.0", "node-sass": "^6.0.0 || ^7.0.0", - "react": "^17.0.2 || ^18.0.0-0", - "react-dom": "^17.0.2 || ^18.0.0-0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -13084,10 +13282,13 @@ "optional": true }, "node_modules/styled-jsx": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz", - "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.0.tgz", + "integrity": "sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==", "dev": true, + "dependencies": { + "client-only": "0.0.1" + }, "engines": { "node": ">= 12.0.0" }, @@ -16661,15 +16862,99 @@ } }, "@next/env": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.1.tgz", - "integrity": "sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.2.tgz", + "integrity": "sha512-Qb6WPuRriGIQ19qd6NBxpcrFOfj8ziN7l9eZUfwff5gl4zLXluqtuZPddYZM/oWjN53ZYcuRXzL+oowKyJeYtA==", "dev": true }, + "@next/swc-android-arm-eabi": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.2.tgz", + "integrity": "sha512-X54UQCTFyOGnJP//Z71dPPlp4BCYcQL2ncikKXQcPzVpqPs4C3m+tKC8ivBNH6edAXkppwsLRz1/yQwgSZ9Swg==", + "dev": true, + "optional": true + }, + "@next/swc-android-arm64": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.2.tgz", + "integrity": "sha512-1P00Kv8uKaLubqo7JzPrTqgFAzSOmfb8iwqJrOb9in5IvTRtNGlkR4hU0sXzqbQNM/+SaYxze6Z5ry1IDyb/cQ==", + "dev": true, + "optional": true + }, + "@next/swc-darwin-arm64": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.2.tgz", + "integrity": "sha512-1zGIOkInkOLRv0QQGZ+3wffYsyKI4vIy62LYTvDWUn7TAYqnmXwougp9NSLqDeagLwgsv2URrykyAFixA/YqxA==", + "dev": true, + "optional": true + }, "@next/swc-darwin-x64": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz", - "integrity": "sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.2.tgz", + "integrity": "sha512-ECDAjoMP1Y90cARaelS6X+k6BQx+MikAYJ8f/eaJrLur44NIOYc9HA/dgcTp5jenguY4yT8V+HCquLjAVle6fA==", + "dev": true, + "optional": true + }, + "@next/swc-freebsd-x64": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.2.tgz", + "integrity": "sha512-2DcL/ofQdBnQX3IoI9sjlIAyLCD1oZoUBuhrhWbejvBQjutWrI0JTEv9uG69WcxWhVMm3BCsjv8GK2/68OKp7A==", + "dev": true, + "optional": true + }, + "@next/swc-linux-arm-gnueabihf": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.2.tgz", + "integrity": "sha512-Y3OQF1CSBSWW2vGkmvOIuOUNqOq8qX7f1ZpcKUVWP3/Uq++DZmVi9d18lgnSe1I3QFqc+nXWyun9ljsN83j0sw==", + "dev": true, + "optional": true + }, + "@next/swc-linux-arm64-gnu": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.2.tgz", + "integrity": "sha512-mNyzwsFF6kwZYEjnGicx9ksDZYEZvyzEc1BtCu8vdZi/v8UeixQwCiAT6FyYX9uxMPEkzk8qiU0t0u9gvltsKw==", + "dev": true, + "optional": true + }, + "@next/swc-linux-arm64-musl": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.2.tgz", + "integrity": "sha512-M6SdYjWgRrY3tJBxz0663zCRPTu5BRONmxlftKWWHv9LjAJ59neTLaGj4rp0A08DkJglZIoCkLOzLrzST6TGag==", + "dev": true, + "optional": true + }, + "@next/swc-linux-x64-gnu": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.2.tgz", + "integrity": "sha512-pi63RoxvG4ES1KS06Zpm0MATVIXTs/TIbLbdckeLoM40u1d3mQl/+hSSrLRSxzc2OtyL8fh92sM4gkJrQXAMAw==", + "dev": true, + "optional": true + }, + "@next/swc-linux-x64-musl": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.2.tgz", + "integrity": "sha512-9Pv91gfYnDONgjtRm78n64b/c54+azeHtlnqBLTnIFWSMBDRl1/WDkhKWIj3fBGPLimtK7Tko3ULR3og9RRUPw==", + "dev": true, + "optional": true + }, + "@next/swc-win32-arm64-msvc": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.2.tgz", + "integrity": "sha512-Nvewe6YZaizAkGHHprbMkYqQulBjZCHKBGKeFPwoPtOA+a2Qi4pZzc/qXFyC5/2A6Z0mr2U1zg9rd04WBYMwBw==", + "dev": true, + "optional": true + }, + "@next/swc-win32-ia32-msvc": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.2.tgz", + "integrity": "sha512-ZUBYGZw5G3QrqDpRq1EWi3aHmvPZM8ijK5TFL6UbH16cYQ0JpANmuG2P66KB93Qe/lWWzbeAZk/tj1XqwoCuPA==", + "dev": true, + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.2.tgz", + "integrity": "sha512-fA9uW1dm7C0mEYGcKlbmLcVm2sKcye+1kPxh2cM4jVR+kQQMtHWsjIzeSpe2grQLSDan06z4n6hbr8b1c3hA8w==", "dev": true, "optional": true }, @@ -18730,6 +19015,12 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" }, + "client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "dev": true + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -23059,29 +23350,29 @@ "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" }, "next": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", - "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", - "dev": true, - "requires": { - "@next/env": "12.3.1", - "@next/swc-android-arm-eabi": "12.3.1", - "@next/swc-android-arm64": "12.3.1", - "@next/swc-darwin-arm64": "12.3.1", - "@next/swc-darwin-x64": "12.3.1", - "@next/swc-freebsd-x64": "12.3.1", - "@next/swc-linux-arm-gnueabihf": "12.3.1", - "@next/swc-linux-arm64-gnu": "12.3.1", - "@next/swc-linux-arm64-musl": "12.3.1", - "@next/swc-linux-x64-gnu": "12.3.1", - "@next/swc-linux-x64-musl": "12.3.1", - "@next/swc-win32-arm64-msvc": "12.3.1", - "@next/swc-win32-ia32-msvc": "12.3.1", - "@next/swc-win32-x64-msvc": "12.3.1", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/next/-/next-13.0.2.tgz", + "integrity": "sha512-uQ5z5e4D9mOe8+upy6bQdYYjo/kk1v3jMW87kTy2TgAyAsEO+CkwRnMgyZ4JoHEnhPZLHwh7dk0XymRNLe1gFw==", + "dev": true, + "requires": { + "@next/env": "13.0.2", + "@next/swc-android-arm-eabi": "13.0.2", + "@next/swc-android-arm64": "13.0.2", + "@next/swc-darwin-arm64": "13.0.2", + "@next/swc-darwin-x64": "13.0.2", + "@next/swc-freebsd-x64": "13.0.2", + "@next/swc-linux-arm-gnueabihf": "13.0.2", + "@next/swc-linux-arm64-gnu": "13.0.2", + "@next/swc-linux-arm64-musl": "13.0.2", + "@next/swc-linux-x64-gnu": "13.0.2", + "@next/swc-linux-x64-musl": "13.0.2", + "@next/swc-win32-arm64-msvc": "13.0.2", + "@next/swc-win32-ia32-msvc": "13.0.2", + "@next/swc-win32-x64-msvc": "13.0.2", "@swc/helpers": "0.4.11", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", - "styled-jsx": "5.0.7", + "styled-jsx": "5.1.0", "use-sync-external-store": "1.2.0" } }, @@ -25294,11 +25585,13 @@ "optional": true }, "styled-jsx": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz", - "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.0.tgz", + "integrity": "sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==", "dev": true, - "requires": {} + "requires": { + "client-only": "0.0.1" + } }, "superagent": { "version": "7.1.3", diff --git a/package.json b/package.json index bdfc2de6125..e74c2adf0a5 100644 --- a/package.json +++ b/package.json @@ -222,7 +222,7 @@ "google-discovery-to-swagger": "^2.1.0", "googleapis": "^105.0.0", "mocha": "^9.1.3", - "next": "^12.3.0", + "next": "^13.0.2", "nock": "^13.0.5", "node-mocks-http": "^1.11.0", "nyc": "^15.1.0", diff --git a/scripts/webframeworks-deploy-tests/hosting/middleware.ts b/scripts/webframeworks-deploy-tests/hosting/middleware.ts new file mode 100644 index 00000000000..114232407cc --- /dev/null +++ b/scripts/webframeworks-deploy-tests/hosting/middleware.ts @@ -0,0 +1,12 @@ +// middleware.ts +import { NextRequest, NextResponse } from 'next/server'; + +// This function can be marked `async` if using `await` inside +export function middleware(request: NextRequest) { + return NextResponse.redirect(new URL('/about-2', request.url)); +} + +// See "Matching Paths" below to learn more +export const config = { + matcher: '/about/:path*', +} diff --git a/scripts/webframeworks-deploy-tests/hosting/pages/about/me.tsx b/scripts/webframeworks-deploy-tests/hosting/pages/about/me.tsx new file mode 100644 index 00000000000..477ae07b032 --- /dev/null +++ b/scripts/webframeworks-deploy-tests/hosting/pages/about/me.tsx @@ -0,0 +1,7 @@ +export const getStaticProps = () => { + return { props: {} }; +} + +export default function Me() { + return <>About me; +} diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index a3158d8e0db..7dc172cd72e 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -51,7 +51,8 @@ export interface Framework { support: SupportLevel; init?: (setup: any) => Promise; getDevModeHandle?: ( - dir: string + dir: string, + hostingEmulatorInfo?: EmulatorInfo ) => Promise<(req: IncomingMessage, res: ServerResponse, next: () => void) => void>; ɵcodegenPublicDirectory: (dir: string, dest: string) => Promise; ɵcodegenFunctionsDirectory?: ( @@ -382,8 +383,13 @@ export async function prepareFrameworks( console.log(`Detected a ${name} codebase. ${SupportLevelWarnings[support] || ""}\n`); // TODO allow for override const isDevMode = context._name === "serve" || context._name === "emulators:start"; + + const hostingEmulatorInfo = emulators.find((e) => e.name === Emulators.HOSTING); + const devModeHandle = - isDevMode && getDevModeHandle && (await getDevModeHandle(getProjectPath())); + isDevMode && + getDevModeHandle && + (await getDevModeHandle(getProjectPath(), hostingEmulatorInfo)); let codegenFunctionsDirectory: Framework["ɵcodegenFunctionsDirectory"]; if (devModeHandle) { config.public = relative(projectRoot, publicDirectory); diff --git a/src/frameworks/next/constants.ts b/src/frameworks/next/constants.ts new file mode 100644 index 00000000000..e180d9b10ad --- /dev/null +++ b/src/frameworks/next/constants.ts @@ -0,0 +1,18 @@ +import type { + APP_PATH_ROUTES_MANIFEST as APP_PATH_ROUTES_MANIFEST_TYPE, + EXPORT_MARKER as EXPORT_MARKER_TYPE, + IMAGES_MANIFEST as IMAGES_MANIFEST_TYPE, + MIDDLEWARE_MANIFEST as MIDDLEWARE_MANIFEST_TYPE, + PAGES_MANIFEST as PAGES_MANIFEST_TYPE, + PRERENDER_MANIFEST as PRERENDER_MANIFEST_TYPE, + ROUTES_MANIFEST as ROUTES_MANIFEST_TYPE, +} from "next/constants"; + +export const APP_PATH_ROUTES_MANIFEST: typeof APP_PATH_ROUTES_MANIFEST_TYPE = + "app-path-routes-manifest.json"; +export const EXPORT_MARKER: typeof EXPORT_MARKER_TYPE = "export-marker.json"; +export const IMAGES_MANIFEST: typeof IMAGES_MANIFEST_TYPE = "images-manifest.json"; +export const MIDDLEWARE_MANIFEST: typeof MIDDLEWARE_MANIFEST_TYPE = "middleware-manifest.json"; +export const PAGES_MANIFEST: typeof PAGES_MANIFEST_TYPE = "pages-manifest.json"; +export const PRERENDER_MANIFEST: typeof PRERENDER_MANIFEST_TYPE = "prerender-manifest.json"; +export const ROUTES_MANIFEST: typeof ROUTES_MANIFEST_TYPE = "routes-manifest.json"; diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index 53888503ed9..459e6e4aa4f 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -1,10 +1,16 @@ import { execSync } from "child_process"; -import { readFile, mkdir, copyFile } from "fs/promises"; +import { mkdir, copyFile } from "fs/promises"; import { dirname, join } from "path"; import type { NextConfig } from "next"; +import type { PrerenderManifest } from "next/dist/build"; +import type { MiddlewareManifest } from "next/dist/build/webpack/plugins/middleware-plugin"; +import type { PagesManifest } from "next/dist/build/webpack/plugins/pages-manifest-plugin"; import { copy, mkdirp, pathExists } from "fs-extra"; import { pathToFileURL, parse } from "url"; import { existsSync } from "fs"; +import { gte } from "semver"; +import { IncomingMessage, ServerResponse } from "http"; +import * as clc from "colorette"; import { BuildResult, @@ -16,35 +22,39 @@ import { SupportLevel, } from ".."; import { promptOnce } from "../../prompt"; -import { gte } from "semver"; -import { IncomingMessage, ServerResponse } from "http"; import { logger } from "../../logger"; import { FirebaseError } from "../../error"; -import { fileExistsSync } from "../../fsutils"; import { cleanEscapedChars, getNextjsRewritesToUse, - isHeaderSupportedByFirebase, - isRedirectSupportedByFirebase, - isRewriteSupportedByFirebase, + isHeaderSupportedByHosting, + isRedirectSupportedByHosting, + isRewriteSupportedByHosting, + isUsingAppDirectory, + isUsingImageOptimization, + isUsingMiddleware, } from "./utils"; import type { Manifest } from "./interfaces"; import { readJSON } from "../utils"; import { warnIfCustomBuildScript } from "../utils"; +import type { EmulatorInfo } from "../../emulator/types"; import { usesAppDirRouter, usesNextImage, hasUnoptimizedImage } from "./utils"; - -const CLI_COMMAND = join( - "node_modules", - ".bin", - process.platform === "win32" ? "next.cmd" : "next" -); +import { + MIDDLEWARE_MANIFEST, + PAGES_MANIFEST, + PRERENDER_MANIFEST, + ROUTES_MANIFEST, +} from "./constants"; const DEFAULT_BUILD_SCRIPT = ["next build"]; +const PUBLIC_DIR = "public"; export const name = "Next.js"; export const support = SupportLevel.Experimental; export const type = FrameworkType.MetaFramework; +const DEFAULT_NUMBER_OF_REASONS_TO_LIST = 5; + function getNextVersion(cwd: string): string | undefined { return findDependency("next", { cwd, depth: 0, omitDev: false })?.version; } @@ -59,8 +69,8 @@ function getReactVersion(cwd: string): string | undefined { export async function discover(dir: string) { if (!(await pathExists(join(dir, "package.json")))) return; if (!(await pathExists("next.config.js")) && !getNextVersion(dir)) return; - // TODO don't hardcode public dir - return { mayWantBackend: true, publicDirectory: join(dir, "public") }; + + return { mayWantBackend: true, publicDirectory: join(dir, PUBLIC_DIR) }; } /** @@ -84,56 +94,65 @@ export async function build(dir: string): Promise { throw e; }); - try { - // Using spawn here, rather than their programatic API because I can't silence it - // Failures with Next export are expected, we're just trying to do it if we can - execSync(`${CLI_COMMAND} export`, { cwd: dir, stdio: "ignore" }); - } catch (e) { - // continue, failure is expected + const reasonsForBackend = []; + const { distDir } = await getConfig(dir); + + if (await isUsingMiddleware(join(dir, distDir), false)) { + reasonsForBackend.push("middleware"); } - let wantsBackend = true; - const { distDir } = await getConfig(dir); - const exportDetailPath = join(dir, distDir, "export-detail.json"); - const exportDetailExists = await pathExists(exportDetailPath); - const exportDetailBuffer = exportDetailExists ? await readFile(exportDetailPath) : undefined; - const exportDetailJson = exportDetailBuffer && JSON.parse(exportDetailBuffer.toString()); - if (exportDetailJson?.success) { - const appPathRoutesManifestPath = join(dir, distDir, "app-path-routes-manifest.json"); - const appPathRoutesManifestJSON = fileExistsSync(appPathRoutesManifestPath) - ? await readFile(appPathRoutesManifestPath).then((it) => JSON.parse(it.toString())) - : {}; - const prerenderManifestJSON = await readFile( - join(dir, distDir, "prerender-manifest.json") - ).then((it) => JSON.parse(it.toString())); - const anyDynamicRouteFallbacks = !!Object.values( - prerenderManifestJSON.dynamicRoutes || {} - ).find((it: any) => it.fallback !== false); - const pagesManifestJSON = await readFile( - join(dir, distDir, "server", "pages-manifest.json") - ).then((it) => JSON.parse(it.toString())); - const prerenderedRoutes = Object.keys(prerenderManifestJSON.routes); - const dynamicRoutes = Object.keys(prerenderManifestJSON.dynamicRoutes); - const unrenderedPages = [ - ...Object.keys(pagesManifestJSON), - // TODO flush out fully rendered detection with a app directory (Next 13) - // we shouldn't go too crazy here yet, as this is currently an expiriment - ...Object.values(appPathRoutesManifestJSON), - ].filter( - (it) => - !( - ["/_app", "/", "/_error", "/_document", "/404"].includes(it) || - prerenderedRoutes.includes(it) || - dynamicRoutes.includes(it) - ) - ); - // TODO log these as a reason why Cloud Functions are needed - if (!anyDynamicRouteFallbacks && unrenderedPages.length === 0) { - wantsBackend = false; + if (await isUsingImageOptimization(join(dir, distDir))) { + reasonsForBackend.push(`Image Optimization`); + } + + if (isUsingAppDirectory(join(dir, distDir))) { + // Let's not get smart here, if they are using the app directory we should + // opt for spinning up a Cloud Function. The app directory is unstable. + reasonsForBackend.push("app directory (unstable)"); + } + + const prerenderManifest = await readJSON( + join(dir, distDir, PRERENDER_MANIFEST) + ); + + const dynamicRoutesWithFallback = Object.entries(prerenderManifest.dynamicRoutes || {}).filter( + ([, it]) => it.fallback !== false + ); + if (dynamicRoutesWithFallback.length > 0) { + for (const [key] of dynamicRoutesWithFallback) { + reasonsForBackend.push(`use of fallback ${key}`); + } + } + + const routesWithRevalidate = Object.entries(prerenderManifest.routes).filter( + ([, it]) => it.initialRevalidateSeconds + ); + if (routesWithRevalidate.length > 0) { + for (const [key] of routesWithRevalidate) { + reasonsForBackend.push(`use of revalidate ${key}`); } } - const manifest = await readJSON(join(dir, distDir, "routes-manifest.json")); + const pagesManifestJSON = await readJSON( + join(dir, distDir, "server", PAGES_MANIFEST) + ); + const prerenderedRoutes = Object.keys(prerenderManifest.routes); + const dynamicRoutes = Object.keys(prerenderManifest.dynamicRoutes); + const unrenderedPages = Object.keys(pagesManifestJSON).filter( + (it) => + !( + ["/_app", "/", "/_error", "/_document", "/404"].includes(it) || + prerenderedRoutes.includes(it) || + dynamicRoutes.includes(it) + ) + ); + if (unrenderedPages.length > 0) { + for (const key of unrenderedPages) { + reasonsForBackend.push(`non-static route ${key}`); + } + } + + const manifest = await readJSON(join(dir, distDir, ROUTES_MANIFEST)); const { headers: nextJsHeaders = [], @@ -141,20 +160,24 @@ export async function build(dir: string): Promise { rewrites: nextJsRewrites = [], } = manifest; - const isEveryHeaderSupported = nextJsHeaders.every(isHeaderSupportedByFirebase); - if (!isEveryHeaderSupported) wantsBackend = true; + const isEveryHeaderSupported = nextJsHeaders.every(isHeaderSupportedByHosting); + if (!isEveryHeaderSupported) { + reasonsForBackend.push("advanced headers"); + } - const headers = nextJsHeaders.filter(isHeaderSupportedByFirebase).map(({ source, headers }) => ({ + const headers = nextJsHeaders.filter(isHeaderSupportedByHosting).map(({ source, headers }) => ({ // clean up unnecessary escaping source: cleanEscapedChars(source), headers, })); - const isEveryRedirectSupported = nextJsRedirects.every(isRedirectSupportedByFirebase); - if (!isEveryRedirectSupported) wantsBackend = true; + const isEveryRedirectSupported = nextJsRedirects.every(isRedirectSupportedByHosting); + if (!isEveryRedirectSupported) { + reasonsForBackend.push("advanced redirects"); + } const redirects = nextJsRedirects - .filter(isRedirectSupportedByFirebase) + .filter(isRedirectSupportedByHosting) .map(({ source, destination, statusCode: type }) => ({ // clean up unnecessary escaping source: cleanEscapedChars(source), @@ -169,21 +192,41 @@ export async function build(dir: string): Promise { !Array.isArray(nextJsRewrites) && (nextJsRewrites.afterFiles?.length || nextJsRewrites.fallback?.length) ) { - wantsBackend = true; - } else { - const isEveryRewriteSupported = nextJsRewritesToUse.every(isRewriteSupportedByFirebase); - if (!isEveryRewriteSupported) wantsBackend = true; + reasonsForBackend.push("advanced rewrites"); + } + + const isEveryRewriteSupported = nextJsRewritesToUse.every(isRewriteSupportedByHosting); + if (!isEveryRewriteSupported) { + reasonsForBackend.push("advanced rewrites"); } // Can we change i18n into Firebase settings? const rewrites = nextJsRewritesToUse - .filter(isRewriteSupportedByFirebase) + .filter(isRewriteSupportedByHosting) .map(({ source, destination }) => ({ // clean up unnecessary escaping source: cleanEscapedChars(source), destination, })); + const wantsBackend = reasonsForBackend.length > 0; + + if (wantsBackend) { + const numberOfReasonsToList = process.env.DEBUG ? Infinity : DEFAULT_NUMBER_OF_REASONS_TO_LIST; + console.log("Building a Cloud Function to run this application. This is needed due to:"); + for (const reason of reasonsForBackend.slice(0, numberOfReasonsToList)) { + console.log(` • ${reason}`); + } + if (reasonsForBackend.length > numberOfReasonsToList) { + console.log( + ` • and ${ + reasonsForBackend.length - numberOfReasonsToList + } other reasons, use --debug to see more` + ); + } + console.log(""); + } + return { wantsBackend, headers, redirects, rewrites }; } @@ -210,131 +253,86 @@ export async function init(setup: any) { */ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: string) { const { distDir } = await getConfig(sourceDir); - const exportDetailPath = join(sourceDir, distDir, "export-detail.json"); - const exportDetailExists = await pathExists(exportDetailPath); - const exportDetailBuffer = exportDetailExists ? await readFile(exportDetailPath) : undefined; - const exportDetailJson = exportDetailBuffer && JSON.parse(exportDetailBuffer.toString()); - if (exportDetailJson?.success) { - copy(exportDetailJson.outDirectory, destDir); - } else { - const publicPath = join(sourceDir, "public"); - await mkdir(join(destDir, "_next", "static"), { recursive: true }); - if (await pathExists(publicPath)) { - await copy(publicPath, destDir); + + const publicPath = join(sourceDir, "public"); + await mkdir(join(destDir, "_next", "static"), { recursive: true }); + if (await pathExists(publicPath)) { + await copy(publicPath, destDir); + } + await copy(join(sourceDir, distDir, "static"), join(destDir, "_next", "static")); + + // Copy over the default html files + for (const file of ["index.html", "404.html", "500.html"]) { + const pagesPath = join(sourceDir, distDir, "server", "pages", file); + if (await pathExists(pagesPath)) { + await copyFile(pagesPath, join(destDir, file)); + continue; } - await copy(join(sourceDir, distDir, "static"), join(destDir, "_next", "static")); - - // Copy over the default html files - for (const file of ["index.html", "404.html", "500.html"]) { - const pagesPath = join(sourceDir, distDir, "server", "pages", file); - if (await pathExists(pagesPath)) { - await copyFile(pagesPath, join(destDir, file)); - continue; - } - const appPath = join(sourceDir, distDir, "server", "app", file); - if (await pathExists(appPath)) { - await copyFile(appPath, join(destDir, file)); - } + const appPath = join(sourceDir, distDir, "server", "app", file); + if (await pathExists(appPath)) { + await copyFile(appPath, join(destDir, file)); + } + } + + const [middlewareManifest, prerenderManifest, routesManifest] = await Promise.all([ + readJSON(join(sourceDir, distDir, "server", MIDDLEWARE_MANIFEST)), + readJSON(join(sourceDir, distDir, PRERENDER_MANIFEST)), + readJSON(join(sourceDir, distDir, ROUTES_MANIFEST)), + ]); + + const middlewareMatcherRegexes = Object.values(middlewareManifest.middleware) + .map((it) => it.matchers) + .flat() + .map((it) => new RegExp(it.regexp)); + + const { redirects = [], rewrites = [], headers = [] } = routesManifest; + + const rewritesRegexesNotSupportedByHosting = getNextjsRewritesToUse(rewrites) + .filter((rewrite) => !isRewriteSupportedByHosting(rewrite)) + .map((rewrite) => new RegExp(rewrite.regex)); + + const redirectsRegexesNotSupportedByHosting = redirects + .filter((redirect) => !isRedirectSupportedByHosting(redirect)) + .map((redirect) => new RegExp(redirect.regex)); + + const headersRegexesNotSupportedByHosting = headers + .filter((header) => !isHeaderSupportedByHosting(header)) + .map((header) => new RegExp(header.regex)); + + const pathsUsingsFeaturesNotSupportedByHosting = [ + ...middlewareMatcherRegexes, + ...rewritesRegexesNotSupportedByHosting, + ...redirectsRegexesNotSupportedByHosting, + ...headersRegexesNotSupportedByHosting, + ]; + + for (const [path, route] of Object.entries(prerenderManifest.routes)) { + if ( + route.initialRevalidateSeconds || + pathsUsingsFeaturesNotSupportedByHosting.some((it) => path.match(it)) + ) { + continue; } - const [prerenderManifest, routesManifest] = await Promise.all([ - readJSON( - join( - sourceDir, - distDir, - "prerender-manifest.json" // TODO: get this from next/constants - ) - ), - readJSON( - join( - sourceDir, - distDir, - "routes-manifest.json" // TODO: get this from next/constants - ) - ), - ]); - - const { redirects = [], rewrites = [], headers = [] } = routesManifest; - - const rewritesToUse = getNextjsRewritesToUse(rewrites); - const rewritesNotSupportedByFirebase = rewritesToUse.filter( - (rewrite) => !isRewriteSupportedByFirebase(rewrite) - ); - const rewritesRegexesNotSupportedByFirebase = rewritesNotSupportedByFirebase.map( - (rewrite) => new RegExp(rewrite.regex) + const isReactServerComponent = route.dataRoute.endsWith(".rsc"); + const contentDist = join( + sourceDir, + distDir, + "server", + isReactServerComponent ? "app" : "pages" ); - const redirectsNotSupportedByFirebase = redirects.filter( - (redirect) => !isRedirectSupportedByFirebase(redirect) - ); - const redirectsRegexesNotSupportedByFirebase = redirectsNotSupportedByFirebase.map( - (redirect) => new RegExp(redirect.regex) - ); + const parts = path.split("/").filter((it) => !!it); + const partsOrIndex = parts.length > 0 ? parts : ["index"]; - const headersNotSupportedByFirebase = headers.filter( - (header) => !isHeaderSupportedByFirebase(header) - ); - const headersRegexesNotSupportedByFirebase = headersNotSupportedByFirebase.map( - (header) => new RegExp(header.regex) - ); + const htmlPath = `${join(...partsOrIndex)}.html`; + await mkdir(join(destDir, dirname(htmlPath)), { recursive: true }); + await copyFile(join(contentDist, htmlPath), join(destDir, htmlPath)); - for (const path in prerenderManifest.routes) { - if (prerenderManifest.routes[path]) { - // Skip ISR in the deploy to hosting - const { initialRevalidateSeconds } = prerenderManifest.routes[path]; - if (initialRevalidateSeconds) { - continue; - } - - const routeMatchUnsupportedRewrite = rewritesRegexesNotSupportedByFirebase.some( - (rewriteRegex) => rewriteRegex.test(path) - ); - if (routeMatchUnsupportedRewrite) continue; - - const routeMatchUnsupportedRedirect = redirectsRegexesNotSupportedByFirebase.some( - (redirectRegex) => redirectRegex.test(path) - ); - if (routeMatchUnsupportedRedirect) continue; - - const routeMatchUnsupportedHeader = headersRegexesNotSupportedByFirebase.some( - (headerRegex) => headerRegex.test(path) - ); - if (routeMatchUnsupportedHeader) continue; - - // TODO(jamesdaniels) explore oppertunity to simplify this now that we - // are defaulting cleanURLs to true for frameworks - - // / => index.json => index.html => index.html - // /foo => foo.json => foo.html - const parts = path - .split("/") - .slice(1) - .filter((it) => !!it); - const partsOrIndex = parts.length > 0 ? parts : ["index"]; - const dataPath = `${join(...partsOrIndex)}.json`; - const htmlPath = `${join(...partsOrIndex)}.html`; - await mkdir(join(destDir, dirname(htmlPath)), { recursive: true }); - const pagesHtmlPath = join(sourceDir, distDir, "server", "pages", htmlPath); - if (await pathExists(pagesHtmlPath)) { - await copyFile(pagesHtmlPath, join(destDir, htmlPath)); - } else { - const appHtmlPath = join(sourceDir, distDir, "server", "app", htmlPath); - if (await pathExists(appHtmlPath)) { - await copyFile(appHtmlPath, join(destDir, htmlPath)); - } - } - const dataRoute = prerenderManifest.routes[path].dataRoute; - await mkdir(join(destDir, dirname(dataRoute)), { recursive: true }); - const pagesDataPath = join(sourceDir, distDir, "server", "pages", dataPath); - if (await pathExists(pagesDataPath)) { - await copyFile(pagesDataPath, join(destDir, dataRoute)); - } else { - const appDataPath = join(sourceDir, distDir, "server", "app", dataPath); - if (await pathExists(appDataPath)) { - await copyFile(appDataPath, join(destDir, dataRoute)); - } - } - } + if (!isReactServerComponent) { + const dataPath = `${join(...partsOrIndex)}.json`; + await mkdir(join(destDir, dirname(route.dataRoute)), { recursive: true }); + await copyFile(join(contentDist, dataPath), join(destDir, route.dataRoute)); } } } @@ -344,8 +342,7 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin */ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) { const { distDir } = await getConfig(sourceDir); - const packageJsonBuffer = await readFile(join(sourceDir, "package.json")); - const packageJson = JSON.parse(packageJsonBuffer.toString()); + const packageJson = await readJSON(join(sourceDir, "package.json")); if (existsSync(join(sourceDir, "next.config.js"))) { let esbuild; try { @@ -391,14 +388,28 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: st /** * Create a dev server. */ -export async function getDevModeHandle(dir: string) { +export async function getDevModeHandle(dir: string, hostingEmulatorInfo?: EmulatorInfo) { + // throw error when using Next.js middleware with firebase serve + if (!hostingEmulatorInfo) { + if (await isUsingMiddleware(dir, true)) { + throw new FirebaseError( + `${clc.bold("firebase serve")} does not support Next.js Middleware. Please use ${clc.bold( + "firebase emulators:start" + )} instead.` + ); + } + } + const { default: next } = relativeRequire(dir, "next"); const nextApp = next({ dev: true, dir, + hostname: hostingEmulatorInfo?.host, + port: hostingEmulatorInfo?.port, }); const handler = nextApp.getRequestHandler(); await nextApp.prepare(); + return (req: IncomingMessage, res: ServerResponse, next: () => void) => { const parsedUrl = parse(req.url!, true); const proxy = createServerResponseProxy(req, res, next); diff --git a/src/frameworks/next/interfaces.ts b/src/frameworks/next/interfaces.ts index 4a88ac9f2d5..31b6d09b95d 100644 --- a/src/frameworks/next/interfaces.ts +++ b/src/frameworks/next/interfaces.ts @@ -1,4 +1,5 @@ import type { Header, Rewrite, Redirect } from "next/dist/lib/load-custom-routes"; +import type { ImageConfigComplete } from "next/dist/shared/lib/image-config"; export interface RoutesManifestRewrite extends Rewrite { regex: string; @@ -37,9 +38,9 @@ export interface ExportMarker { isNextImageImported: boolean; } -export interface ImageManifest { +export interface ImagesManifest { version: number; - images: { - unoptimized: boolean; + images: ImageConfigComplete & { + sizes: number[]; }; } diff --git a/src/frameworks/next/utils.ts b/src/frameworks/next/utils.ts index 0d3cb4afbb1..852088e6b04 100644 --- a/src/frameworks/next/utils.ts +++ b/src/frameworks/next/utils.ts @@ -1,9 +1,18 @@ import { existsSync } from "fs"; +import { pathExists } from "fs-extra"; import { join } from "path"; import type { Header, Redirect, Rewrite } from "next/dist/lib/load-custom-routes"; -import type { Manifest, RoutesManifestRewrite } from "./interfaces"; +import type { MiddlewareManifest } from "next/dist/build/webpack/plugins/middleware-plugin"; + import { isUrl, readJSON } from "../utils"; -import type { ExportMarker, ImageManifest } from "./interfaces"; +import type { Manifest, RoutesManifestRewrite, ExportMarker, ImagesManifest } from "./interfaces"; +import { + APP_PATH_ROUTES_MANIFEST, + EXPORT_MARKER, + IMAGES_MANIFEST, + MIDDLEWARE_MANIFEST, +} from "./constants"; +import { fileExistsSync } from "../../fsutils"; /** * Whether the given path has a regex or not. @@ -53,7 +62,7 @@ export function cleanEscapedChars(path: string): string { * * - Rewrites to external URLs */ -export function isRewriteSupportedByFirebase(rewrite: Rewrite): boolean { +export function isRewriteSupportedByHosting(rewrite: Rewrite): boolean { return !("has" in rewrite || pathHasRegex(rewrite.source) || isUrl(rewrite.destination)); } @@ -72,7 +81,7 @@ export function isRewriteSupportedByFirebase(rewrite: Rewrite): boolean { * * - Next.js internal redirects */ -export function isRedirectSupportedByFirebase(redirect: Redirect): boolean { +export function isRedirectSupportedByHosting(redirect: Redirect): boolean { return !("has" in redirect || pathHasRegex(redirect.source) || "internal" in redirect); } @@ -89,7 +98,7 @@ export function isRedirectSupportedByFirebase(redirect: Redirect): boolean { * - Custom header using regex for path matching. * - https://nextjs.org/docs/api-reference/next.config.js/headers#regex-path-matching */ -export function isHeaderSupportedByFirebase(header: Header): boolean { +export function isHeaderSupportedByHosting(header: Header): boolean { return !("has" in header || pathHasRegex(header.source)); } @@ -122,7 +131,7 @@ export function getNextjsRewritesToUse( * @return true if app directory is used in the Next.js project */ export function usesAppDirRouter(sourceDir: string): boolean { - const appPathRoutesManifestPath = join(sourceDir, "app-path-routes-manifest.json"); + const appPathRoutesManifestPath = join(sourceDir, APP_PATH_ROUTES_MANIFEST); return existsSync(appPathRoutesManifestPath); } /** @@ -131,7 +140,7 @@ export function usesAppDirRouter(sourceDir: string): boolean { * @return true if the Next.js project uses the next/image component */ export async function usesNextImage(sourceDir: string, distDir: string): Promise { - const exportMarker = await readJSON(join(sourceDir, distDir, "export-marker.json")); + const exportMarker = await readJSON(join(sourceDir, distDir, EXPORT_MARKER)); return exportMarker.isNextImageImported; } @@ -145,8 +154,60 @@ export async function usesNextImage(sourceDir: string, distDir: string): Promise * @return true if image optimization is disabled */ export async function hasUnoptimizedImage(sourceDir: string, distDir: string): Promise { - const imageManifest = await readJSON( - join(sourceDir, distDir, "images-manifest.json") - ); - return imageManifest.images.unoptimized; + const imagesManifest = await readJSON(join(sourceDir, distDir, IMAGES_MANIFEST)); + return imagesManifest.images.unoptimized; +} + +/** + * Whether Next.js middleware is being used + * + * @param dir in development must be the project root path, otherwise `distDir` + * @param isDevMode whether the project is running on dev or production + */ +export async function isUsingMiddleware(dir: string, isDevMode: boolean): Promise { + if (isDevMode) { + const [middlewareJs, middlewareTs] = await Promise.all([ + pathExists(join(dir, "middleware.js")), + pathExists(join(dir, "middleware.ts")), + ]); + + return middlewareJs || middlewareTs; + } else { + const middlewareManifest: MiddlewareManifest = await readJSON( + join(dir, "server", MIDDLEWARE_MANIFEST) + ); + + return Object.keys(middlewareManifest.middleware).length > 0; + } +} + +/** + * Whether image optimization is being used + * + * @param dir path to `distDir` - where the manifests are located + */ +export async function isUsingImageOptimization(dir: string): Promise { + const { isNextImageImported } = await readJSON(join(dir, EXPORT_MARKER)); + + if (isNextImageImported) { + const imagesManifest = await readJSON(join(dir, IMAGES_MANIFEST)); + const usingImageOptimization = imagesManifest.images.unoptimized === false; + + if (usingImageOptimization) { + return true; + } + } + + return false; +} + +/** + * Whether Next.js app directory is being used + * + * @param dir path to `distDir` - where the manifests are located + */ +export function isUsingAppDirectory(dir: string): boolean { + const appPathRoutesManifestPath = join(dir, APP_PATH_ROUTES_MANIFEST); + + return fileExistsSync(appPathRoutesManifestPath); } diff --git a/src/test/frameworks/next/helpers/images.ts b/src/test/frameworks/next/helpers/images.ts new file mode 100644 index 00000000000..394ee132560 --- /dev/null +++ b/src/test/frameworks/next/helpers/images.ts @@ -0,0 +1,50 @@ +import type { ExportMarker, ImagesManifest } from "../../../../frameworks/next/interfaces"; + +export const exportMarkerWithoutImage: ExportMarker = { + version: 1, + hasExportPathMap: false, + exportTrailingSlash: false, + isNextImageImported: false, +}; + +export const exportMarkerWithImage: ExportMarker = { + version: 1, + hasExportPathMap: false, + exportTrailingSlash: false, + isNextImageImported: true, +}; + +export const imagesManifest: ImagesManifest = { + version: 1, + images: { + deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], + imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], + path: "/_next/image", + loader: "default", + loaderFile: "", + domains: [], + disableStaticImages: false, + minimumCacheTTL: 60, + formats: ["image/avif", "image/webp"], + dangerouslyAllowSVG: false, + contentSecurityPolicy: "script-src 'none'; frame-src 'none'; sandbox;", + remotePatterns: [ + { + protocol: "https", + hostname: "^(?:^(?:assets\\.vercel\\.com)$)$", + port: "", + pathname: "^(?:\\/image\\/upload(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)|$))$", + }, + ], + unoptimized: false, + sizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840, 16, 32, 48, 64, 96, 128, 256, 384], + }, +}; + +export const imagesManifestUnoptimized: ImagesManifest = { + ...imagesManifest, + images: { + ...imagesManifest.images, + unoptimized: true, + }, +}; diff --git a/src/test/frameworks/next/helpers/index.ts b/src/test/frameworks/next/helpers/index.ts index 83772e4ea98..4a61b6042d1 100644 --- a/src/test/frameworks/next/helpers/index.ts +++ b/src/test/frameworks/next/helpers/index.ts @@ -2,3 +2,5 @@ export * from "./paths"; export * from "./headers"; export * from "./redirects"; export * from "./rewrites"; +export * from "./images"; +export * from "./middleware"; diff --git a/src/test/frameworks/next/helpers/middleware.ts b/src/test/frameworks/next/helpers/middleware.ts new file mode 100644 index 00000000000..28cdc466cb6 --- /dev/null +++ b/src/test/frameworks/next/helpers/middleware.ts @@ -0,0 +1,30 @@ +import type { MiddlewareManifest } from "next/dist/build/webpack/plugins/middleware-plugin"; + +export const middlewareManifestWhenUsed: MiddlewareManifest = { + sortedMiddleware: ["/"], + middleware: { + "/": { + env: [], + files: ["server/edge-runtime-webpack.js", "server/middleware.js"], + name: "middleware", + page: "/", + matchers: [ + { + regexp: + "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/([^/.]{1,}))\\/about(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?(.json)?[\\/#\\?]?$", + }, + ], + wasm: [], + assets: [], + }, + }, + functions: {}, + version: 2, +}; + +export const middlewareManifestWhenNotUsed: MiddlewareManifest = { + sortedMiddleware: [], + middleware: {}, + functions: {}, + version: 2, +}; diff --git a/src/test/frameworks/next/utils.spec.ts b/src/test/frameworks/next/utils.spec.ts index f8cecfc6870..776d13af163 100644 --- a/src/test/frameworks/next/utils.spec.ts +++ b/src/test/frameworks/next/utils.spec.ts @@ -3,18 +3,35 @@ import * as fs from "fs"; import * as fsExtra from "fs-extra"; import * as sinon from "sinon"; +import { + EXPORT_MARKER, + IMAGES_MANIFEST, + APP_PATH_ROUTES_MANIFEST, +} from "../../../frameworks/next/constants"; import { pathHasRegex, cleanEscapedChars, - isRewriteSupportedByFirebase, - isRedirectSupportedByFirebase, - isHeaderSupportedByFirebase, + isRewriteSupportedByHosting, + isRedirectSupportedByHosting, + isHeaderSupportedByHosting, getNextjsRewritesToUse, usesAppDirRouter, usesNextImage, hasUnoptimizedImage, + isUsingMiddleware, + isUsingImageOptimization, + isUsingAppDirectory, } from "../../../frameworks/next/utils"; +import * as frameworksUtils from "../../../frameworks/utils"; +import * as fsUtils from "../../../fsutils"; + import { + exportMarkerWithImage, + exportMarkerWithoutImage, + imagesManifest, + imagesManifestUnoptimized, + middlewareManifestWhenNotUsed, + middlewareManifestWhenUsed, pathsAsGlobs, pathsWithEscapedChars, pathsWithRegex, @@ -89,13 +106,13 @@ describe("Next.js utils", () => { describe("isRewriteSupportedByFirebase", () => { it("should allow supported rewrites", () => { for (const rewrite of supportedRewritesArray) { - expect(isRewriteSupportedByFirebase(rewrite)).to.be.true; + expect(isRewriteSupportedByHosting(rewrite)).to.be.true; } }); it("should disallow unsupported rewrites", () => { for (const rewrite of unsupportedRewritesArray) { - expect(isRewriteSupportedByFirebase(rewrite)).to.be.false; + expect(isRewriteSupportedByHosting(rewrite)).to.be.false; } }); }); @@ -103,13 +120,13 @@ describe("Next.js utils", () => { describe("isRedirectSupportedByFirebase", () => { it("should allow supported redirects", () => { for (const redirect of supportedRedirects) { - expect(isRedirectSupportedByFirebase(redirect)).to.be.true; + expect(isRedirectSupportedByHosting(redirect)).to.be.true; } }); it("should disallow unsupported redirects", () => { for (const redirect of unsupportedRedirects) { - expect(isRedirectSupportedByFirebase(redirect)).to.be.false; + expect(isRedirectSupportedByHosting(redirect)).to.be.false; } }); }); @@ -117,13 +134,13 @@ describe("Next.js utils", () => { describe("isHeaderSupportedByFirebase", () => { it("should allow supported headers", () => { for (const header of supportedHeaders) { - expect(isHeaderSupportedByFirebase(header)).to.be.true; + expect(isHeaderSupportedByHosting(header)).to.be.true; } }); it("should disallow unsupported headers", () => { for (const header of unsupportedHeaders) { - expect(isHeaderSupportedByFirebase(header)).to.be.false; + expect(isHeaderSupportedByHosting(header)).to.be.false; } }); }); @@ -222,4 +239,77 @@ describe("Next.js utils", () => { expect(await hasUnoptimizedImage("", "")).to.be.false; }); }); + + describe("isUsingMiddleware", () => { + let sandbox: sinon.SinonSandbox; + beforeEach(() => (sandbox = sinon.createSandbox())); + afterEach(() => sandbox.restore()); + + it("should return true if using middleware in development", async () => { + sandbox.stub(fsExtra, "pathExists").resolves(true); + expect(await isUsingMiddleware("", true)).to.be.true; + }); + + it("should return false if not using middleware in development", async () => { + sandbox.stub(fsExtra, "pathExists").resolves(false); + expect(await isUsingMiddleware("", true)).to.be.false; + }); + + it("should return true if using middleware in production", async () => { + sandbox.stub(fsExtra, "readJSON").resolves(middlewareManifestWhenUsed); + expect(await isUsingMiddleware("", false)).to.be.true; + }); + + it("should return false if not using middleware in production", async () => { + sandbox.stub(fsExtra, "readJSON").resolves(middlewareManifestWhenNotUsed); + expect(await isUsingMiddleware("", false)).to.be.false; + }); + }); + + describe("isUsingImageOptimization", () => { + let sandbox: sinon.SinonSandbox; + beforeEach(() => (sandbox = sinon.createSandbox())); + afterEach(() => sandbox.restore()); + + it("should return true if images optimization is used", async () => { + const stub = sandbox.stub(frameworksUtils, "readJSON"); + stub.withArgs(EXPORT_MARKER).resolves(exportMarkerWithImage); + stub.withArgs(IMAGES_MANIFEST).resolves(imagesManifest); + + expect(await isUsingImageOptimization("")).to.be.true; + }); + + it("should return false if isNextImageImported is false", async () => { + const stub = sandbox.stub(frameworksUtils, "readJSON"); + stub.withArgs(EXPORT_MARKER).resolves(exportMarkerWithoutImage); + + expect(await isUsingImageOptimization("")).to.be.false; + }); + + it("should return false if `unoptimized` option is used", async () => { + const stub = sandbox.stub(frameworksUtils, "readJSON"); + stub.withArgs(EXPORT_MARKER).resolves(exportMarkerWithImage); + stub.withArgs(IMAGES_MANIFEST).resolves(imagesManifestUnoptimized); + + expect(await isUsingImageOptimization("")).to.be.false; + }); + }); + + describe("isUsingAppDirectory", () => { + let sandbox: sinon.SinonSandbox; + beforeEach(() => (sandbox = sinon.createSandbox())); + afterEach(() => sandbox.restore()); + + it(`should return true if ${APP_PATH_ROUTES_MANIFEST} exists`, () => { + sandbox.stub(fsUtils, "fileExistsSync").returns(true); + + expect(isUsingAppDirectory("")).to.be.true; + }); + + it(`should return false if ${APP_PATH_ROUTES_MANIFEST} did not exist`, () => { + sandbox.stub(fsUtils, "fileExistsSync").returns(false); + + expect(isUsingAppDirectory("")).to.be.false; + }); + }); }); From 2a25c5f26dbbbecc3a8097698f906ecf3bc1c952 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Wed, 14 Dec 2022 15:35:14 -0500 Subject: [PATCH 0727/1699] When using v2 functions enable Compute Service API and grant its P4SA necessary IAM roles (#5338) --- CHANGELOG.md | 1 + src/deploy/extensions/prepare.ts | 6 ++ src/deploy/extensions/v2FunctionHelper.ts | 66 +++++++++++++++ .../extensions/v2FunctionHelper.spec.ts | 83 +++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 src/deploy/extensions/v2FunctionHelper.ts create mode 100644 src/test/deploy/extensions/v2FunctionHelper.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 06dd776ac68..9222ed5c7b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Adds user-defined env vars into the functions emulator (#5330). - Support Next.js Middleware (#5320) - Log the reason for a Cloud Function if needed in Next.js (#5320) +- Fixed service enablement when installing extensions with v2 functions (#5338) diff --git a/src/deploy/extensions/prepare.ts b/src/deploy/extensions/prepare.ts index 74c32613f29..dca4de8c357 100644 --- a/src/deploy/extensions/prepare.ts +++ b/src/deploy/extensions/prepare.ts @@ -13,6 +13,7 @@ import { ensureSecretManagerApiEnabled } from "../../extensions/secretsUtils"; import { checkSpecForSecrets } from "./secrets"; import { displayWarningsForDeploy, outOfBandChangesWarning } from "../../extensions/warnings"; import { detectEtagChanges } from "../../extensions/etags"; +import { checkSpecForV2Functions, ensureNecessaryV2ApisAndRoles } from "./v2FunctionHelper"; export async function prepare(context: Context, options: Options, payload: Payload) { const projectId = needProjectId(options); @@ -58,6 +59,11 @@ export async function prepare(context: Context, options: Options, payload: Paylo await ensureSecretManagerApiEnabled(options); } + const usingV2Functions = await Promise.all(context.want?.map(checkSpecForV2Functions)); + if (usingV2Functions) { + await ensureNecessaryV2ApisAndRoles(options); + } + payload.instancesToCreate = context.want.filter((i) => !context.have?.some(matchesInstanceId(i))); payload.instancesToConfigure = context.want.filter((i) => context.have?.some(isConfigure(i))); payload.instancesToUpdate = context.want.filter((i) => context.have?.some(isUpdate(i))); diff --git a/src/deploy/extensions/v2FunctionHelper.ts b/src/deploy/extensions/v2FunctionHelper.ts new file mode 100644 index 00000000000..70ae2f6983e --- /dev/null +++ b/src/deploy/extensions/v2FunctionHelper.ts @@ -0,0 +1,66 @@ +import { getProjectNumber } from "../../getProjectNumber"; +import * as resourceManager from "../../gcp/resourceManager"; +import { logger } from "../../logger"; +import { FirebaseError } from "../../error"; +import { ensure } from "../../ensureApiEnabled"; +import * as planner from "./planner"; +import { needProjectId } from "../../projectUtils"; + +const SERVICE_AGENT_ROLE = "roles/eventarc.eventReceiver"; + +/** + * Checks whether spec contains v2 function resource. + */ +export async function checkSpecForV2Functions(i: planner.InstanceSpec): Promise { + const extensionSpec = await planner.getExtensionSpec(i); + return extensionSpec.resources.some((r) => r.type === "firebaseextensions.v1beta.v2function"); +} + +/** + * Enables APIs and grants roles necessary for running v2 functions. + */ +export async function ensureNecessaryV2ApisAndRoles(options: any) { + const projectId = needProjectId(options); + await ensure(projectId, "compute.googleapis.com", "extensions", options.markdown); + await ensureComputeP4SARole(projectId); +} + +async function ensureComputeP4SARole(projectId: string): Promise { + const projectNumber = await getProjectNumber({ projectId }); + const saEmail = `${projectNumber}-compute@developer.gserviceaccount.com`; + + let policy; + try { + policy = await resourceManager.getIamPolicy(projectId); + } catch (e) { + if (e instanceof FirebaseError && e.status === 403) { + throw new FirebaseError( + "Unable to get project IAM policy, permission denied (403). Please " + + "make sure you have sufficient project privileges or if this is a brand new project " + + "try again in a few minutes." + ); + } + throw e; + } + + if ( + policy.bindings.find( + (b) => b.role === SERVICE_AGENT_ROLE && b.members.includes("serviceAccount:" + saEmail) + ) + ) { + logger.debug("Compute Service API Agent IAM policy OK"); + return true; + } else { + logger.debug( + "Firebase Extensions Service Agent is missing a required IAM role " + + "`Firebase Extensions API Service Agent`." + ); + policy.bindings.push({ + role: SERVICE_AGENT_ROLE, + members: ["serviceAccount:" + saEmail], + }); + await resourceManager.setIamPolicy(projectId, policy, "bindings"); + logger.debug("Compute Service API Agent IAM policy updated successfully"); + return true; + } +} diff --git a/src/test/deploy/extensions/v2FunctionHelper.spec.ts b/src/test/deploy/extensions/v2FunctionHelper.spec.ts new file mode 100644 index 00000000000..ed8ff168add --- /dev/null +++ b/src/test/deploy/extensions/v2FunctionHelper.spec.ts @@ -0,0 +1,83 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import * as resourceManager from "../../../gcp/resourceManager"; +import * as pn from "../../../getProjectNumber"; +import * as v2FunctionHelper from "../../../deploy/extensions/v2FunctionHelper"; +import * as ensureApiEnabled from "../../../ensureApiEnabled"; +import * as projectUtils from "../../../projectUtils"; + +const GOOD_BINDING = { + role: "roles/eventarc.eventReceiver", + members: ["serviceAccount:123456-compute@developer.gserviceaccount.com"], +}; + +describe("ensureNecessaryV2ApisAndRoles", () => { + let getIamStub: sinon.SinonStub; + let setIamStub: sinon.SinonStub; + let needProjectIdStub: sinon.SinonStub; + let getProjectNumberStub: sinon.SinonStub; + let ensureApiEnabledStub: sinon.SinonStub; + + beforeEach(() => { + getIamStub = sinon + .stub(resourceManager, "getIamPolicy") + .throws("unexpected call to resourceManager.getIamStub"); + setIamStub = sinon + .stub(resourceManager, "setIamPolicy") + .throws("unexpected call to resourceManager.setIamPolicy"); + needProjectIdStub = sinon + .stub(projectUtils, "needProjectId") + .throws("unexpected call to pn.getProjectNumber"); + getProjectNumberStub = sinon + .stub(pn, "getProjectNumber") + .throws("unexpected call to pn.getProjectNumber"); + ensureApiEnabledStub = sinon + .stub(ensureApiEnabled, "ensure") + .throws("unexpected call to ensureApiEnabled.ensure"); + + getProjectNumberStub.resolves(123456); + needProjectIdStub.returns("project_id"); + ensureApiEnabledStub.resolves(undefined); + }); + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("should succeed when IAM policy is correct", async () => { + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [GOOD_BINDING], + }); + + expect(await v2FunctionHelper.ensureNecessaryV2ApisAndRoles({ projectId: "project_id" })).to.not + .throw; + + expect(getIamStub).to.have.been.calledWith("project_id"); + expect(setIamStub).to.not.have.been.called; + }); + + it("should fix the IAM policy by adding missing bindings", async () => { + getIamStub.resolves({ + etag: "etag", + version: 3, + bindings: [], + }); + setIamStub.resolves(); + + expect(await v2FunctionHelper.ensureNecessaryV2ApisAndRoles({ projectId: "project_id" })).to.not + .throw; + + expect(getIamStub).to.have.been.calledWith("project_id"); + expect(setIamStub).to.have.been.calledWith( + "project_id", + { + etag: "etag", + version: 3, + bindings: [GOOD_BINDING], + }, + "bindings" + ); + }); +}); From 0c3a6881a1826a8fbef184fc3b9efef088f57789 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 15 Dec 2022 12:40:33 -0800 Subject: [PATCH 0728/1699] Fix bug where environment variables for an emulated function did not consider emulators running remotely. (#5269) https://github.com/firebase/firebase-tools/pull/5079 refactored the way to set environment variables on Functions Emulator. As implemented, the refactored function does not consider "remote" emulators i.e. emulator started by another instance of `emulators:start` command. This broke users who relies this to connect `functions:shell` command to the remote emulators. Fixes https://github.com/firebase/firebase-tools/issues/5225 --- CHANGELOG.md | 1 + scripts/triggers-end-to-end-tests/tests.ts | 0 src/emulator/commandUtils.ts | 3 +- src/emulator/env.ts | 74 ++++++++++------------ src/emulator/functionsEmulator.ts | 8 ++- 5 files changed, 44 insertions(+), 42 deletions(-) mode change 100755 => 100644 scripts/triggers-end-to-end-tests/tests.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9222ed5c7b5..d96f38d154f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Support Next.js Middleware (#5320) - Log the reason for a Cloud Function if needed in Next.js (#5320) - Fixed service enablement when installing extensions with v2 functions (#5338) +- Fix bug where functions:shell command didn't connect to emulators running on other processes. (#5269) diff --git a/scripts/triggers-end-to-end-tests/tests.ts b/scripts/triggers-end-to-end-tests/tests.ts old mode 100755 new mode 100644 diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index 7f3aeadd5a2..1bea0bc62b7 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -330,7 +330,8 @@ async function runScript(script: string, extraEnv: Record): Prom const env: NodeJS.ProcessEnv = { ...process.env, ...extraEnv }; - setEnvVarsForEmulators(env); + const emulatorInfos = EmulatorRegistry.listRunningWithInfo(); + setEnvVarsForEmulators(env, emulatorInfos); const proc = childProcess.spawn(script, { stdio: ["inherit", "inherit", "inherit"] as childProcess.StdioOptions, diff --git a/src/emulator/env.ts b/src/emulator/env.ts index 05524e25adf..f8ef67709f2 100644 --- a/src/emulator/env.ts +++ b/src/emulator/env.ts @@ -1,47 +1,43 @@ import { Constants } from "./constants"; -import { Emulators } from "./types"; -import { EmulatorRegistry } from "./registry"; +import { EmulatorInfo, Emulators } from "./types"; +import { formatHost } from "./functionsEmulatorShared"; /** * Adds or replaces emulator-related env vars (for Admin SDKs, etc.). * @param env a `process.env`-like object or Record to be modified + * @param emulators the emulator info to use */ -export function setEnvVarsForEmulators(env: Record): void { - if (EmulatorRegistry.isRunning(Emulators.DATABASE)) { - env[Constants.FIREBASE_DATABASE_EMULATOR_HOST] = EmulatorRegistry.url(Emulators.DATABASE).host; - } - - if (EmulatorRegistry.isRunning(Emulators.FIRESTORE)) { - const { host } = EmulatorRegistry.url(Emulators.FIRESTORE); - env[Constants.FIRESTORE_EMULATOR_HOST] = host; - env[Constants.FIRESTORE_EMULATOR_ENV_ALT] = host; - } - - if (EmulatorRegistry.isRunning(Emulators.STORAGE)) { - const { host } = EmulatorRegistry.url(Emulators.STORAGE); - env[Constants.FIREBASE_STORAGE_EMULATOR_HOST] = host; - // The protocol is required for the Google Cloud Storage Node.js Client SDK. - env[Constants.CLOUD_STORAGE_EMULATOR_HOST] = `http://${host}`; - } - - if (EmulatorRegistry.isRunning(Emulators.AUTH)) { - env[Constants.FIREBASE_AUTH_EMULATOR_HOST] = EmulatorRegistry.url(Emulators.AUTH).host; - } - - if (EmulatorRegistry.isRunning(Emulators.HUB)) { - env[Constants.FIREBASE_EMULATOR_HUB] = EmulatorRegistry.url(Emulators.HUB).host; - } - - const pubsubEmulator = EmulatorRegistry.isRunning(Emulators.PUBSUB); - if (pubsubEmulator) { - env[Constants.PUBSUB_EMULATOR_HOST] = EmulatorRegistry.url(Emulators.PUBSUB).host; - } - - if (EmulatorRegistry.isRunning(Emulators.EVENTARC)) { - // The protocol is required for the Firebase Admin Node.js SDK for Eventarc. - // https://github.com/firebase/firebase-admin-node/blob/ee60cd1acb8722ba4081b9837d2f90101e2b3227/src/eventarc/eventarc-client-internal.ts#L105 - env[Constants.CLOUD_EVENTARC_EMULATOR_HOST] = `http://${ - EmulatorRegistry.url(Emulators.EVENTARC).host - }`; +export function setEnvVarsForEmulators( + env: Record, + emulators: EmulatorInfo[] +): void { + for (const emu of emulators) { + const host = formatHost(emu); + switch (emu.name) { + case Emulators.FIRESTORE: + env[Constants.FIRESTORE_EMULATOR_HOST] = host; + env[Constants.FIRESTORE_EMULATOR_ENV_ALT] = host; + break; + case Emulators.DATABASE: + env[Constants.FIREBASE_DATABASE_EMULATOR_HOST] = host; + break; + case Emulators.STORAGE: + env[Constants.FIREBASE_STORAGE_EMULATOR_HOST] = host; + // The protocol is required for the Google Cloud Storage Node.js Client SDK. + env[Constants.CLOUD_STORAGE_EMULATOR_HOST] = `http://${host}`; + break; + case Emulators.AUTH: + env[Constants.FIREBASE_AUTH_EMULATOR_HOST] = host; + break; + case Emulators.HUB: + env[Constants.FIREBASE_EMULATOR_HUB] = host; + break; + case Emulators.PUBSUB: + env[Constants.PUBSUB_EMULATOR_HOST] = host; + break; + case Emulators.EVENTARC: + env[Constants.CLOUD_EVENTARC_EMULATOR_HOST] = `http://${host}`; + break; + } } } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index e04caae8367..499826d0de0 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -116,7 +116,7 @@ export interface FunctionsEmulatorArgs { quiet?: boolean; disabledRuntimeFeatures?: FunctionsRuntimeFeatures; debugPort?: number; - remoteEmulators?: { [key: string]: EmulatorInfo }; + remoteEmulators?: Record; adminSdkConfig?: AdminSdkConfig; projectAlias?: string; } @@ -1160,7 +1160,11 @@ export class FunctionsEmulator implements EmulatorInstance { enableCors: true, }); - setEnvVarsForEmulators(envs); + let emulatorInfos = EmulatorRegistry.listRunningWithInfo(); + if (this.args.remoteEmulators) { + emulatorInfos = emulatorInfos.concat(Object.values(this.args.remoteEmulators)); + } + setEnvVarsForEmulators(envs, emulatorInfos); if (this.args.debugPort) { // Start runtime in debug mode to allow triggers to share single runtime process. From d073b0959637cc780b32444b880f54712ea130ed Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 15 Dec 2022 17:45:40 -0500 Subject: [PATCH 0729/1699] When deploying Next.js utilize esbuild via NPX (#5336) * Drop esbuild as a dependency of firebase-tools * Spawn npx esbuild to bundle next.config.js when deploying a Next.js application * Widen the net on externals, we were missing deps of deps --- CHANGELOG.md | 1 + npm-shrinkwrap.json | 608 +++++++++++++++++++--- package.json | 3 - src/frameworks/next/index.ts | 48 +- src/frameworks/next/interfaces.ts | 16 + src/frameworks/next/utils.ts | 23 +- src/test/frameworks/next/helpers/index.ts | 1 + src/test/frameworks/next/helpers/npm.ts | 129 +++++ src/test/frameworks/next/utils.spec.ts | 47 ++ 9 files changed, 789 insertions(+), 87 deletions(-) create mode 100644 src/test/frameworks/next/helpers/npm.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d96f38d154f..fc9926853ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Remove esbuild dependency, instead bundle Next.js configuration on deploy with NPX (#5336) - Add sharp NPM module to Cloud Functions when using Next.js Image Optimization (#5238) - Adds user-defined env vars into the functions emulator (#5330). - Support Next.js Middleware (#5320) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c4cb36e76d3..346a4c13aa6 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -160,9 +160,6 @@ }, "engines": { "node": "^14.18.0 || >=16.4.0" - }, - "optionalDependencies": { - "esbuild": "^0.15.7" } }, "node_modules/@ampproject/remapping": { @@ -698,6 +695,38 @@ "node": "^14 || ^16 || ^17 || ^18" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", @@ -5793,10 +5822,10 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.15.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.8.tgz", - "integrity": "sha512-Remsk2dmr1Ia65sU+QasE6svJbsHe62lzR+CnjpUvbZ+uSYo1SitiOWPRfZQkCu82YWZBBKXiD/j0i//XWMZ+Q==", - "devOptional": true, + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -5805,37 +5834,70 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.15.8", - "@esbuild/linux-loong64": "0.15.8", - "esbuild-android-64": "0.15.8", - "esbuild-android-arm64": "0.15.8", - "esbuild-darwin-64": "0.15.8", - "esbuild-darwin-arm64": "0.15.8", - "esbuild-freebsd-64": "0.15.8", - "esbuild-freebsd-arm64": "0.15.8", - "esbuild-linux-32": "0.15.8", - "esbuild-linux-64": "0.15.8", - "esbuild-linux-arm": "0.15.8", - "esbuild-linux-arm64": "0.15.8", - "esbuild-linux-mips64le": "0.15.8", - "esbuild-linux-ppc64le": "0.15.8", - "esbuild-linux-riscv64": "0.15.8", - "esbuild-linux-s390x": "0.15.8", - "esbuild-netbsd-64": "0.15.8", - "esbuild-openbsd-64": "0.15.8", - "esbuild-sunos-64": "0.15.8", - "esbuild-windows-32": "0.15.8", - "esbuild-windows-64": "0.15.8", - "esbuild-windows-arm64": "0.15.8" + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, "node_modules/esbuild-darwin-64": { - "version": "0.15.8", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.8.tgz", - "integrity": "sha512-KaKcGfJ+yto7Fo5gAj3xwxHMd1fBIKatpCHK8znTJLVv+9+NN2/tIPBqA4w5rBwjX0UqXDeIE2v1xJP+nGEXgA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -5844,6 +5906,278 @@ "node": ">=12" } }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -15681,6 +16015,20 @@ "jsdoc-type-pratt-parser": "~3.0.1" } }, + "@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "dev": true, + "optional": true + }, "@eslint/eslintrc": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", @@ -19782,39 +20130,173 @@ "dev": true }, "esbuild": { - "version": "0.15.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.8.tgz", - "integrity": "sha512-Remsk2dmr1Ia65sU+QasE6svJbsHe62lzR+CnjpUvbZ+uSYo1SitiOWPRfZQkCu82YWZBBKXiD/j0i//XWMZ+Q==", - "devOptional": true, - "requires": { - "@esbuild/android-arm": "0.15.8", - "@esbuild/linux-loong64": "0.15.8", - "esbuild-android-64": "0.15.8", - "esbuild-android-arm64": "0.15.8", - "esbuild-darwin-64": "0.15.8", - "esbuild-darwin-arm64": "0.15.8", - "esbuild-freebsd-64": "0.15.8", - "esbuild-freebsd-arm64": "0.15.8", - "esbuild-linux-32": "0.15.8", - "esbuild-linux-64": "0.15.8", - "esbuild-linux-arm": "0.15.8", - "esbuild-linux-arm64": "0.15.8", - "esbuild-linux-mips64le": "0.15.8", - "esbuild-linux-ppc64le": "0.15.8", - "esbuild-linux-riscv64": "0.15.8", - "esbuild-linux-s390x": "0.15.8", - "esbuild-netbsd-64": "0.15.8", - "esbuild-openbsd-64": "0.15.8", - "esbuild-sunos-64": "0.15.8", - "esbuild-windows-32": "0.15.8", - "esbuild-windows-64": "0.15.8", - "esbuild-windows-arm64": "0.15.8" - } + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "dev": true, + "optional": true }, "esbuild-darwin-64": { - "version": "0.15.8", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.8.tgz", - "integrity": "sha512-KaKcGfJ+yto7Fo5gAj3xwxHMd1fBIKatpCHK8znTJLVv+9+NN2/tIPBqA4w5rBwjX0UqXDeIE2v1xJP+nGEXgA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "dev": true, "optional": true }, "escalade": { diff --git a/package.json b/package.json index e74c2adf0a5..c20ef17ba25 100644 --- a/package.json +++ b/package.json @@ -239,8 +239,5 @@ "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1", "vite": "^3.1.0" - }, - "optionalDependencies": { - "esbuild": "^0.15.7" } } diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index 459e6e4aa4f..9e1b8690e37 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -1,4 +1,4 @@ -import { execSync } from "child_process"; +import { execSync, spawnSync } from "child_process"; import { mkdir, copyFile } from "fs/promises"; import { dirname, join } from "path"; import type { NextConfig } from "next"; @@ -22,7 +22,6 @@ import { SupportLevel, } from ".."; import { promptOnce } from "../../prompt"; -import { logger } from "../../logger"; import { FirebaseError } from "../../error"; import { cleanEscapedChars, @@ -33,8 +32,9 @@ import { isUsingAppDirectory, isUsingImageOptimization, isUsingMiddleware, + allDependencyNames, } from "./utils"; -import type { Manifest } from "./interfaces"; +import type { Manifest, NpmLsReturn } from "./interfaces"; import { readJSON } from "../utils"; import { warnIfCustomBuildScript } from "../utils"; import type { EmulatorInfo } from "../../emulator/types"; @@ -344,25 +344,33 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: st const { distDir } = await getConfig(sourceDir); const packageJson = await readJSON(join(sourceDir, "package.json")); if (existsSync(join(sourceDir, "next.config.js"))) { - let esbuild; - try { - esbuild = await import("esbuild"); - } catch (e: unknown) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - logger.debug(`Failed to load 'esbuild': ${e}`); - throw new FirebaseError( - `Unable to find 'esbuild'. Install it into your local dev dependencies with 'npm i --save-dev esbuild''` + // Bundle their next.config.js with esbuild via NPX, pinned version was having troubles on m1 + // macs and older Node versions; either way, we should avoid taking on any deps in firebase-tools + // Alternatively I tried using @swc/spack and the webpack bundled into Next.js but was + // encountering difficulties with both of those + const dependencyTree: NpmLsReturn = JSON.parse( + spawnSync("npm", ["ls", "--omit=dev", "--all", "--json"], { + cwd: sourceDir, + }).stdout.toString() + ); + // Mark all production deps as externals, so they aren't bundled + // DevDeps won't be included in the Cloud Function, so they should be bundled + const esbuildArgs = allDependencyNames(dependencyTree) + .map((it) => `--external:${it}`) + .concat( + "--bundle", + "--platform=node", + `--target=node${NODE_VERSION}`, + `--outdir=${destDir}`, + "--log-level=error" ); - } - await esbuild.build({ - bundle: true, - external: Object.keys(packageJson.dependencies), - absWorkingDir: sourceDir, - entryPoints: ["next.config.js"], - outfile: join(destDir, "next.config.js"), - target: `node${NODE_VERSION}`, - platform: "node", + const bundle = spawnSync("npx", ["--yes", "esbuild", "next.config.js", ...esbuildArgs], { + cwd: sourceDir, }); + if (bundle.status) { + console.error(bundle.stderr.toString()); + throw new FirebaseError("Unable to bundle next.config.js for use in Cloud Functions"); + } } if (await pathExists(join(sourceDir, "public"))) { await mkdir(join(destDir, "public")); diff --git a/src/frameworks/next/interfaces.ts b/src/frameworks/next/interfaces.ts index 31b6d09b95d..ecdab1bee46 100644 --- a/src/frameworks/next/interfaces.ts +++ b/src/frameworks/next/interfaces.ts @@ -44,3 +44,19 @@ export interface ImagesManifest { sizes: number[]; }; } + +export interface NpmLsDepdendency { + version?: string; + resolved?: string; + dependencies?: { + [key: string]: NpmLsDepdendency; + }; +} + +export interface NpmLsReturn { + version: string; + name: string; + dependencies: { + [key: string]: NpmLsDepdendency; + }; +} diff --git a/src/frameworks/next/utils.ts b/src/frameworks/next/utils.ts index 852088e6b04..793a8cd1245 100644 --- a/src/frameworks/next/utils.ts +++ b/src/frameworks/next/utils.ts @@ -5,7 +5,13 @@ import type { Header, Redirect, Rewrite } from "next/dist/lib/load-custom-routes import type { MiddlewareManifest } from "next/dist/build/webpack/plugins/middleware-plugin"; import { isUrl, readJSON } from "../utils"; -import type { Manifest, RoutesManifestRewrite, ExportMarker, ImagesManifest } from "./interfaces"; +import type { + Manifest, + RoutesManifestRewrite, + ExportMarker, + ImagesManifest, + NpmLsDepdendency, +} from "./interfaces"; import { APP_PATH_ROUTES_MANIFEST, EXPORT_MARKER, @@ -211,3 +217,18 @@ export function isUsingAppDirectory(dir: string): boolean { return fileExistsSync(appPathRoutesManifestPath); } + +/** + * Given input from `npm ls` flatten the dependency tree and return all module names + * + * @param dependencies returned from `npm ls` + */ +export function allDependencyNames(mod: NpmLsDepdendency): string[] { + if (!mod.dependencies) return []; + const dependencyNames = Object.keys(mod.dependencies).reduce( + (acc, it) => [...acc, it, ...allDependencyNames(mod.dependencies![it])], + [] as string[] + ); + // deduplicate the names + return [...new Set(dependencyNames)]; +} diff --git a/src/test/frameworks/next/helpers/index.ts b/src/test/frameworks/next/helpers/index.ts index 4a61b6042d1..c399ba13819 100644 --- a/src/test/frameworks/next/helpers/index.ts +++ b/src/test/frameworks/next/helpers/index.ts @@ -4,3 +4,4 @@ export * from "./redirects"; export * from "./rewrites"; export * from "./images"; export * from "./middleware"; +export * from "./npm"; diff --git a/src/test/frameworks/next/helpers/npm.ts b/src/test/frameworks/next/helpers/npm.ts new file mode 100644 index 00000000000..64972c1afe1 --- /dev/null +++ b/src/test/frameworks/next/helpers/npm.ts @@ -0,0 +1,129 @@ +import { NpmLsReturn } from "../../../../frameworks/next/interfaces"; + +export const npmLsReturn: NpmLsReturn = { + version: "0.1.0", + name: "next-next", + dependencies: { + "@next/font": { + version: "13.0.6", + resolved: "https://registry.npmjs.org/@next/font/-/font-13.0.6.tgz", + }, + next: { + version: "13.0.6", + resolved: "https://registry.npmjs.org/next/-/next-13.0.6.tgz", + dependencies: { + "@next/env": { + version: "13.0.6", + resolved: "https://registry.npmjs.org/@next/env/-/env-13.0.6.tgz", + }, + "@next/swc-android-arm-eabi": {}, + "@next/swc-android-arm64": {}, + "@next/swc-darwin-arm64": {}, + "@next/swc-darwin-x64": { + version: "13.0.6", + resolved: "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.6.tgz", + }, + "@next/swc-freebsd-x64": {}, + "@next/swc-linux-arm-gnueabihf": {}, + "@next/swc-linux-arm64-gnu": {}, + "@next/swc-linux-arm64-musl": {}, + "@next/swc-linux-x64-gnu": {}, + "@next/swc-linux-x64-musl": {}, + "@next/swc-win32-arm64-msvc": {}, + "@next/swc-win32-ia32-msvc": {}, + "@next/swc-win32-x64-msvc": {}, + "@swc/helpers": { + version: "0.4.14", + resolved: "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + dependencies: { + tslib: { + version: "2.4.1", + resolved: "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + }, + }, + }, + "caniuse-lite": { + version: "1.0.30001439", + resolved: "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz", + }, + fibers: {}, + "node-sass": {}, + postcss: { + version: "8.4.14", + resolved: "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + dependencies: { + nanoid: { + version: "3.3.4", + resolved: "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + }, + picocolors: { + version: "1.0.0", + resolved: "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + }, + "source-map-js": { + version: "1.0.2", + resolved: "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + }, + }, + }, + "react-dom": { + version: "18.2.0", + }, + react: { + version: "18.2.0", + }, + sass: {}, + "styled-jsx": { + version: "5.1.0", + resolved: "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.0.tgz", + dependencies: { + "client-only": { + version: "0.0.1", + resolved: "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + }, + react: { + version: "18.2.0", + }, + }, + }, + }, + }, + "react-dom": { + version: "18.2.0", + resolved: "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + dependencies: { + "loose-envify": { + version: "1.4.0", + resolved: "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + dependencies: { + "js-tokens": { + version: "4.0.0", + resolved: "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + }, + }, + }, + react: { + version: "18.2.0", + }, + scheduler: { + version: "0.23.0", + resolved: "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + dependencies: { + "loose-envify": { + version: "1.4.0", + }, + }, + }, + }, + }, + react: { + version: "18.2.0", + resolved: "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + dependencies: { + "loose-envify": { + version: "1.4.0", + }, + }, + }, + }, +}; diff --git a/src/test/frameworks/next/utils.spec.ts b/src/test/frameworks/next/utils.spec.ts index 776d13af163..31d5877dc49 100644 --- a/src/test/frameworks/next/utils.spec.ts +++ b/src/test/frameworks/next/utils.spec.ts @@ -21,6 +21,7 @@ import { isUsingMiddleware, isUsingImageOptimization, isUsingAppDirectory, + allDependencyNames, } from "../../../frameworks/next/utils"; import * as frameworksUtils from "../../../frameworks/utils"; import * as fsUtils from "../../../fsutils"; @@ -43,6 +44,7 @@ import { unsupportedHeaders, unsupportedRedirects, unsupportedRewritesArray, + npmLsReturn, } from "./helpers"; describe("Next.js utils", () => { @@ -312,4 +314,49 @@ describe("Next.js utils", () => { expect(isUsingAppDirectory("")).to.be.false; }); }); + + describe("allDependencyNames", () => { + it("should return empty on stopping conditions", () => { + expect(allDependencyNames({})).to.eql([]); + expect(allDependencyNames({ version: "foo" })).to.eql([]); + }); + + it("should return expected dependency names", () => { + expect(allDependencyNames(npmLsReturn)).to.eql([ + "@next/font", + "next", + "@next/env", + "@next/swc-android-arm-eabi", + "@next/swc-android-arm64", + "@next/swc-darwin-arm64", + "@next/swc-darwin-x64", + "@next/swc-freebsd-x64", + "@next/swc-linux-arm-gnueabihf", + "@next/swc-linux-arm64-gnu", + "@next/swc-linux-arm64-musl", + "@next/swc-linux-x64-gnu", + "@next/swc-linux-x64-musl", + "@next/swc-win32-arm64-msvc", + "@next/swc-win32-ia32-msvc", + "@next/swc-win32-x64-msvc", + "@swc/helpers", + "tslib", + "caniuse-lite", + "fibers", + "node-sass", + "postcss", + "nanoid", + "picocolors", + "source-map-js", + "react-dom", + "react", + "sass", + "styled-jsx", + "client-only", + "loose-envify", + "js-tokens", + "scheduler", + ]); + }); + }); }); From 699c2a074abdf8be19b1d6c1382c28c340b5612e Mon Sep 17 00:00:00 2001 From: Corey Sewell Date: Fri, 16 Dec 2022 14:48:35 +1300 Subject: [PATCH 0730/1699] Copy project .npmrc to function dist (#5235) To support private repositories and custom npm configs, copy the projects .npmrc file to the functions dist dir before deploying web frameworks to Cloud Functions Co-authored-by: James Daniels --- CHANGELOG.md | 1 + src/frameworks/index.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9926853ae..1a82ac737da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Respect .npmrc in backends spun up for web frameworks (#5235) - Remove esbuild dependency, instead bundle Next.js configuration on deploy with NPX (#5336) - Add sharp NPM module to Cloud Functions when using Next.js Image Optimization (#5238) - Adds user-defined env vars into the functions emulator (#5330). diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 7dc172cd72e..2a6222b9b11 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -519,6 +519,10 @@ ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\ // continue }); + if (await pathExists(getProjectPath(".npmrc"))) { + await copyFile(getProjectPath(".npmrc"), join(functionsDist, ".npmrc")); + } + execSync(`${NPM_COMMAND} i --omit dev --no-audit`, { cwd: functionsDist, stdio: "inherit", From 77e6e6d99cbaf52a924b9451cd02c990b1458f1c Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 16 Dec 2022 10:55:49 -0800 Subject: [PATCH 0731/1699] Bump storage rules emulator version (#5342) * Bump storage rules emulator version; correct null_value field accordingly * Update changelog * Format --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- src/emulator/storage/rules/runtime.ts | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a82ac737da..4d8cb15c9e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ - Log the reason for a Cloud Function if needed in Next.js (#5320) - Fixed service enablement when installing extensions with v2 functions (#5338) - Fix bug where functions:shell command didn't connect to emulators running on other processes. (#5269) +- Fixed bug with Cross-Service Rules integration for Firestore documents containing nulls (#5342) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index d307cc5567f..4bdf7d6bfdc 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -38,9 +38,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee", }, storage: { - version: "1.1.1", - expectedSize: 46448285, - expectedChecksum: "691982db4019d49d345a97151bdea7e2", + version: "1.1.2", + expectedSize: 47028740, + expectedChecksum: "983b4415b1e72b109864f1b8e7ea7546", }, ui: experiments.isEnabled("emulatoruisnapshot") ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" } diff --git a/src/emulator/storage/rules/runtime.ts b/src/emulator/storage/rules/runtime.ts index 1f53eb05bfd..127cddd8890 100644 --- a/src/emulator/storage/rules/runtime.ts +++ b/src/emulator/storage/rules/runtime.ts @@ -415,7 +415,7 @@ function toExpressionValue(obj: any): ExpressionValue { if (obj == null) { return { - null_value: 0, + null_value: null, }; } @@ -484,7 +484,7 @@ function createRequestExpressionValue(opts: RulesetVerificationOpts): Expression }, time: toExpressionValue(new Date()), resource: toExpressionValue(opts.file.after ? opts.file.after : null), - auth: opts.token ? createAuthExpressionValue(opts) : { null_value: 0 }, + auth: opts.token ? createAuthExpressionValue(opts) : { null_value: null }, }; return { From e939a2b7e9cd6cfae302eb92ab9ac4e927f88050 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Mon, 19 Dec 2022 02:30:00 -0800 Subject: [PATCH 0732/1699] List Params support in CEL and .env parsing (#5137) --- src/deploy/functions/build.ts | 22 +- src/deploy/functions/cel.ts | 142 +++++++++++-- src/deploy/functions/params.ts | 199 +++++++++++++++++- src/deploy/functions/prepare.ts | 5 +- .../functions/runtimes/discovery/parsing.ts | 11 +- .../functions/runtimes/discovery/v1alpha1.ts | 4 +- src/test/deploy/functions/cel.spec.ts | 153 +++++++++++++- .../runtimes/discovery/v1alpha1.spec.ts | 42 ++++ 8 files changed, 541 insertions(+), 37 deletions(-) diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index 0032602da6b..432a73d3f3d 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -52,8 +52,9 @@ export interface RequiredApi { // expressions. // `Expression == Expression` is an Expression // `Expression ? Expression : Expression` is an Expression -export type Expression = string; // eslint-disable-line +export type Expression = string; // eslint-disable-line export type Field = T | Expression | null; +export type ListField = Expression | (string | Expression)[] | null; // A service account must either: // 1. Be a project-relative email that ends with "@" (e.g. database-users@) @@ -237,7 +238,7 @@ export type Endpoint = Triggered & { // defaults to ["us-central1"], overridable in firebase-tools with // process.env.FIREBASE_FUNCTIONS_DEFAULT_REGION - region?: string[]; + region?: ListField; // The Cloud project associated with this endpoint. project: string; @@ -317,6 +318,7 @@ function envWithTypes( string: true, boolean: true, number: true, + list: true, }; for (const param of definedParams) { if (param.name === envName) { @@ -325,18 +327,28 @@ function envWithTypes( string: true, boolean: false, number: false, + list: false, }; } else if (param.type === "int") { providedType = { string: false, boolean: false, number: true, + list: false, }; } else if (param.type === "boolean") { providedType = { string: false, boolean: true, number: false, + list: false, + }; + } else if (param.type === "list") { + providedType = { + string: false, + boolean: false, + number: false, + list: true, }; } } @@ -420,9 +432,11 @@ export function toBackend( continue; } - let regions = bdEndpoint.region; - if (typeof regions === "undefined") { + let regions: string[] = []; + if (!bdEndpoint.region) { regions = [api.functionsDefaultRegion]; + } else { + regions = params.resolveList(bdEndpoint.region, paramValues); } for (const region of regions) { const trigger = discoverTrigger(bdEndpoint, region, r); diff --git a/src/deploy/functions/cel.ts b/src/deploy/functions/cel.ts index 96286124551..9e694eb3acd 100644 --- a/src/deploy/functions/cel.ts +++ b/src/deploy/functions/cel.ts @@ -10,8 +10,8 @@ type TernaryExpression = CelExpression; type LiteralTernaryExpression = CelExpression; type DualTernaryExpression = CelExpression; -type Literal = string | number | boolean; -type L = "string" | "number" | "boolean"; +type Literal = string | number | boolean | string[]; +type L = "string" | "number" | "boolean" | "string[]"; const paramRegexp = /params\.(\S+)/; const CMP = /((?:!=)|(?:==)|(?:>=)|(?:<=)|>|<)/.source; // !=, ==, >=, <=, >, < @@ -28,6 +28,15 @@ const ternaryRegexp = new RegExp( ); const literalTernaryRegexp = /{{ params\.(\S+) \? (.+) : (.+) }/; +/** + * An array equality test for use on resolved list literal ParamValues only; + * skips a lot of the null/undefined/object-y/nested-list checks that something + * like Underscore's isEqual() would make because args have to be string[]. + */ +function listEquals(a: string[], b: string[]): boolean { + return a.every((item) => b.includes(item)) && b.every((item) => a.includes(item)); +} + /** * Determines if something is a string that looks vaguely like a CEL expression. * No guarantees as to whether it'll actually evaluate. @@ -75,6 +84,10 @@ export function resolveExpression( expr: CelExpression, params: Record ): Literal { + // N.B: List literals [] can contain CEL inside them, so we need to process them + // first and resolve them. This isn't (and can't be) recursive, but the fact that + // we only support string[] types mostly saves us here. + expr = preprocessLists(wantType, expr, params); // N.B: Since some of these regexps are supersets of others--anything that is // params\.(\S+) is also (.+)--the order in which they are tested matters if (isIdentityExpression(expr)) { @@ -94,11 +107,72 @@ export function resolveExpression( } } +/** + * Replaces all lists in a CEL expression string, which can contain string-type CEL + * subexpressions or references to params, with their literal resolved values. + * Not recursive. + */ +function preprocessLists( + wantType: L, + expr: CelExpression, + params: Record +): CelExpression { + let rv = expr; + const listMatcher = /\[[^\[\]]*\]/g; + let match: RegExpMatchArray | null; + while ((match = listMatcher.exec(expr)) != null) { + const list = match[0]; + const resolved = resolveList("string", list, params); + rv = rv.replace(list, JSON.stringify(resolved)); + } + return rv; +} + +/** + * A List in Functions CEL is a []-bracketed string with comma-seperated values that can be: + * - A double quoted string literal + * - A reference to a param value (params.FOO) which must resolve with type string + * - A sub-CEL expression {{ params.BAR == 0 ? "a" : "b" }} which must resolve with type string + */ +function resolveList( + wantType: "string", + list: string, + params: Record +): string[] { + if (!list.startsWith("[") || !list.endsWith("]")) { + throw new ExprParseError("Invalid list: must start with '[' and end with ']'"); + } else if (list === "[]") { + return []; + } + const rv: string[] = []; + const entries = list.slice(1, -1).split(","); + + for (const entry of entries) { + const trimmed = entry.trim(); + if (trimmed.startsWith('"') && trimmed.endsWith('"')) { + rv.push(trimmed.slice(1, -1)); + } else if (trimmed.startsWith("{{") && trimmed.endsWith("}}")) { + rv.push(resolveExpression("string", trimmed, params) as string); + } else { + const paramMatch = paramRegexp.exec(trimmed); + if (!paramMatch) { + throw new ExprParseError(`Malformed list component ${trimmed}`); + } else if (!(paramMatch[1] in params)) { + throw new ExprParseError(`List expansion referenced nonexistent param ${paramMatch[1]}`); + } + rv.push(resolveParamListOrLiteral("string", trimmed, params) as string); + } + } + + return rv; +} + function assertType(wantType: L, paramName: string, paramValue: ParamValue) { if ( (wantType === "string" && !paramValue.legalString) || (wantType === "number" && !paramValue.legalNumber) || - (wantType === "boolean" && !paramValue.legalBoolean) + (wantType === "boolean" && !paramValue.legalBoolean) || + (wantType === "string[]" && !paramValue.legalList) ) { throw new ExprParseError(`Illegal type coercion of param ${paramName} to type ${wantType}`); } @@ -111,6 +185,8 @@ function readParamValue(wantType: L, paramName: string, paramValue: ParamValue): return paramValue.asNumber(); } else if (wantType === "boolean") { return paramValue.asBoolean(); + } else if (wantType === "string[]") { + return paramValue.asList(); } else { assertExhaustive(wantType); } @@ -154,9 +230,9 @@ function resolveComparison( const test = function (a: Literal, b: Literal): boolean { switch (cmp) { case "!=": - return a !== b; + return Array.isArray(a) ? !listEquals(a, b as string[]) : a !== b; case "==": - return a === b; + return Array.isArray(a) ? listEquals(a, b as string[]) : a === b; case ">=": return a >= b; case "<=": @@ -187,6 +263,14 @@ function resolveComparison( } else if (lhsVal.legalBoolean) { rhs = resolveLiteral("boolean", match[3]); return test(lhsVal.asBoolean(), rhs); + } else if (lhsVal.legalList) { + if (!["==", "!="].includes(cmp)) { + throw new ExprParseError( + `Unsupported comparison operation ${cmp} on list operands in expression ${expr}` + ); + } + rhs = resolveLiteral("string[]", match[3]); + return test(lhsVal.asList(), rhs); } else { throw new ExprParseError( `Could not infer type of param ${lhsName} used in comparison operation` @@ -210,9 +294,9 @@ function resolveDualComparison( const test = function (a: Literal, b: Literal): boolean { switch (cmp) { case "!=": - return a !== b; + return Array.isArray(a) ? !listEquals(a, b as string[]) : a !== b; case "==": - return a === b; + return Array.isArray(a) ? listEquals(a, b as string[]) : a === b; case ">=": return a >= b; case "<=": @@ -263,6 +347,18 @@ function resolveDualComparison( ); } return test(lhsVal.asBoolean(), rhsVal.asBoolean()); + } else if (lhsVal.legalList) { + if (!rhsVal.legalList) { + throw new ExprParseError( + `CEL comparison expression ${expr} has type mismatch between the operands` + ); + } + if (!["==", "!="].includes(cmp)) { + throw new ExprParseError( + `Unsupported comparison operation ${cmp} on list operands in expression ${expr}` + ); + } + return test(lhsVal.asList(), rhsVal.asList()); } else { throw new ExprParseError( `could not infer type of param ${lhsName} used in comparison operation` @@ -286,9 +382,9 @@ function resolveTernary( const comparisonExpr = `{{ params.${match[1]} ${match[2]} ${match[3]} }}`; const isTrue = resolveComparison(comparisonExpr, params); if (isTrue) { - return resolveParamOrLiteral(wantType, match[4], params); + return resolveParamListOrLiteral(wantType, match[4], params); } else { - return resolveParamOrLiteral(wantType, match[5], params); + return resolveParamListOrLiteral(wantType, match[5], params); } } @@ -304,13 +400,12 @@ function resolveDualTernary( if (!match) { throw new ExprParseError("Malformed CEL ternary expression '" + expr + "'"); } - const comparisonExpr = `{{ params.${match[1]} ${match[2]} params.${match[3]} }}`; const isTrue = resolveDualComparison(comparisonExpr, params); if (isTrue) { - return resolveParamOrLiteral(wantType, match[4], params); + return resolveParamListOrLiteral(wantType, match[4], params); } else { - return resolveParamOrLiteral(wantType, match[5], params); + return resolveParamListOrLiteral(wantType, match[5], params); } } @@ -342,13 +437,13 @@ function resolveLiteralTernary( } if (paramValue.asBoolean()) { - return resolveParamOrLiteral(wantType, match[2], params); + return resolveParamListOrLiteral(wantType, match[2], params); } else { - return resolveParamOrLiteral(wantType, match[3], params); + return resolveParamListOrLiteral(wantType, match[3], params); } } -function resolveParamOrLiteral( +function resolveParamListOrLiteral( wantType: L, field: string, params: Record @@ -371,7 +466,22 @@ function resolveLiteral(wantType: L, value: string): Literal { ); } - if (wantType === "number") { + if (wantType === "string[]") { + // N.B: value being a literal list that can just be JSON.parsed should be guaranteed + // by the preprocessLists() invocation at the beginning of CEL resolution + const parsed = JSON.parse(value); + if (!Array.isArray(parsed)) { + throw new ExprParseError(`CEL tried to read non-list ${JSON.stringify(parsed)} as a list`); + } + for (const shouldBeString of parsed) { + if (typeof shouldBeString !== "string") { + throw new ExprParseError( + `Evaluated CEL list ${JSON.stringify(parsed)} contained non-string values` + ); + } + } + return parsed as string[]; + } else if (wantType === "number") { if (isNaN(+value)) { throw new ExprParseError("CEL literal " + value + " does not seem to be a number"); } diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index ef67bcb72a5..18c000e5683 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -66,6 +66,26 @@ export function resolveString( return output; } +/** + * Resolves a FieldList in a Build to an an actual string[] value. + * FieldLists can be a list of string | Expression, or a single + * Expression. + */ +export function resolveList( + from: build.ListField, + paramValues: Record +): string[] { + if (!from) { + return []; + } else if (Array.isArray(from)) { + return from.map((entry) => resolveString(entry, paramValues)); + } else if (typeof from === "string") { + return resolveExpression("string[]", from, paramValues) as string[]; + } else { + assertExhaustive(from); + } +} + /** * Resolves a boolean field in a Build to an an actual boolean value. * Fields can be literal or an expression written in a subset of the CEL specification. @@ -81,9 +101,9 @@ export function resolveBoolean( return resolveExpression("boolean", from, paramValues) as boolean; } -type ParamInput = TextInput | SelectInput | ResourceInput; +type ParamInput = TextInput | SelectInput | MultiSelectInput | ResourceInput; -type ParamBase = { +type ParamBase = { // name of the param. Will be exposed as an environment variable with this name name: string; @@ -123,6 +143,12 @@ export function isSelectInput(input: ParamInput): input is SelectInput export function isResourceInput(input: ParamInput): input is ResourceInput { return {}.hasOwnProperty.call(input, "resource"); } +/** + * Determines whether an Input field value can be coerced to MultiSelectInput. + */ +export function isMultiSelectInput(input: ParamInput): input is MultiSelectInput { + return {}.hasOwnProperty.call(input, "multiSelect"); +} export interface StringParam extends ParamBase { type: "string"; @@ -136,6 +162,12 @@ export interface BooleanParam extends ParamBase { type: "boolean"; } +export interface ListParam extends ParamBase { + type: "list"; + + delimiter?: string; +} + export interface TextInput { // eslint-disable-line text: { example?: string; @@ -172,6 +204,12 @@ interface ResourceInput { }; } +interface MultiSelectInput { + multiSelect: { + options: Array>; + }; +} + interface SecretParam { type: "secret"; @@ -187,8 +225,8 @@ interface SecretParam { description?: string; } -export type Param = StringParam | IntParam | BooleanParam | SecretParam; -type RawParamValue = string | number | boolean; +export type Param = StringParam | IntParam | BooleanParam | ListParam | SecretParam; +type RawParamValue = string | number | boolean | string[]; /** * A type which contains the resolved value of a param, and metadata ensuring @@ -206,21 +244,43 @@ export class ParamValue { legalBoolean: boolean; // Whether this param value can be sensibly interpreted as a number legalNumber: boolean; + // Whether this param value can be sensibly interpreted as a list + legalList: boolean; + // What delimiter to use between fields when reading/writing to .env format + delimiter: string; constructor( private readonly rawValue: string, readonly internal: boolean, - types: { string?: boolean; boolean?: boolean; number?: boolean } + types: { string?: boolean; boolean?: boolean; number?: boolean; list?: boolean } ) { this.legalString = types.string || false; this.legalBoolean = types.boolean || false; this.legalNumber = types.number || false; + this.legalList = types.list || false; + this.delimiter = ","; + } + + static fromList(ls: string[], delimiter = ","): ParamValue { + const pv = new ParamValue(ls.join(delimiter), false, { list: true }); + pv.setDelimiter(delimiter); + return pv; } + setDelimiter(delimiter: string) { + this.delimiter = delimiter; + } + + // Returns this param's representation as it should be in .env files toString(): string { return this.rawValue; } + // Returns this param's representatiom as it should be in process.env during runtime + toSDK(): string { + return this.legalList ? JSON.stringify(this.asList()) : this.toString(); + } + asString(): string { return this.rawValue; } @@ -229,6 +289,10 @@ export class ParamValue { return ["true", "y", "yes", "1"].includes(this.rawValue); } + asList(): string[] { + return this.rawValue.split(this.delimiter); + } + asNumber(): number { return +this.rawValue; } @@ -261,6 +325,8 @@ function resolveDefaultCEL( return resolveString(expr, currentEnv); case "int": return resolveInt(expr, currentEnv); + case "list": + return resolveList(expr, currentEnv); default: throw new FirebaseError( "Build specified parameter with default " + expr + " of unsupported type" @@ -278,6 +344,8 @@ function canSatisfyParam(param: Param, value: RawParamValue): boolean { return typeof value === "number" && Number.isInteger(value); } else if (param.type === "boolean") { return typeof value === "boolean"; + } else if (param.type === "list") { + return Array.isArray(value); } else if (param.type === "secret") { return false; } @@ -436,6 +504,9 @@ async function promptParam( } else if (param.type === "boolean") { const provided = await promptBooleanParam(param, resolvedDefault as boolean | undefined); return new ParamValue(provided.toString(), false, { boolean: true }); + } else if (param.type === "list") { + const provided = await promptList(param, projectId, resolvedDefault as string[] | undefined); + return ParamValue.fromList(provided, param.delimiter); } else if (param.type === "secret") { throw new FirebaseError( `Somehow ended up trying to interactively prompt for secret parameter ${param.name}, which should never happen.` @@ -444,6 +515,52 @@ async function promptParam( assertExhaustive(param); } +async function promptList( + param: ListParam, + projectId: string, + resolvedDefault?: string[] +): Promise { + if (!param.input) { + const defaultToText: TextInput = { text: {} }; + param.input = defaultToText; + } + let prompt: string; + + if (isSelectInput(param.input)) { + throw new FirebaseError("List params cannot have non-list selector inputs"); + } else if (isMultiSelectInput(param.input)) { + prompt = `Select a value for ${param.label || param.name}:`; + if (param.description) { + prompt += ` \n(${param.description})`; + } + prompt += "\nSelect an option with the arrow keys, and use Enter to confirm your choice. "; + return promptSelectMultiple( + prompt, + param.input, + resolvedDefault, + (res: string[]) => res + ); + } else if (isTextInput(param.input)) { + prompt = `Enter a list of strings (delimiter: ${param.delimiter ? param.delimiter : ","}) for ${ + param.label || param.name + }:`; + if (param.description) { + prompt += ` \n(${param.description})`; + } + return promptText(prompt, param.input, resolvedDefault, (res: string): string[] => { + return res.split(param.delimiter || ","); + }); + } else if (isResourceInput(param.input)) { + prompt = `Select values for ${param.label || param.name}:`; + if (param.description) { + prompt += ` \n(${param.description})`; + } + return promptResourceStrings(prompt, param.input, projectId); + } else { + assertExhaustive(param.input); + } +} + async function promptBooleanParam( param: BooleanParam, resolvedDefault?: boolean @@ -462,6 +579,8 @@ async function promptBooleanParam( } prompt += "\nSelect an option with the arrow keys, and use Enter to confirm your choice. "; return promptSelect(prompt, param.input, resolvedDefault, isTruthyInput); + } else if (isMultiSelectInput(param.input)) { + throw new FirebaseError("Non-list params cannot have multi selector inputs"); } else if (isTextInput(param.input)) { prompt = `Enter a boolean value for ${param.label || param.name}:`; if (param.description) { @@ -492,6 +611,8 @@ async function promptStringParam( prompt += ` \n(${param.description})`; } return promptResourceString(prompt, param.input, projectId, resolvedDefault); + } else if (isMultiSelectInput(param.input)) { + throw new FirebaseError("Non-list params cannot have multi selector inputs"); } else if (isSelectInput(param.input)) { prompt = `Select a value for ${param.label || param.name}:`; if (param.description) { @@ -532,8 +653,9 @@ async function promptIntParam(param: IntParam, resolvedDefault?: number): Promis } return +res; }); - } - if (isTextInput(param.input)) { + } else if (isMultiSelectInput(param.input)) { + throw new FirebaseError("Non-list params cannot have multi selector inputs"); + } else if (isTextInput(param.input)) { prompt = `Enter an integer value for ${param.label || param.name}:`; if (param.description) { prompt += ` \n(${param.description})`; @@ -583,7 +705,39 @@ async function promptResourceString( } } +async function promptResourceStrings( + prompt: string, + input: ResourceInput, + projectId: string +): Promise { + const notFound = new FirebaseError(`No instances of ${input.resource.type} found.`); + switch (input.resource.type) { + case "storage.googleapis.com/Bucket": + const buckets = await listBuckets(projectId); + if (buckets.length === 0) { + throw notFound; + } + const forgedInput: MultiSelectInput = { + multiSelect: { + options: buckets.map((bucketName: string): SelectOptions => { + return { label: bucketName, value: bucketName }; + }), + }, + }; + return promptSelectMultiple(prompt, forgedInput, undefined, (res: string[]) => res); + default: + logger.warn( + `Warning: unknown resource type ${input.resource.type}; defaulting to raw text input...` + ); + return promptText(prompt, { text: {} }, undefined, (res: string) => res.split(",")); + } +} + type retryInput = { message: string }; +function shouldRetry(obj: any): obj is retryInput { + return typeof obj === "object" && (obj as retryInput).message !== undefined; +} + async function promptText( prompt: string, input: TextInput, @@ -609,7 +763,7 @@ async function promptText( // is wrong--it will return the type of the default if selected. Remove this // hack once we fix the prompt.ts metaprogramming. const converted = converter(res.toString()); - if (typeof converted === "object") { + if (shouldRetry(converted)) { logger.error(converted.message); return promptText(prompt, input, resolvedDefault, converter); } @@ -636,9 +790,36 @@ async function promptSelect( }), }); const converted = converter(response); - if (typeof converted === "object") { + if (shouldRetry(converted)) { logger.error(converted.message); return promptSelect(prompt, input, resolvedDefault, converter); } return converted; } + +async function promptSelectMultiple( + prompt: string, + input: MultiSelectInput, + resolvedDefault: T[] | undefined, + converter: (res: string[]) => T[] | retryInput +): Promise { + const response = await promptOnce({ + name: "input", + type: "checkbox", + default: resolvedDefault, + message: prompt, + choices: input.multiSelect.options.map((option: SelectOptions): ListItem => { + return { + checked: false, + name: option.label, + value: option.value.toString(), + }; + }), + }); + const converted = converter(response); + if (shouldRetry(converted)) { + logger.error(converted.message); + return promptSelectMultiple(prompt, input, resolvedDefault, converter); + } + return converted; +} diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index ca9d9079bdd..70f6713d6a8 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -122,11 +122,12 @@ export async function prepare( let hasEnvsFromParams = false; wantBackend.environmentVariables = envs; for (const envName of Object.keys(resolvedEnvs)) { - const envValue = resolvedEnvs[envName]?.toString(); + const isList = resolvedEnvs[envName]?.legalList; + const envValue = resolvedEnvs[envName]?.toSDK(); if ( envValue && !resolvedEnvs[envName].internal && - !Object.prototype.hasOwnProperty.call(wantBackend.environmentVariables, envName) + (!Object.prototype.hasOwnProperty.call(wantBackend.environmentVariables, envName) || isList) ) { wantBackend.environmentVariables[envName] = envValue; hasEnvsFromParams = true; diff --git a/src/deploy/functions/runtimes/discovery/parsing.ts b/src/deploy/functions/runtimes/discovery/parsing.ts index 396af1e15af..0a33e18174f 100644 --- a/src/deploy/functions/runtimes/discovery/parsing.ts +++ b/src/deploy/functions/runtimes/discovery/parsing.ts @@ -34,6 +34,7 @@ export type FieldType = | `Field${NullSuffix}` | `Field${NullSuffix}` | `Field${NullSuffix}` + | `List${NullSuffix}` | ((t: T) => boolean); export type Schema = { @@ -49,7 +50,7 @@ export function requireKeys(prefix: string, yaml: T, ...keys: } for (const key of keys) { if (!yaml[key]) { - throw new FirebaseError(`Expected key ${prefix + key}`); + throw new FirebaseError(`Expected key ${prefix + key.toString()}`); } } } @@ -106,6 +107,14 @@ export function assertKeyTypes( } continue; } + if (schemaType === "List") { + if (typeof value !== "string" && !Array.isArray(value)) { + throw new FirebaseError( + `Expected ${fullKey} to be a field list (array or list expression); was ${typeof value}` + ); + } + continue; + } if (value === null) { if (schemaType.endsWith("?")) { diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 5b2369c76a4..8cc49964fd0 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -64,7 +64,7 @@ export type WireEndpoint = build.Triggered & // We now use "serviceAccount" but maintain backwards compatability in the // wire format for the time being. serviceAccountEmail?: string | null; - region?: string[]; + region?: build.ListField; entryPoint: string; platform?: build.FunctionsPlatform; secretEnvironmentVariables?: Array | null; @@ -122,7 +122,7 @@ function parseRequiredAPIs(manifest: WireManifest): build.RequiredApi[] { function assertBuildEndpoint(ep: WireEndpoint, id: string): void { const prefix = `endpoints[${id}]`; assertKeyTypes(prefix, ep, { - region: "array", + region: "List", platform: (platform) => build.AllFunctionsPlatforms.includes(platform), entryPoint: "string", omit: "Field?", diff --git a/src/test/deploy/functions/cel.spec.ts b/src/test/deploy/functions/cel.spec.ts index 98d6799aa06..1083953131c 100644 --- a/src/test/deploy/functions/cel.spec.ts +++ b/src/test/deploy/functions/cel.spec.ts @@ -3,16 +3,163 @@ import { resolveExpression, ExprParseError } from "../../../deploy/functions/cel import { ParamValue } from "../../../deploy/functions/params"; function stringV(value: string): ParamValue { - return new ParamValue(value, false, { string: true, number: false, boolean: false }); + return new ParamValue(value, false, { string: true, number: false, boolean: false, list: false }); } function numberV(value: number): ParamValue { - return new ParamValue(value.toString(), false, { string: false, number: true, boolean: false }); + return new ParamValue(value.toString(), false, { + string: false, + number: true, + boolean: false, + list: false, + }); } function boolV(value: boolean): ParamValue { - return new ParamValue(value.toString(), false, { string: false, number: false, boolean: true }); + return new ParamValue(value.toString(), false, { + string: false, + number: false, + boolean: true, + list: false, + }); +} +function listV(value: string[]): ParamValue { + return ParamValue.fromList(value); } describe("CEL evaluation", () => { + describe("String list resolution", () => { + it("can pull lists directly out of paramvalues", () => { + expect( + resolveExpression("string[]", "{{ params.FOO }}", { + FOO: listV(["1"]), + }) + ).to.deep.equal(["1"]); + }); + + it("can handle literals in a list", () => { + expect( + resolveExpression("string[]", '{{ params.FOO == params.FOO ? ["asdf"] : [] }}', { + FOO: numberV(1), + }) + ).to.deep.equal(["asdf"]); + }); + + it("can handle CEL expressions in a list", () => { + expect( + resolveExpression("string[]", "{{ params.FOO == params.FOO ? [{{ params.BAR }}] : [] }}", { + FOO: numberV(1), + BAR: stringV("asdf"), + }) + ).to.deep.equal(["asdf"]); + }); + + it("can handle direct references to string params in a list", () => { + expect( + resolveExpression("string[]", "{{ params.FOO == params.FOO ? [params.BAR] : [] }}", { + FOO: numberV(1), + BAR: stringV("asdf"), + }) + ).to.deep.equal(["asdf"]); + }); + + it("can handle a list with multiple elements", () => { + expect( + resolveExpression( + "string[]", + '{{ params.FOO == params.FOO ? [ "foo", params.BAR, {{ params.BAR }} ] : [] }}', + { + FOO: numberV(1), + BAR: stringV("asdf"), + } + ) + ).to.deep.equal(["foo", "asdf", "asdf"]); + }); + + it("isn't picky about whitespace around the commas", () => { + expect( + resolveExpression( + "string[]", + '{{ params.FOO == params.FOO ? ["foo ",params.BAR ,{{ params.BAR }}] : [] }}', + { + FOO: numberV(1), + BAR: stringV("asdf"), + } + ) + ).to.deep.equal(["foo ", "asdf", "asdf"]); + }); + + it("can do == comparisons between lists", () => { + expect( + resolveExpression("boolean", "{{ params.FOO == params.FOO }}", { + FOO: listV(["a", "2", "false"]), + }) + ).to.be.true; + expect( + resolveExpression("boolean", '{{ params.FOO == ["a", "2", "false"] }}', { + FOO: listV(["a", "2", "false"]), + }) + ).to.be.true; + expect( + resolveExpression("boolean", "{{ params.FOO != params.FOO }}", { + FOO: listV(["a", "2", "false"]), + }) + ).to.be.false; + expect( + resolveExpression("boolean", '{{ params.FOO != ["a", "2", "false"] }}', { + FOO: listV(["a", "2", "false"]), + }) + ).to.be.false; + expect( + resolveExpression("boolean", "{{ params.FOO == params.BAR }}", { + FOO: listV(["a", "2", "false"]), + BAR: listV(["b", "-2", "true"]), + }) + ).to.be.false; + expect( + resolveExpression("boolean", '{{ params.FOO == ["a", "2", "false"] }}', { + FOO: listV(["b", "-2", "true"]), + }) + ).to.be.false; + expect( + resolveExpression("boolean", "{{ params.FOO != params.BAR }}", { + FOO: listV(["a", "2", "false"]), + BAR: listV(["b", "-2", "true"]), + }) + ).to.be.true; + expect( + resolveExpression("boolean", '{{ params.FOO != ["a", "2", "false"] }}', { + FOO: listV(["b", "-2", "true"]), + }) + ).to.be.true; + }); + + it("throws if asked to do type comparisons between lists", () => { + expect(() => + resolveExpression("boolean", "{{ params.FOO > params.BAR }}", { + FOO: listV(["a", "2", "false"]), + BAR: listV(["b", "-2", "true"]), + }) + ).to.throw(ExprParseError); + expect(() => + resolveExpression("boolean", "{{ params.FOO >= params.BAR }}", { + FOO: listV(["a", "2", "false"]), + BAR: listV(["b", "-2", "true"]), + }) + ).to.throw(ExprParseError); + expect(() => + resolveExpression("boolean", "{{ params.FOO < params.BAR }}", { + FOO: listV(["a", "2", "false"]), + BAR: listV(["b", "-2", "true"]), + }) + ).to.throw(ExprParseError); + expect(() => + resolveExpression("boolean", "{{ params.FOO <= params.BAR }}", { + FOO: listV(["a", "2", "false"]), + BAR: listV(["b", "-2", "true"]), + }) + ).to.throw(ExprParseError); + }); + }); + describe("Identity expressions", () => { it("raises when the referenced parameter does not exist", () => { expect(() => { diff --git a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index f38610358ae..3793e69a667 100644 --- a/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -609,6 +609,48 @@ describe("buildFromV1Alpha", () => { expect(parsed).to.deep.equal(expected); }); + it("allows both CEL and lists containing CEL in FieldList typed keys", () => { + const yamlCEL: v1alpha1.WireManifest = { + specVersion: "v1alpha1", + endpoints: { + id: { + ...MIN_WIRE_ENDPOINT, + httpsTrigger: {}, + region: "{{ params.REGION }}", + }, + }, + }; + const parsedCEL = v1alpha1.buildFromV1Alpha1(yamlCEL, PROJECT, REGION, RUNTIME); + const expectedCEL: build.Build = build.of({ + id: { + ...DEFAULTED_ENDPOINT, + region: "{{ params.REGION }}", + httpsTrigger: {}, + }, + }); + expect(parsedCEL).to.deep.equal(expectedCEL); + + const yamlList: v1alpha1.WireManifest = { + specVersion: "v1alpha1", + endpoints: { + id: { + ...MIN_WIRE_ENDPOINT, + httpsTrigger: {}, + region: ["{{ params.FOO }}", "BAR", "params.BAZ"], + }, + }, + }; + const parsedList = v1alpha1.buildFromV1Alpha1(yamlList, PROJECT, REGION, RUNTIME); + const expectedList: build.Build = build.of({ + id: { + ...DEFAULTED_ENDPOINT, + region: ["{{ params.FOO }}", "BAR", "params.BAZ"], + httpsTrigger: {}, + }, + }); + expect(parsedList).to.deep.equal(expectedList); + }); + it("copies schedules", () => { const scheduleTrigger: build.ScheduleTrigger = { schedule: "every 5 minutes", From ffac46898fbdc68da3399f009abecc89547c46a7 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Mon, 19 Dec 2022 14:23:04 -0800 Subject: [PATCH 0733/1699] add a note about list params to the changelog file (#5349) * add a note about list params to the changelog file * Update CHANGELOG.md Co-authored-by: joehan Co-authored-by: joehan --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d8cb15c9e1..bf37cac0ff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Support for string list typed parameters in functions deployment (#5137) - Respect .npmrc in backends spun up for web frameworks (#5235) - Remove esbuild dependency, instead bundle Next.js configuration on deploy with NPX (#5336) - Add sharp NPM module to Cloud Functions when using Next.js Image Optimization (#5238) From 5a463aeb789fb876a4a9b0ea412dcbc6234cf284 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 19 Dec 2022 23:05:07 +0000 Subject: [PATCH 0734/1699] 11.19.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 346a4c13aa6..0891f19b992 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.18.0", + "version": "11.19.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.18.0", + "version": "11.19.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index c20ef17ba25..c477a8e5e05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.18.0", + "version": "11.19.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 86a5ba1472e4cb3bfda00450326efdce705d102f Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 19 Dec 2022 23:05:20 +0000 Subject: [PATCH 0735/1699] [firebase-release] Removed change log and reset repo after 11.19.0 release --- CHANGELOG.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf37cac0ff9..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +0,0 @@ -- Support for string list typed parameters in functions deployment (#5137) -- Respect .npmrc in backends spun up for web frameworks (#5235) -- Remove esbuild dependency, instead bundle Next.js configuration on deploy with NPX (#5336) -- Add sharp NPM module to Cloud Functions when using Next.js Image Optimization (#5238) -- Adds user-defined env vars into the functions emulator (#5330). -- Support Next.js Middleware (#5320) -- Log the reason for a Cloud Function if needed in Next.js (#5320) -- Fixed service enablement when installing extensions with v2 functions (#5338) -- Fix bug where functions:shell command didn't connect to emulators running on other processes. (#5269) -- Fixed bug with Cross-Service Rules integration for Firestore documents containing nulls (#5342) From ae4f737c4535a7a7ff161429eccd9e918cf44478 Mon Sep 17 00:00:00 2001 From: Stefan Pfaffel Date: Wed, 28 Dec 2022 23:45:36 +0100 Subject: [PATCH 0736/1699] [Feat] Support extensions with scheduled triggers (#5373) * feat: support extensions with scheduled triggers * chore: format trigger helper --- src/extensions/emulator/triggerHelper.ts | 20 +++++++++---- src/extensions/types.ts | 5 ++-- .../extensions/emulator/triggerHelper.spec.ts | 29 +++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/extensions/emulator/triggerHelper.ts b/src/extensions/emulator/triggerHelper.ts index 192d117208f..b27e864706c 100644 --- a/src/extensions/emulator/triggerHelper.ts +++ b/src/extensions/emulator/triggerHelper.ts @@ -1,17 +1,18 @@ +import * as backend from "../../deploy/functions/backend"; +import { EmulatorLogger } from "../../emulator/emulatorLogger"; import { - ParsedTriggerDefinition, + EventSchedule, getServiceFromEventType, + ParsedTriggerDefinition, } from "../../emulator/functionsEmulatorShared"; -import { EmulatorLogger } from "../../emulator/emulatorLogger"; import { Emulators } from "../../emulator/types"; +import { FirebaseError } from "../../error"; import { - Resource, FUNCTIONS_RESOURCE_TYPE, FUNCTIONS_V2_RESOURCE_TYPE, + Resource, } from "../../extensions/types"; -import * as backend from "../../deploy/functions/backend"; import * as proto from "../../gcp/proto"; -import { FirebaseError } from "../../error"; /** * Convert a Resource into a ParsedTriggerDefinition @@ -39,6 +40,15 @@ export function functionResourceToEmulatedTriggerDefintion( resource: properties.eventTrigger.resource, service: getServiceFromEventType(properties.eventTrigger.eventType), }; + } else if (properties.scheduleTrigger) { + const schedule: EventSchedule = { + schedule: properties.scheduleTrigger.schedule, + }; + etd.schedule = schedule; + etd.eventTrigger = { + eventType: "google.pubsub.topic.publish", + resource: "", + }; } else { EmulatorLogger.forEmulator(Emulators.FUNCTIONS).log( "WARN", diff --git a/src/extensions/types.ts b/src/extensions/types.ts index 10396365836..2138e1643eb 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -1,7 +1,7 @@ +import { MemoryOptions } from "../deploy/functions/backend"; +import { Runtime } from "../deploy/functions/runtimes"; import * as proto from "../gcp/proto"; import { SpecParamType } from "./extensionsHelper"; -import { Runtime } from "../deploy/functions/runtimes"; -import { MemoryOptions } from "../deploy/functions/backend"; export enum RegistryLaunchStage { EXPERIMENTAL = "EXPERIMENTAL", @@ -139,6 +139,7 @@ export interface FunctionResourceProperties { availableMemoryMb?: MemoryOptions; runtime?: Runtime; httpsTrigger?: Record; + scheduleTrigger?: Record; taskQueueTrigger?: { rateLimits?: { maxConcurrentDispatchs?: number; diff --git a/src/test/extensions/emulator/triggerHelper.spec.ts b/src/test/extensions/emulator/triggerHelper.spec.ts index b2fb3d90ffa..1f5812455b9 100644 --- a/src/test/extensions/emulator/triggerHelper.spec.ts +++ b/src/test/extensions/emulator/triggerHelper.spec.ts @@ -135,6 +135,35 @@ describe("triggerHelper", () => { expect(result).to.eql(expected); }); + it("should handle scheduled triggers", () => { + const testResource: Resource = { + name: "test-resource", + entryPoint: "functionName", + type: "firebaseextensions.v1beta.function", + properties: { + scheduleTrigger: { + schedule: "every 5 minutes", + }, + }, + }; + const expected = { + platform: "gcfv1", + entryPoint: "test-resource", + name: "test-resource", + eventTrigger: { + eventType: "google.pubsub.topic.publish", + resource: "", + }, + schedule: { + schedule: "every 5 minutes", + }, + }; + + const result = triggerHelper.functionResourceToEmulatedTriggerDefintion(testResource); + + expect(result).to.eql(expected); + }); + it("should handle v2 custom event triggers", () => { const testResource: Resource = { name: "test-resource", From 35560bec209497b1445376643bf553a1c5e025f7 Mon Sep 17 00:00:00 2001 From: Stefan Pfaffel Date: Wed, 28 Dec 2022 23:47:04 +0100 Subject: [PATCH 0737/1699] [Fix] Extension dependencies do not get installed on Windows (#5371) * tests: convert path to platform paths before assertion * chore: add some content to package.json file * chore: use gitkeep to keep directory * fix(extensionsEmulator): no deps installed no windows machines * chore: ignore static files in extension test folder * chore: format extensions emulator spec * chore: reuse existing path import --- .gitignore | 4 ++ src/emulator/extensionsEmulator.ts | 33 +++++++++-------- .../functions/node_modules/.gitkeep | 0 .../functions/node_modules/test_data.text | 1 - .../functions/package.json | 9 ++++- src/test/emulators/extensionsEmulator.spec.ts | 37 +++++++++++++++++-- 6 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/.gitkeep delete mode 100644 src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/test_data.text diff --git a/.gitignore b/.gitignore index 9e75eba6a61..be8e06a9d6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/* +src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json +src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package-lock.json + /.vscode node_modules /coverage diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index c1fd847d7dc..d62a1c53ec0 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -1,24 +1,24 @@ +import * as clc from "colorette"; +import * as spawn from "cross-spawn"; import * as fs from "fs-extra"; import * as os from "os"; import * as path from "path"; -import * as clc from "colorette"; import Table = require("cli-table"); -import * as spawn from "cross-spawn"; import * as planner from "../deploy/extensions/planner"; -import { Options } from "../options"; +import { enableApiURI } from "../ensureApiEnabled"; import { FirebaseError } from "../error"; +import { getExtensionFunctionInfo } from "../extensions/emulator/optionsHelper"; import { toExtensionVersionRef } from "../extensions/refs"; +import { Options } from "../options"; +import { shortenUrl } from "../shortenUrl"; +import { Constants } from "./constants"; import { downloadExtensionVersion } from "./download"; -import { EmulatableBackend } from "./functionsEmulator"; -import { getExtensionFunctionInfo } from "../extensions/emulator/optionsHelper"; import { EmulatorLogger } from "./emulatorLogger"; -import { EmulatorInfo, EmulatorInstance, Emulators } from "./types"; import { checkForUnemulatedTriggerTypes, getUnemulatedAPIs } from "./extensions/validation"; -import { enableApiURI } from "../ensureApiEnabled"; -import { shortenUrl } from "../shortenUrl"; -import { Constants } from "./constants"; +import { EmulatableBackend } from "./functionsEmulator"; import { EmulatorRegistry } from "./registry"; +import { EmulatorInfo, EmulatorInstance, Emulators } from "./types"; export interface ExtensionEmulatorArgs { projectId: string; @@ -167,11 +167,13 @@ export class ExtensionsEmulator implements EmulatorInstance { return true; } - private installAndBuildSourceCode(sourceCodePath: string): void { + installAndBuildSourceCode(sourceCodePath: string): void { // TODO: Add logging during this so it is clear what is happening. this.logger.logLabeled("DEBUG", "Extensions", `Running "npm install" for ${sourceCodePath}`); - const npmInstall = spawn.sync("npm", ["--prefix", `/${sourceCodePath}/functions/`, "install"], { + const functionsDirectory = path.resolve(sourceCodePath, "functions"); + const npmInstall = spawn.sync("npm", ["install"], { encoding: "utf8", + cwd: functionsDirectory, }); if (npmInstall.error) { throw npmInstall.error; @@ -183,11 +185,10 @@ export class ExtensionsEmulator implements EmulatorInstance { "Extensions", `Running "npm run gcp-build" for ${sourceCodePath}` ); - const npmRunGCPBuild = spawn.sync( - "npm", - ["--prefix", `/${sourceCodePath}/functions/`, "run", "gcp-build"], - { encoding: "utf8" } - ); + const npmRunGCPBuild = spawn.sync("npm", ["run", "gcp-build"], { + encoding: "utf8", + cwd: functionsDirectory, + }); if (npmRunGCPBuild.error) { // TODO: Make sure this does not error out if "gcp-build" is not defined, but does error if it fails otherwise. throw npmRunGCPBuild.error; diff --git a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/.gitkeep b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/test_data.text b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/test_data.text deleted file mode 100644 index 739ae61c52a..00000000000 --- a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/node_modules/test_data.text +++ /dev/null @@ -1 +0,0 @@ -Empty file to hold the directory. diff --git a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json index d17601ddffd..a821a111f1a 100644 --- a/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json +++ b/src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package.json @@ -1,3 +1,8 @@ -{ - "what's this": "Empty file for testing" +{ + "name": "storage-resize-images", + "vresion": "0.1.18", + "description": "Package file for testing only", + "dependencies": { + "firebase-tools": "file:../../../../../../.." + } } diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts index 59231fd3636..baa0c6c5a05 100644 --- a/src/test/emulators/extensionsEmulator.spec.ts +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -1,5 +1,8 @@ import { expect } from "chai"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import * as planner from "../../deploy/extensions/planner"; import { ExtensionsEmulator } from "../../emulator/extensionsEmulator"; import { EmulatableBackend } from "../../emulator/functionsEmulator"; import { @@ -8,7 +11,6 @@ import { RegistryLaunchStage, Visibility, } from "../../extensions/types"; -import * as planner from "../../deploy/extensions/planner"; const TEST_EXTENSION: Extension = { name: "publishers/firebase/extensions/storage-resize-images", @@ -101,8 +103,10 @@ describe("Extensions Emulator", () => { }, secretEnv: [], extensionInstanceId: "ext-test", - functionsDir: - "src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions", + // use join to convert path to platform dependent path + // so test also runs on win machines + // eslint-disable-next-line prettier/prettier + functionsDir: join("src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions"), nodeMajorVersion: 10, predefinedTriggers: [ { @@ -134,9 +138,34 @@ describe("Extensions Emulator", () => { }); const result = await e.toEmulatableBackend(testCase.input); - expect(result).to.deep.equal(testCase.expected); }); } }); + + describe("installAndBuildSourceCode", () => { + const extensionPath = "src/test/emulators/extensions/firebase/storage-resize-images@0.1.18"; + it("installs dependecies", () => { + // creating a subclass of ext emulator + // to be able to test private method + class DependencyInstallingExtensionsEmulator extends ExtensionsEmulator { + constructor(extensionPath: string) { + super({ + projectId: "test-project", + projectNumber: "1234567", + projectDir: ".", + extensions: {}, + aliases: [], + }); + + this.installAndBuildSourceCode(extensionPath); + } + } + new DependencyInstallingExtensionsEmulator(extensionPath); + const nodeModulesFolderExists = existsSync( + `${extensionPath}/functions/node_modules/firebase-tools` + ); + expect(nodeModulesFolderExists).to.be.true; + }).timeout(60_000); + }); }); From 7b98cc8f5b184ae1b4509ef7b8adbbf813593642 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 29 Dec 2022 09:02:47 -0800 Subject: [PATCH 0738/1699] Add CHANGELOG entries for #5372 and #5374 (#5377) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..c30f6adfcef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Fixes an issue where dependencies for emulated Extensions would not be installed on Windows - thanks @stfsy! (#5372) +- Adds emulator support for Extensions with schedule triggers - thanks @stsfy! (#5374) From 47aabb7501c2fa488f736f076261702661ee1e32 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 29 Dec 2022 14:02:07 -0800 Subject: [PATCH 0739/1699] Add changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0c8713f3bc..63cacae792c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Add sharp NPM module to Cloud Functions when using Next.js Image Optimization +- Fix bug where functions:delete command did not recognize '-' as delimiter. (#5290) From 2957b119975b11e3db5e009c396e45998b975e19 Mon Sep 17 00:00:00 2001 From: Lisa Jian Date: Tue, 3 Jan 2023 12:05:35 -0800 Subject: [PATCH 0740/1699] Update custom_claims type to match production (#5353) --- src/emulator/auth/operations.ts | 4 ++-- src/test/emulators/auth/emailLink.spec.ts | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index f40c1beea10..386a4269230 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -3174,7 +3174,7 @@ function generateBlockingFunctionJwt( photo_url: user.photoUrl, disabled: user.disabled, phone_number: user.phoneNumber, - custom_claims: user.customAttributes, + custom_claims: JSON.parse(user.customAttributes || "{}") as Record, }, sub: user.localId, sign_in_method: options.signInMethod, @@ -3473,7 +3473,7 @@ export interface BlockingFunctionsJwtPayload { last_sign_in_time?: string; creation_time?: string; }; - custom_claims?: string; + custom_claims?: Record; tenant_id?: string; // should match top level tenant_id }; tenant_id?: string; // `tenantId` if present diff --git a/src/test/emulators/auth/emailLink.spec.ts b/src/test/emulators/auth/emailLink.spec.ts index 67f4dd37b5b..a5dc3e3f239 100644 --- a/src/test/emulators/auth/emailLink.spec.ts +++ b/src/test/emulators/auth/emailLink.spec.ts @@ -538,9 +538,7 @@ describeAuthEmulator("email link sign-in", ({ authApi }) => { expect(jwt.user_record).to.have.property("email_verified").to.be.false; expect(jwt.user_record).to.have.property("display_name").eql(DISPLAY_NAME); expect(jwt.user_record).to.have.property("photo_url").eql(PHOTO_URL); - expect(jwt.user_record) - .to.have.property("custom_claims") - .eql(JSON.stringify({ customAttribute: "custom" })); + expect(jwt.user_record).to.have.property("custom_claims").eql({ customAttribute: "custom" }); expect(jwt.user_record).to.have.property("metadata"); expect(jwt.user_record.metadata).to.have.property("creation_time").that.is.a("string"); }); From f7dbd3a1faf181dc977e7db7cdebcafbe8dbcb68 Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Tue, 3 Jan 2023 18:02:06 -0800 Subject: [PATCH 0741/1699] Adjust from localhost to 127.0.0.1 to avoid some IP loookup issues. (#5358) * Adjust from localhost to 127.0.0.1 to avoid some IP loookup issues. --- scripts/integration-helpers/framework.ts | 20 ++++++++++---------- scripts/triggers-end-to-end-tests/README.md | 6 +++--- scripts/triggers-end-to-end-tests/tests.ts | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/integration-helpers/framework.ts b/scripts/integration-helpers/framework.ts index e0a90602944..6618994912a 100644 --- a/scripts/integration-helpers/framework.ts +++ b/scripts/integration-helpers/framework.ts @@ -65,17 +65,17 @@ export interface FrameworkOptions { export class EmulatorEndToEndTest { emulatorHubPort = 0; - rtdbEmulatorHost = "localhost"; + rtdbEmulatorHost = "127.0.0.1"; rtdbEmulatorPort = 0; - firestoreEmulatorHost = "localhost"; + firestoreEmulatorHost = "127.0.0.1"; firestoreEmulatorPort = 0; - functionsEmulatorHost = "localhost"; + functionsEmulatorHost = "127.0.0.1"; functionsEmulatorPort = 0; - pubsubEmulatorHost = "localhost"; + pubsubEmulatorHost = "127.0.0.1"; pubsubEmulatorPort = 0; - authEmulatorHost = "localhost"; + authEmulatorHost = "127.0.0.1"; authEmulatorPort = 0; - storageEmulatorHost = "localhost"; + storageEmulatorHost = "127.0.0.1"; storageEmulatorPort = 0; allEmulatorsStarted = false; @@ -318,7 +318,7 @@ export class TriggerEndToEndTest extends EmulatorEndToEndTest { } invokeHttpFunction(name: string, zone = FIREBASE_PROJECT_ZONE): Promise { - const url = `http://localhost:${[this.functionsEmulatorPort, this.project, zone, name].join( + const url = `http://127.0.0.1:${[this.functionsEmulatorPort, this.project, zone, name].join( "/" )}`; return fetch(url); @@ -329,7 +329,7 @@ export class TriggerEndToEndTest extends EmulatorEndToEndTest { body: Record, zone = FIREBASE_PROJECT_ZONE ): Promise { - const url = `http://localhost:${this.functionsEmulatorPort}/${[this.project, zone, name].join( + const url = `http://127.0.0.1:${this.functionsEmulatorPort}/${[this.project, zone, name].join( "/" )}`; return fetch(url, { @@ -414,12 +414,12 @@ export class TriggerEndToEndTest extends EmulatorEndToEndTest { } disableBackgroundTriggers(): Promise { - const url = `http://localhost:${this.emulatorHubPort}/functions/disableBackgroundTriggers`; + const url = `http://127.0.0.1:${this.emulatorHubPort}/functions/disableBackgroundTriggers`; return fetch(url, { method: "PUT" }); } enableBackgroundTriggers(): Promise { - const url = `http://localhost:${this.emulatorHubPort}/functions/enableBackgroundTriggers`; + const url = `http://127.0.0.1:${this.emulatorHubPort}/functions/enableBackgroundTriggers`; return fetch(url, { method: "PUT" }); } } diff --git a/scripts/triggers-end-to-end-tests/README.md b/scripts/triggers-end-to-end-tests/README.md index 77cf7899b37..e75619b254e 100644 --- a/scripts/triggers-end-to-end-tests/README.md +++ b/scripts/triggers-end-to-end-tests/README.md @@ -8,16 +8,16 @@ introduced in the following PRs: # Running Instructions -Install dependencies: +From the firebase-tools folder, install dependencies: ``` -cd firebase-tools/scripts/triggers-end-to-end-tests && npm install +$ (cd scripts/triggers-end-to-end-tests && npm install) ``` Run the test: ``` -$ cd firebase-tools/scripts/triggers-end-to-end-tests && npm test +$ FBTOOLS_TARGET_PROJECT=demo-test npm run test:triggers-end-to-end ``` This end-to-end test uses the mocha testing framework. diff --git a/scripts/triggers-end-to-end-tests/tests.ts b/scripts/triggers-end-to-end-tests/tests.ts index 58b768d3509..c0265991029 100644 --- a/scripts/triggers-end-to-end-tests/tests.ts +++ b/scripts/triggers-end-to-end-tests/tests.ts @@ -56,13 +56,13 @@ describe("function triggers", () => { firestore = new Firestore({ port: test.firestoreEmulatorPort, projectId: FIREBASE_PROJECT, - servicePath: "localhost", + servicePath: "127.0.0.1", ssl: false, }); admin.initializeApp({ projectId: FIREBASE_PROJECT, - databaseURL: `http://localhost:${test.rtdbEmulatorPort}?ns=${FIREBASE_PROJECT}`, + databaseURL: `http://127.0.0.1:${test.rtdbEmulatorPort}?ns=${FIREBASE_PROJECT}`, credential: ADMIN_CREDENTIAL, }); From 09d1f1ff0b6fae8511d22f3a2c62b43375ee1449 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 4 Jan 2023 08:31:13 -0800 Subject: [PATCH 0742/1699] Rename internal package name to avoid triggering vuln scan. (#5375) --- .../fixtures/yarn-workspaces/package.json | 2 +- .../yarn-workspaces/packages/{a => a-test-pkg}/index.js | 0 .../yarn-workspaces/packages/a-test-pkg/package.json | 5 +++++ .../fixtures/yarn-workspaces/packages/a/package.json | 4 ---- .../fixtures/yarn-workspaces/packages/functions/index.js | 2 +- .../fixtures/yarn-workspaces/packages/functions/package.json | 5 +++-- 6 files changed, 10 insertions(+), 8 deletions(-) rename scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/{a => a-test-pkg}/index.js (100%) create mode 100644 scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a-test-pkg/package.json delete mode 100644 scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/package.json diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/package.json b/scripts/functions-discover-tests/fixtures/yarn-workspaces/package.json index 01514ba5512..643e18c292f 100644 --- a/scripts/functions-discover-tests/fixtures/yarn-workspaces/package.json +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/package.json @@ -1,5 +1,5 @@ { "name": "yarn-workspace", "private": true, - "workspaces": ["packages/functions", "packages/a"] + "workspaces": ["packages/functions", "packages/a-test-pkg"] } \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/index.js b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a-test-pkg/index.js similarity index 100% rename from scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/index.js rename to scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a-test-pkg/index.js diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a-test-pkg/package.json b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a-test-pkg/package.json new file mode 100644 index 00000000000..14739ac24c8 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a-test-pkg/package.json @@ -0,0 +1,5 @@ +{ + "name": "@firebase/a-test-pkg", + "version": "0.0.1", + "private": true +} diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/package.json b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/package.json deleted file mode 100644 index 2330fc5f4c0..00000000000 --- a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/a/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "a", - "version": "0.0.1" -} diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/index.js b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/index.js index 53008415880..35bc8c0f570 100644 --- a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/index.js +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/index.js @@ -1,6 +1,6 @@ const functions = require("firebase-functions"); const { onRequest } = require("firebase-functions/v2/https"); -const { msg } = require("a"); +const { msg } = require("@firebase/a-test-pkg"); exports.hellov1 = functions.https.onRequest((request, response) => { response.send(msg); diff --git a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/package.json b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/package.json index b2e02c8e3f7..d97802dab12 100644 --- a/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/package.json +++ b/scripts/functions-discover-tests/fixtures/yarn-workspaces/packages/functions/package.json @@ -4,9 +4,10 @@ "dependencies": { "firebase-functions": "4.0.0", "firebase-admin": "^11.2.0", - "a": "latest" + "@firebase/a-test-pkg": "0.0.1" }, "engines": { "node": "16" - } + }, + "private": true } From ffb09f8809e6bf6a96bf9fe26eb18ae35221355f Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Wed, 4 Jan 2023 16:20:16 -0800 Subject: [PATCH 0743/1699] Pubsub force shutdown if shutdown isn't working. (#5294) * Pubsub force shutdown if shutdown isn't working. --- CHANGELOG.md | 1 + src/emulator/pubsubEmulator.ts | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5fa8a4aede..5fcce035c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed a bug in the pubsub emulator by forcing a shutdown if it didn't end cleanly. (#5294) - Fixes an issue where dependencies for emulated Extensions would not be installed on Windows - thanks @stfsy! (#5372) - Adds emulator support for Extensions with schedule triggers - thanks @stsfy! (#5374) - Fix bug where functions:delete command did not recognize '-' as delimiter. (#5290) diff --git a/src/emulator/pubsubEmulator.ts b/src/emulator/pubsubEmulator.ts index 68500e5b9d7..a208407a930 100644 --- a/src/emulator/pubsubEmulator.ts +++ b/src/emulator/pubsubEmulator.ts @@ -11,6 +11,14 @@ import { FirebaseError } from "../error"; import { EmulatorRegistry } from "./registry"; import { SignatureType } from "./functionsEmulatorShared"; import { CloudEvent } from "./events/types"; +import { execSync } from "child_process"; + +// Finds processes with "pubsub-emulator" in the description and runs `kill` if any exist +// Since the pubsub emulator doesn't export any data, force-killing will not affect export-on-exit +// Note the `[p]` is a workaround to avoid selecting the currently running `ps` process. +const PUBSUB_KILL_COMMAND = + "pubsub_pids=$(ps aux | grep '[p]ubsub-emulator' | awk '{print $2}');" + + " if [ ! -z '$pubsub_pids' ]; then kill -9 $pubsub_pids; fi;"; export interface PubsubEmulatorArgs { projectId: string; @@ -62,7 +70,15 @@ export class PubsubEmulator implements EmulatorInstance { } async stop(): Promise { - await downloadableEmulators.stop(Emulators.PUBSUB); + try { + await downloadableEmulators.stop(Emulators.PUBSUB); + } catch (e: unknown) { + this.logger.logLabeled("DEBUG", "pubsub", JSON.stringify(e)); + if (process.platform !== "win32") { + const buffer = execSync(PUBSUB_KILL_COMMAND); + this.logger.logLabeled("DEBUG", "pubsub", "Pubsub kill output: " + JSON.stringify(buffer)); + } + } } getInfo(): EmulatorInfo { From 1f2d8dbbfbfb5b7a06662d4cec819302b795b83f Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 5 Jan 2023 13:01:11 -0800 Subject: [PATCH 0744/1699] upgrade superstatic (#5389) * upgrade superstatic@9.0.1 * upgrade superstatic to 9.0.2 --- CHANGELOG.md | 1 + npm-shrinkwrap.json | 921 ++++++++++++++++++++++--------------------- package.json | 2 +- src/serve/hosting.ts | 12 +- 4 files changed, 486 insertions(+), 450 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fcce035c3c..de72ce64539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Fixes an issue where dependencies for emulated Extensions would not be installed on Windows - thanks @stfsy! (#5372) - Adds emulator support for Extensions with schedule triggers - thanks @stsfy! (#5374) - Fix bug where functions:delete command did not recognize '-' as delimiter. (#5290) +- Reintroduces an updated Hosting emulator with i18n (#4879) and Windows path (#5133) fixes. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0891f19b992..e2409366526 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -57,7 +57,7 @@ "stream-chain": "^2.2.4", "stream-json": "^1.7.3", "strip-ansi": "^6.0.1", - "superstatic": "^8.0.0", + "superstatic": "^9.0.2", "tar": "^6.1.11", "tcp-port-used": "^1.0.2", "tmp": "^0.2.1", @@ -2439,6 +2439,29 @@ "node": ">=10.13.0" } }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/npm-conf": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-1.0.5.tgz", + "integrity": "sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==", + "dependencies": { + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2706,7 +2729,8 @@ "node_modules/@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true }, "node_modules/@types/configstore": { "version": "4.0.0", @@ -3802,14 +3826,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -4260,6 +4276,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, "dependencies": { "ansi-align": "^3.0.0", "camelcase": "^5.3.1", @@ -4281,6 +4298,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, "dependencies": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -4296,6 +4314,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4308,6 +4327,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4318,12 +4338,14 @@ "node_modules/boxen/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==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/boxen/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, "engines": { "node": ">=8" } @@ -4332,6 +4354,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -4655,6 +4678,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, "engines": { "node": ">=6" } @@ -5052,14 +5076,6 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "node_modules/compare-semver": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/compare-semver/-/compare-semver-1.1.0.tgz", - "integrity": "sha512-AENcdfhxsMCzzl+QRdOwMQeA8tZBEEacAmA4pGPoyco27G9sIaM98WNYkcToC9O0wIx1vE+1ErmaM4t0/fXhMw==", - "dependencies": { - "semver": "^5.0.1" - } - }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -5121,6 +5137,15 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -7902,23 +7927,26 @@ } }, "node_modules/global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dependencies": { - "ini": "1.3.7" + "ini": "2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/global-dirs/node_modules/ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "engines": { + "node": ">=10" + } }, "node_modules/globals": { "version": "11.12.0", @@ -8331,9 +8359,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -8424,17 +8452,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -8972,15 +8989,15 @@ } }, "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9001,11 +9018,14 @@ "optional": true }, "node_modules/is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-number": { @@ -9119,6 +9139,20 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/isomorphic-fetch/node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -12094,6 +12128,11 @@ "node": ">= 8" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, "node_modules/proto3-json-serializer": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", @@ -13525,28 +13564,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/string-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", - "integrity": "sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==", - "dependencies": { - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -13714,81 +13731,47 @@ } }, "node_modules/superstatic": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-8.0.0.tgz", - "integrity": "sha512-PqlA2xuEwOlRZsknl58A/rZEmgCUcfWIFec0bn10wYE5/tbMhEbMXGHCYDppiXLXcuhGHyOp1IimM2hLqkLLuw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-9.0.2.tgz", + "integrity": "sha512-eKX9qubOaJbtdxn4gWhVVMXuno8cn0WPKOYgLAmLwYiHafrASXAIXHzL3Jx7w06yXiaM5e1DA2Ezeb08iV28+A==", "dependencies": { "basic-auth-connect": "^1.0.0", - "chalk": "^1.1.3", - "commander": "^9.2.0", - "compare-semver": "^1.0.0", + "commander": "^9.4.0", "compression": "^1.7.0", - "connect": "^3.6.2", + "connect": "^3.7.0", "destroy": "^1.0.4", "fast-url-parser": "^1.1.3", "glob-slasher": "^1.0.1", "is-url": "^1.2.2", "join-path": "^1.1.1", "lodash": "^4.17.19", - "mime-types": "^2.1.16", - "minimatch": "^3.0.4", + "mime-types": "^2.1.35", + "minimatch": "^5.1.0", "morgan": "^1.8.2", "on-finished": "^2.2.0", "on-headers": "^1.0.0", "path-to-regexp": "^1.8.0", "router": "^1.3.1", - "string-length": "^1.0.0", - "update-notifier": "^4.1.1" + "update-notifier-cjs": "^5.1.6" }, "bin": { - "superstatic": "bin/server" + "superstatic": "lib/bin/server.js" }, "engines": { - "node": ">= 12.20" + "node": "^14.18.0 || >=16.4.0" }, "optionalDependencies": { - "re2": "^1.15.8" - } - }, - "node_modules/superstatic/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superstatic/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "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" - }, - "engines": { - "node": ">=0.10.0" + "re2": "^1.17.7" } }, - "node_modules/superstatic/node_modules/color-convert": { + "node_modules/superstatic/node_modules/brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "balanced-match": "^1.0.0" } }, - "node_modules/superstatic/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==" - }, "node_modules/superstatic/node_modules/commander": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", @@ -13797,107 +13780,28 @@ "node": "^12.20.0 || >=14" } }, - "node_modules/superstatic/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==", - "engines": { - "node": ">=8" - } - }, "node_modules/superstatic/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "node_modules/superstatic/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/superstatic/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superstatic/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/superstatic/node_modules/update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dependencies": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "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", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/superstatic/node_modules/update-notifier/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/superstatic/node_modules/update-notifier/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/superstatic/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/superstatic/node_modules/update-notifier/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==", + "node_modules/superstatic/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "isarray": "0.0.1" } }, "node_modules/supertest": { @@ -14289,6 +14193,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", + "dev": true, "engines": { "node": ">=8" }, @@ -14571,6 +14476,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, "engines": { "node": ">=8" } @@ -14841,7 +14747,33 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/update-notifier/node_modules/ansi-styles": { + "node_modules/update-notifier-cjs": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/update-notifier-cjs/-/update-notifier-cjs-5.1.6.tgz", + "integrity": "sha512-wgxdSBWv3x/YpMzsWz5G4p4ec7JWD0HCl8W6bmNB6E5Gwo+1ym5oN4hiXpLf0mPySVEJEIsYlkshnplkg2OP9A==", + "dependencies": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "isomorphic-fetch": "^3.0.0", + "pupa": "^2.1.1", + "registry-auth-token": "^5.0.1", + "registry-url": "^5.1.0", + "semver": "^7.3.7", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/update-notifier-cjs/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==", @@ -14855,7 +14787,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/update-notifier/node_modules/boxen": { + "node_modules/update-notifier-cjs/node_modules/boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", @@ -14876,10 +14808,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "node_modules/update-notifier-cjs/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "engines": { "node": ">=10" }, @@ -14887,10 +14819,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "node_modules/update-notifier-cjs/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14902,7 +14834,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/update-notifier/node_modules/color-convert": { + "node_modules/update-notifier-cjs/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==", @@ -14913,26 +14845,12 @@ "node": ">=7.0.0" } }, - "node_modules/update-notifier/node_modules/color-name": { + "node_modules/update-notifier-cjs/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==" }, - "node_modules/update-notifier/node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/has-flag": { + "node_modules/update-notifier-cjs/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==", @@ -14940,22 +14858,46 @@ "node": ">=8" } }, - "node_modules/update-notifier/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "node_modules/update-notifier-cjs/node_modules/registry-auth-token": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz", + "integrity": "sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==", + "dependencies": { + "@pnpm/npm-conf": "^1.0.4" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/update-notifier-cjs/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, "engines": { "node": ">=10" } }, - "node_modules/update-notifier/node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "node_modules/update-notifier-cjs/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==", "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" + "has-flag": "^4.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier-cjs/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "engines": { "node": ">=10" }, @@ -14963,10 +14905,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "node_modules/update-notifier/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, "engines": { "node": ">=10" }, @@ -14974,6 +14940,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/update-notifier/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "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/update-notifier/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/update-notifier/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==" + }, + "node_modules/update-notifier/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==", + "engines": { + "node": ">=8" + } + }, "node_modules/update-notifier/node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -17387,6 +17403,23 @@ "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", "dev": true }, + "@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "requires": { + "graceful-fs": "4.2.10" + } + }, + "@pnpm/npm-conf": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-1.0.5.tgz", + "integrity": "sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==", + "requires": { + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + } + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -17627,7 +17660,8 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true }, "@types/configstore": { "version": "4.0.0", @@ -18532,11 +18566,6 @@ } } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" - }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -18914,6 +18943,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, "requires": { "ansi-align": "^3.0.0", "camelcase": "^5.3.1", @@ -18929,6 +18959,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -18938,6 +18969,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -18947,6 +18979,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -18954,17 +18987,20 @@ "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==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -19197,7 +19233,8 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, "camelcase-keys": { "version": "6.2.2", @@ -19490,14 +19527,6 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "compare-semver": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/compare-semver/-/compare-semver-1.1.0.tgz", - "integrity": "sha512-AENcdfhxsMCzzl+QRdOwMQeA8tZBEEacAmA4pGPoyco27G9sIaM98WNYkcToC9O0wIx1vE+1ErmaM4t0/fXhMw==", - "requires": { - "semver": "^5.0.1" - } - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -19549,6 +19578,15 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -21604,17 +21642,17 @@ } }, "global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "requires": { - "ini": "1.3.7" + "ini": "2.0.0" }, "dependencies": { "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" } } }, @@ -21953,9 +21991,9 @@ } }, "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "grapheme-splitter": { "version": "1.0.4", @@ -22029,14 +22067,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -22439,12 +22469,12 @@ } }, "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" } }, "is-interactive": { @@ -22459,9 +22489,9 @@ "optional": true }, "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==" }, "is-number": { "version": "7.0.0", @@ -22544,6 +22574,22 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "requires": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + }, + "dependencies": { + "whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + } + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -24862,6 +24908,11 @@ "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, "proto3-json-serializer": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", @@ -25996,24 +26047,6 @@ "safe-buffer": "~5.1.0" } }, - "string-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", - "integrity": "sha512-MNCACnufWUf3pQ57O5WTBMkKhzYIaKEcUioO0XHrTMafrbBaNk4IyDOLHBv5xbXO0jLLdsYWeFjpjG2hVHRDtw==", - "requires": { - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -26130,79 +26163,57 @@ } }, "superstatic": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-8.0.0.tgz", - "integrity": "sha512-PqlA2xuEwOlRZsknl58A/rZEmgCUcfWIFec0bn10wYE5/tbMhEbMXGHCYDppiXLXcuhGHyOp1IimM2hLqkLLuw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-9.0.2.tgz", + "integrity": "sha512-eKX9qubOaJbtdxn4gWhVVMXuno8cn0WPKOYgLAmLwYiHafrASXAIXHzL3Jx7w06yXiaM5e1DA2Ezeb08iV28+A==", "requires": { "basic-auth-connect": "^1.0.0", - "chalk": "^1.1.3", - "commander": "^9.2.0", - "compare-semver": "^1.0.0", + "commander": "^9.4.0", "compression": "^1.7.0", - "connect": "^3.6.2", + "connect": "^3.7.0", "destroy": "^1.0.4", "fast-url-parser": "^1.1.3", "glob-slasher": "^1.0.1", "is-url": "^1.2.2", "join-path": "^1.1.1", "lodash": "^4.17.19", - "mime-types": "^2.1.16", - "minimatch": "^3.0.4", + "mime-types": "^2.1.35", + "minimatch": "^5.1.0", "morgan": "^1.8.2", "on-finished": "^2.2.0", "on-headers": "^1.0.0", "path-to-regexp": "^1.8.0", - "re2": "^1.15.8", + "re2": "^1.17.7", "router": "^1.3.1", - "string-length": "^1.0.0", - "update-notifier": "^4.1.1" + "update-notifier-cjs": "^5.1.6" }, "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "requires": { - "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" - } - }, - "color-convert": { + "brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "requires": { - "color-name": "~1.1.4" + "balanced-match": "^1.0.0" } }, - "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==" - }, "commander": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==" }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -26210,66 +26221,6 @@ "requires": { "isarray": "0.0.1" } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==" - }, - "update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "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", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "requires": { - "has-flag": "^4.0.0" - } - } - } } } }, @@ -26575,7 +26526,8 @@ "term-size": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", - "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==" + "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", + "dev": true }, "terser": { "version": "5.15.0", @@ -26783,7 +26735,8 @@ "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true }, "type-is": { "version": "1.6.18", @@ -27027,42 +26980,124 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + } + } + }, + "update-notifier-cjs": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/update-notifier-cjs/-/update-notifier-cjs-5.1.6.tgz", + "integrity": "sha512-wgxdSBWv3x/YpMzsWz5G4p4ec7JWD0HCl8W6bmNB6E5Gwo+1ym5oN4hiXpLf0mPySVEJEIsYlkshnplkg2OP9A==", + "requires": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "isomorphic-fetch": "^3.0.0", + "pupa": "^2.1.1", + "registry-auth-token": "^5.0.1", + "registry-url": "^5.1.0", + "semver": "^7.3.7", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "requires": { - "ini": "2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" - }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "registry-auth-token": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz", + "integrity": "sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==", "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" + "@pnpm/npm-conf": "^1.0.4" } }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==" - }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "requires": { "lru-cache": "^6.0.0" } diff --git a/package.json b/package.json index c477a8e5e05..ea36b4dd897 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "stream-chain": "^2.2.4", "stream-json": "^1.7.3", "strip-ansi": "^6.0.1", - "superstatic": "^8.0.0", + "superstatic": "^9.0.2", "tar": "^6.1.11", "tcp-port-used": "^1.0.2", "tmp": "^0.2.1", diff --git a/src/serve/hosting.ts b/src/serve/hosting.ts index e2bb51cbdd0..f33d586b0d5 100644 --- a/src/serve/hosting.ts +++ b/src/serve/hosting.ts @@ -1,8 +1,7 @@ const morgan = require("morgan"); -const { server: superstatic } = require("superstatic"); // eslint-disable-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment -import * as clc from "colorette"; import { isIPv4 } from "net"; -import { NextFunction, Request, Response } from "express"; +import { server as superstatic } from "superstatic"; +import * as clc from "colorette"; import { detectProjectRoot } from "../detectProjectRoot"; import { FirebaseError } from "../error"; @@ -18,6 +17,7 @@ import { createDestroyer } from "../utils"; import { requireHostingSite } from "../requireHostingSite"; import { getProjectId } from "../projectUtils"; import { checkListenable } from "../emulator/portUtils"; +import { IncomingMessage, ServerResponse } from "http"; let destroyServer: undefined | (() => Promise) = undefined; @@ -52,13 +52,13 @@ function startServer(options: any, config: any, port: number, init: TemplateServ const server = superstatic({ debug: false, port: port, - host: options.host, + hostname: options.host, config: config, compression: true, - cwd: detectProjectRoot(options), + cwd: detectProjectRoot(options) || undefined, stack: "strict", before: { - files: (req: Request, res: Response, next: NextFunction) => { + files: (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => { // We do these in a single method to ensure order of operations morganMiddleware(req, res, () => null); firebaseMiddleware(req, res, next); From a010a78de723cb90170990cbeef638edae4d399e Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 5 Jan 2023 13:28:41 -0800 Subject: [PATCH 0745/1699] Fixes issue where secrets would be undefined in after hot reload in sequential mode (#5397) * Fixes issue where secrets would be undefined in after hot reload in sequential mode * add changelog entry --- CHANGELOG.md | 5 +++-- src/emulator/functionsEmulator.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de72ce64539..502a7d20909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -- Fixed a bug in the pubsub emulator by forcing a shutdown if it didn't end cleanly. (#5294) +- Fixes a bug in the pubsub emulator by forcing a shutdown if it didn't end cleanly. (#5294) - Fixes an issue where dependencies for emulated Extensions would not be installed on Windows - thanks @stfsy! (#5372) - Adds emulator support for Extensions with schedule triggers - thanks @stsfy! (#5374) -- Fix bug where functions:delete command did not recognize '-' as delimiter. (#5290) +- Fixes an issue in the Functions emulator where secret values were undefined after hot reload with the `--inspect-functions` flag. (#5384) +- Fixes a bug where functions:delete command did not recognize '-' as delimiter. (#5290) - Reintroduces an updated Hosting emulator with i18n (#4879) and Windows path (#5133) fixes. diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 499826d0de0..984a94955cc 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -664,7 +664,7 @@ export class FunctionsEmulator implements EmulatorInstance { // Since we're about to start a runtime to be shared by all the functions in this codebase, // we need to make sure it has all the secrets used by any function in the codebase. emulatableBackend.secretEnv = Object.values( - toSetup.reduce( + triggerDefinitions.reduce( (acc: Record, curr: EmulatedTriggerDefinition) => { for (const secret of curr.secretEnvironmentVariables || []) { acc[secret.key] = secret; From 75b3dbee57678f8ca85462dfbd74ca4809106d5e Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Thu, 5 Jan 2023 14:12:12 -0800 Subject: [PATCH 0746/1699] Upgrade ES UI to 1.11.2. (#5394) --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 502a7d20909..45c1842cf77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ - Fixes a bug in the pubsub emulator by forcing a shutdown if it didn't end cleanly. (#5294) - Fixes an issue where dependencies for emulated Extensions would not be installed on Windows - thanks @stfsy! (#5372) - Adds emulator support for Extensions with schedule triggers - thanks @stsfy! (#5374) +- Update the Emulator Suite UI to v1.11.2 to capture a set of accessibility improvements. (#5394) - Fixes an issue in the Functions emulator where secret values were undefined after hot reload with the `--inspect-functions` flag. (#5384) - Fixes a bug where functions:delete command did not recognize '-' as delimiter. (#5290) - Reintroduces an updated Hosting emulator with i18n (#4879) and Windows path (#5133) fixes. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 4bdf7d6bfdc..b4336da8c61 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -45,9 +45,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet ui: experiments.isEnabled("emulatoruisnapshot") ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" } : { - version: "1.11.1", - expectedSize: 3061713, - expectedChecksum: "a4944414518be206280b495f526f18bf", + version: "1.11.2", + expectedSize: 3062873, + expectedChecksum: "fe7f668437d0e3c3b92677aaaade78bf", }, pubsub: { version: "0.7.1", From cb8cbb70b681ad500bc7bd3a32906f40fa5f6a7d Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 5 Jan 2023 22:38:33 +0000 Subject: [PATCH 0747/1699] 11.20.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e2409366526..c2c5d561fa4 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.19.0", + "version": "11.20.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.19.0", + "version": "11.20.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index ea36b4dd897..d7f6e13667f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.19.0", + "version": "11.20.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 68bdfa7ccae2a248be36797a37bb00ccdff846c8 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 5 Jan 2023 22:38:44 +0000 Subject: [PATCH 0748/1699] [firebase-release] Removed change log and reset repo after 11.20.0 release --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45c1842cf77..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +0,0 @@ -- Fixes a bug in the pubsub emulator by forcing a shutdown if it didn't end cleanly. (#5294) -- Fixes an issue where dependencies for emulated Extensions would not be installed on Windows - thanks @stfsy! (#5372) -- Adds emulator support for Extensions with schedule triggers - thanks @stsfy! (#5374) -- Update the Emulator Suite UI to v1.11.2 to capture a set of accessibility improvements. (#5394) -- Fixes an issue in the Functions emulator where secret values were undefined after hot reload with the `--inspect-functions` flag. (#5384) -- Fixes a bug where functions:delete command did not recognize '-' as delimiter. (#5290) -- Reintroduces an updated Hosting emulator with i18n (#4879) and Windows path (#5133) fixes. From 9c2eeba83eff5d8276d828bb36264f18f0c12dbb Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 5 Jan 2023 16:07:06 -0800 Subject: [PATCH 0749/1699] Fix bug where CLI was unable to deploy Firebase Functions in some monorepo setups (#5391) Partially fixes https://github.com/firebase/firebase-tools/issues/4952. https://github.com/firebase/firebase-tools/pull/5215 made changes to the CLI to detect Functions SDK even in monorepo setups where Functions SDK is hoisted (i.e. Functions SDK dependency is declared in the parent directory but not in the sub-package directory). We should've made corresponding change in how the Functions SDK binary is executed - instead of always looking up the sub-package's `node_modules`, we should be looking at the `node_modules` closest to where the Functions SDK dependency is declared. This setup seems common in scenarios where the developer bundles the functions source using bundlers like vite/webpack/etc. This kind of technique has shown to help [AWS lambda's cold start time](https://aws.amazon.com/blogs/compute/optimizing-node-js-dependencies-in-aws-lambda/), and I think it's something we'd want to explore in Google Cloud Functions too. --- CHANGELOG.md | 1 + .../fixtures/bundled/dist/index.js | 20 +++++++++++++++++++ .../fixtures/bundled/dist/package.json | 7 +++++++ .../fixtures/bundled/firebase.json | 5 +++++ .../fixtures/bundled/install.sh | 5 +++++ .../fixtures/bundled/package.json | 10 ++++++++++ scripts/functions-discover-tests/tests.ts | 10 ++++++++++ src/deploy/functions/runtimes/node/index.ts | 7 ++++++- 8 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 scripts/functions-discover-tests/fixtures/bundled/dist/index.js create mode 100644 scripts/functions-discover-tests/fixtures/bundled/dist/package.json create mode 100644 scripts/functions-discover-tests/fixtures/bundled/firebase.json create mode 100755 scripts/functions-discover-tests/fixtures/bundled/install.sh create mode 100644 scripts/functions-discover-tests/fixtures/bundled/package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..542d3f8d087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fix bug where CLI was unable to deploy Firebase Functions in some monorepo setups (#5391) diff --git a/scripts/functions-discover-tests/fixtures/bundled/dist/index.js b/scripts/functions-discover-tests/fixtures/bundled/dist/index.js new file mode 100644 index 00000000000..827530646c8 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/bundled/dist/index.js @@ -0,0 +1,20 @@ +/** + * Minimal example of a "bundled" function source. + * + * Instead of actually bundling the source code, we manually annotate + * the exported function with the __endpoint property to test the situation + * where the distributed package doesn't include Firebase Functions SDK as a + * dependency. + */ + +const hello = (req, resp) => { + resp.send("hello"); +}; + +hello.__endpoint = { + platform: "gcfv2", + region: "region", + httpsTrigger: {}, +}; + +exports.hello = hello; diff --git a/scripts/functions-discover-tests/fixtures/bundled/dist/package.json b/scripts/functions-discover-tests/fixtures/bundled/dist/package.json new file mode 100644 index 00000000000..364e1e20f6d --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/bundled/dist/package.json @@ -0,0 +1,7 @@ +{ + "name": "dist", + "version": "0.0.1", + "engines": { + "node": "16" + } +} diff --git a/scripts/functions-discover-tests/fixtures/bundled/firebase.json b/scripts/functions-discover-tests/fixtures/bundled/firebase.json new file mode 100644 index 00000000000..c9df875f3e4 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/bundled/firebase.json @@ -0,0 +1,5 @@ +{ + "functions": { + "source": "dist" + } +} \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/bundled/install.sh b/scripts/functions-discover-tests/fixtures/bundled/install.sh new file mode 100755 index 00000000000..de6890dcdf0 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/bundled/install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -euxo pipefail # bash strict mode +IFS=$'\n\t' + +npm i diff --git a/scripts/functions-discover-tests/fixtures/bundled/package.json b/scripts/functions-discover-tests/fixtures/bundled/package.json new file mode 100644 index 00000000000..72a92e6258a --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/bundled/package.json @@ -0,0 +1,10 @@ +{ + "name": "dist", + "version": "0.0.1", + "dependencies": { + "firebase-functions": "^4.1.1" + }, + "engines": { + "node": "16" + } +} \ No newline at end of file diff --git a/scripts/functions-discover-tests/tests.ts b/scripts/functions-discover-tests/tests.ts index 769a0e5ec04..4c3cf859d82 100644 --- a/scripts/functions-discover-tests/tests.ts +++ b/scripts/functions-discover-tests/tests.ts @@ -67,6 +67,16 @@ describe("Function discovery test", function (this) { }, ], }, + { + name: "bundled", + projectDir: "bundled", + expects: [ + { + codebase: "default", + endpoints: ["hello"], + }, + ], + }, ]; for (const tc of testCases) { diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 84cec2703a8..8241118caeb 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -108,7 +108,12 @@ export class Delegate { if (Object.keys(config || {}).length) { env.CLOUD_RUNTIME_CONFIG = JSON.stringify(config); } - const childProcess = spawn("./node_modules/.bin/firebase-functions", [this.sourceDir], { + // At this point, we've already confirmed that we found supported firebase functions sdk. + const sdkPath = require.resolve("firebase-functions", { paths: [this.sourceDir] }); + // Find location of the closest node_modules/ directory where we found the sdk. + const binPath = sdkPath.substring(0, sdkPath.lastIndexOf("node_modules") + 12); + // And execute the binary included in the sdk. + const childProcess = spawn(path.join(binPath, ".bin", "firebase-functions"), [this.sourceDir], { env, cwd: this.sourceDir, stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "inherit"], From abcaaab6574a82bf163668cab943a0370aacbe68 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Fri, 6 Jan 2023 14:08:59 -0500 Subject: [PATCH 0750/1699] Upgrade Storage rules runtime to v1.1.3 (ternary operators) (#5398) * upgrade rules * format * v1.1.3 --- CHANGELOG.md | 1 + .../rules/runtime.test.ts | 32 +++++++++++++++++++ src/emulator/downloadableEmulators.ts | 6 ++-- src/emulator/storage/rules/manager.ts | 2 +- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 542d3f8d087..a9dca357275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Fix bug where CLI was unable to deploy Firebase Functions in some monorepo setups (#5391) +- Upgrade Storage Rules Runtime to v1.1.3 to support ternary operators (#5370) diff --git a/scripts/storage-emulator-integration/rules/runtime.test.ts b/scripts/storage-emulator-integration/rules/runtime.test.ts index 4f84c740b00..a8e092ac598 100644 --- a/scripts/storage-emulator-integration/rules/runtime.test.ts +++ b/scripts/storage-emulator-integration/rules/runtime.test.ts @@ -345,6 +345,38 @@ describe("Storage Rules Runtime", function () { ).to.be.false; }); }); + + describe("features", () => { + describe("ternary", () => { + it("should support ternary operators", async () => { + const rulesContent = ` + rules_version = '2'; + service firebase.storage { + match /b/{bucket}/o { + match /{file} { + allow read: if request.path[3] == "test" ? true : false; + } + } + }`; + + expect( + await testIfPermitted(runtime, rulesContent, { + method: RulesetOperationMethod.GET, + path: "/b/BUCKET_NAME/o/test", + file: {}, + }) + ).to.be.true; + + expect( + await testIfPermitted(runtime, rulesContent, { + method: RulesetOperationMethod.GET, + path: "/b/BUCKET_NAME/o/someRandomFile", + file: {}, + }) + ).to.be.false; + }); + }); + }); }); async function testIfPermitted( diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index b4336da8c61..5681beb14f6 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -38,9 +38,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee", }, storage: { - version: "1.1.2", - expectedSize: 47028740, - expectedChecksum: "983b4415b1e72b109864f1b8e7ea7546", + version: "1.1.3", + expectedSize: 52892936, + expectedChecksum: "2ca11ec1193003bea89f806cc085fa25", }, ui: experiments.isEnabled("emulatoruisnapshot") ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" } diff --git a/src/emulator/storage/rules/manager.ts b/src/emulator/storage/rules/manager.ts index f8cb05b814a..53d292196f9 100644 --- a/src/emulator/storage/rules/manager.ts +++ b/src/emulator/storage/rules/manager.ts @@ -110,7 +110,7 @@ class DefaultStorageRulesManager implements StorageRulesManager { }:${parsedIssue.sourcePosition_.line_}` ); } catch { - this._logger.log("WARN", issue); + this._logger.logLabeled("WARN", "storage", issue); } }); return issues; From b501f0740411ee6ad7e56d6e9d71052b7897861b Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 9 Jan 2023 09:52:51 -0800 Subject: [PATCH 0751/1699] update script dependencies (#5409) --- .../triggers/package-lock.json | 1991 ++++++++++++----- 1 file changed, 1419 insertions(+), 572 deletions(-) diff --git a/scripts/triggers-end-to-end-tests/triggers/package-lock.json b/scripts/triggers-end-to-end-tests/triggers/package-lock.json index 1d007bd3b1d..75bcfa5f372 100644 --- a/scripts/triggers-end-to-end-tests/triggers/package-lock.json +++ b/scripts/triggers-end-to-end-tests/triggers/package-lock.json @@ -19,6 +19,17 @@ "node": "16" } }, + "node_modules/@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@fastify/busboy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", @@ -586,159 +597,48 @@ "integrity": "sha512-zThUKcqIU6utWzM93uEvhlh8qj8A5LMPFJPvk/ODb+8GSSif19xM2Lw1M2ijyBy8+6skSkQBbavPzOU5Oh/8tQ==" }, "node_modules/@google-cloud/firestore": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-5.0.2.tgz", - "integrity": "sha512-xlGcNYaW0nvUMzNn2+pLfbEBVt6oysVqtM89faMgZWkWfEtvIQGS0h5PRdLlcqufNzRCX3yIGv29Pb+03ys+VA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.1.tgz", + "integrity": "sha512-5q4sl1XCL8NH2y82KZ4WQGHDOPnrSMYq3JpIeKD5C0OCSb4MfckOTB9LeAQ3p5tlL+7UsVRHj0SyzKz27XZJjw==", "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@google-cloud/firestore/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@google-cloud/firestore/node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/firestore/node_modules/gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "optional": true, - "dependencies": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/firestore/node_modules/google-auth-library": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", - "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/firestore/node_modules/google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", - "optional": true, - "dependencies": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/firestore/node_modules/google-p12-pem": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", - "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", - "optional": true, - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/firestore/node_modules/gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", - "optional": true, - "dependencies": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", - "jws": "^4.0.0" + "google-gax": "^3.5.1", + "protobufjs": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" } }, - "node_modules/@google-cloud/firestore/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/@google-cloud/firestore/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", "optional": true }, - "node_modules/@google-cloud/firestore/node_modules/retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "node_modules/@google-cloud/firestore/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, "optional": true, "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" + "@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": ">=8.10.0" + "node": ">=12.0.0" } }, "node_modules/@google-cloud/paginator": { @@ -803,16 +703,15 @@ } }, "node_modules/@google-cloud/storage": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.2.2.tgz", - "integrity": "sha512-KhAOxmGfmELKKn6cdvgGfAi/YBLi19hI1jX3QI7xQmbeajSFMgUKrIPbbyfMIxQPOEQ9vG0MQX1uganlA/HTRA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "optional": true, "dependencies": { "@google-cloud/paginator": "^3.0.7", "@google-cloud/projectify": "^3.0.0", "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", "duplexify": "^4.0.0", @@ -823,9 +722,7 @@ "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", "retry-request": "^5.0.0", - "stream-events": "^1.0.4", "teeny-request": "^8.0.0", "uuid": "^8.0.0" }, @@ -856,26 +753,81 @@ } }, "node_modules/@google-cloud/storage/node_modules/@google-cloud/promisify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.0.tgz", - "integrity": "sha512-91ArYvRgXWb73YvEOBMmOcJc0bDRs5yiVHnqkwoG0f3nm7nZuipllz6e7BvFESBvjkDTBC0zMD8QxedUwNLc1A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", "optional": true, "engines": { "node": ">=12" } }, + "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==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "dependencies": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" }, "engines": { "node": "^8.13.0 || >=10.10.0" } }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, + "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/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, "node_modules/@grpc/proto-loader": { "version": "0.6.13", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", @@ -1039,6 +991,11 @@ "@types/node": "*" } }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" + }, "node_modules/@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -1050,6 +1007,20 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -1102,6 +1073,25 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1156,6 +1146,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1210,6 +1205,11 @@ "node": "*" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "node_modules/body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -1267,6 +1267,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -1378,6 +1404,11 @@ "ms": "2.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1446,6 +1477,14 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "optional": true }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -1459,6 +1498,95 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1527,6 +1655,11 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, "node_modules/fast-text-encoding": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", @@ -1595,60 +1728,99 @@ } }, "node_modules/firebase-admin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.0.0.tgz", - "integrity": "sha512-x56u+Q1P8QDvQKaYRe29ZUM/3f829cP8tsKCDXOhaIX/GbGfgcdjRhPmCafzlwgCWP5wW9NkOgIhnrw94zucvw==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.4.1.tgz", + "integrity": "sha512-t5+Pf8rC01TW1KPD5U8Q45AEn7eK+FJaHlpzYStFb62J+MQmN/kB/PWUEsNn+7MNAQ0DZxFUCgJoi+bRmf83oQ==", "dependencies": { "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.2.0", - "@firebase/database-types": "^0.9.7", + "@firebase/database-compat": "^0.2.6", + "@firebase/database-types": "^0.9.13", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^2.1.4", "node-forge": "^1.3.1", - "uuid": "^8.3.2" + "uuid": "^9.0.0" }, "engines": { "node": ">=14" }, "optionalDependencies": { - "@google-cloud/firestore": "^5.0.2", - "@google-cloud/storage": "^6.1.0" + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2" } }, - "node_modules/firebase-admin/node_modules/@firebase/database": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.3.tgz", - "integrity": "sha512-ZE+QJqQUaCTZiIzGq3RJLo64HRMtbdaEwyDhfZyPEzMJV4kyLsw3cHdEHVCtBmdasTvwtpO2YRFmd4AXAoKtNw==", + "node_modules/firebase-admin/node_modules/@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + }, + "node_modules/firebase-admin/node_modules/@firebase/auth-interop-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/firebase-admin/node_modules/@firebase/component": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", "dependencies": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "node_modules/firebase-admin/node_modules/@firebase/database": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "node_modules/firebase-admin/node_modules/@firebase/database-compat": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.3.tgz", - "integrity": "sha512-uwSMnbjlSQM5gQRq8OoBLs7uc7obwsl0D6kSDAnMOlPtPl9ert79Rq9faU/COjybsJ8l7tNXMVYYJo3mQ5XNrA==", - "dependencies": { - "@firebase/component": "0.5.17", - "@firebase/database": "0.13.3", - "@firebase/database-types": "0.9.11", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, "node_modules/firebase-admin/node_modules/@firebase/database-types": { - "version": "0.9.11", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.11.tgz", - "integrity": "sha512-27V3eFomWCZqLR6qb3Q9eS2lsUtulhSHeDNaL6fImwnhvMYTmf6ZwMfRWupgi8AFwW4s91g9Oc1/fkQtJGHKQw==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "dependencies": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.6.3" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" + } + }, + "node_modules/firebase-admin/node_modules/@firebase/logger": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/firebase-admin/node_modules/@firebase/util": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", + "dependencies": { + "tslib": "^2.1.0" } }, "node_modules/firebase-functions": { @@ -1843,12 +2015,12 @@ } }, "node_modules/google-gax": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.1.3.tgz", - "integrity": "sha512-hWF2WbfD3o1Fnfq3qf0Wnr3DuQczC/5ebGYGB5swUZXl8sT5y5mhDbxOKAg+xUzhiPgnQADNyFk0uIsA+NKRIw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "dependencies": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", @@ -1858,16 +2030,73 @@ "node-fetch": "^2.6.1", "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", - "protobufjs": "6.11.3", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", "retry-request": "^5.0.0" }, "bin": { - "compileProtos": "build/tools/compileProtos.js" + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" }, "engines": { "node": ">=12" } }, + "node_modules/google-gax/node_modules/@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/google-gax/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/google-gax/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-gax/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/google-gax/node_modules/proto3-json-serializer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.0.2.tgz", @@ -1879,6 +2108,86 @@ "node": ">=12.0.0" } }, + "node_modules/google-gax/node_modules/proto3-json-serializer/node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "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.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/google-gax/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, + "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/google-gax/node_modules/protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/google-gax/node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, "node_modules/google-p12-pem": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.0.tgz", @@ -1893,6 +2202,11 @@ "node": ">=12.0.0" } }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, "node_modules/gtoken": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.0.tgz", @@ -1932,6 +2246,14 @@ "node": ">= 0.4.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==", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -2119,6 +2441,42 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "dependencies": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -2128,24 +2486,18 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/jwa": { @@ -2172,14 +2524,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/jszip": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz", @@ -2285,6 +2629,26 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -2298,6 +2662,14 @@ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2313,41 +2685,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -2392,6 +2729,46 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz", + "integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2455,6 +2832,25 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2538,6 +2934,22 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-defer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", @@ -2587,6 +2999,14 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2597,15 +3017,6 @@ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" }, - "node_modules/proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", - "optional": true, - "dependencies": { - "protobufjs": "^6.11.2" - } - }, "node_modules/protobufjs": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", @@ -2648,27 +3059,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "node_modules/qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -2726,6 +3116,14 @@ "node": ">=0.10.0" } }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -2819,6 +3217,20 @@ "node": ">= 10.15.0" } }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -2895,6 +3307,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2949,23 +3370,50 @@ "node": ">=8" } }, + "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==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" + }, "node_modules/teeny-request": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.0.tgz", - "integrity": "sha512-6KEYxXI4lQPSDkXzXpPmJPNmo7oqduFFbhOEHf8sfsLbXyCsb+umUjBtMGAKhaSToD8JNCtQutTRefu29K64JA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "optional": true, "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" }, "engines": { "node": ">=12" @@ -3005,6 +3453,17 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -3017,6 +3476,27 @@ "node": ">= 0.6" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3039,9 +3519,9 @@ } }, "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==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { "uuid": "dist/bin/uuid" } @@ -3094,6 +3574,14 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3135,6 +3623,11 @@ } } }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -3187,6 +3680,11 @@ } }, "dependencies": { + "@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==" + }, "@fastify/busboy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", @@ -3637,161 +4135,81 @@ "@firebase/storage": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.9.9.tgz", - "integrity": "sha512-Zch7srLT2SIh9y2nCVv/4Kne0HULn7OPkmreY70BJTUJ+g5WLRjggBq6x9fV5ls9V38iqMWfn4prxzX8yIc08A==", - "requires": { - "@firebase/component": "0.5.17", - "@firebase/util": "1.6.3", - "node-fetch": "2.6.7", - "tslib": "^2.1.0" - } - }, - "@firebase/storage-compat": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.1.17.tgz", - "integrity": "sha512-nOYmnpI0gwoz5nROseMi9WbmHGf+xumfsOvdPyMZAjy0VqbDnpKIwmTUZQBdR+bLuB5oIkHQsvw9nbb1SH+PzQ==", - "requires": { - "@firebase/component": "0.5.17", - "@firebase/storage": "0.9.9", - "@firebase/storage-types": "0.6.0", - "@firebase/util": "1.6.3", - "tslib": "^2.1.0" - } - }, - "@firebase/storage-types": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.6.0.tgz", - "integrity": "sha512-1LpWhcCb1ftpkP/akhzjzeFxgVefs6eMD2QeKiJJUGH1qOiows2w5o0sKCUSQrvrRQS1lz3SFGvNR1Ck/gqxeA==", - "requires": {} - }, - "@firebase/util": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", - "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/webchannel-wrapper": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.6.2.tgz", - "integrity": "sha512-zThUKcqIU6utWzM93uEvhlh8qj8A5LMPFJPvk/ODb+8GSSif19xM2Lw1M2ijyBy8+6skSkQBbavPzOU5Oh/8tQ==" - }, - "@google-cloud/firestore": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-5.0.2.tgz", - "integrity": "sha512-xlGcNYaW0nvUMzNn2+pLfbEBVt6oysVqtM89faMgZWkWfEtvIQGS0h5PRdLlcqufNzRCX3yIGv29Pb+03ys+VA==", - "optional": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "optional": true, - "requires": { - "ms": "2.1.2" - } - }, - "gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - }, - "gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "optional": true, - "requires": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - } - }, - "google-auth-library": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", - "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", - "optional": true, - "requires": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" - } - }, - "google-p12-pem": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", - "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", - "optional": true, - "requires": { - "node-forge": "^1.3.1" - } - }, - "gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", - "optional": true, - "requires": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", - "jws": "^4.0.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "integrity": "sha512-Zch7srLT2SIh9y2nCVv/4Kne0HULn7OPkmreY70BJTUJ+g5WLRjggBq6x9fV5ls9V38iqMWfn4prxzX8yIc08A==", + "requires": { + "@firebase/component": "0.5.17", + "@firebase/util": "1.6.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + } + }, + "@firebase/storage-compat": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.1.17.tgz", + "integrity": "sha512-nOYmnpI0gwoz5nROseMi9WbmHGf+xumfsOvdPyMZAjy0VqbDnpKIwmTUZQBdR+bLuB5oIkHQsvw9nbb1SH+PzQ==", + "requires": { + "@firebase/component": "0.5.17", + "@firebase/storage": "0.9.9", + "@firebase/storage-types": "0.6.0", + "@firebase/util": "1.6.3", + "tslib": "^2.1.0" + } + }, + "@firebase/storage-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.6.0.tgz", + "integrity": "sha512-1LpWhcCb1ftpkP/akhzjzeFxgVefs6eMD2QeKiJJUGH1qOiows2w5o0sKCUSQrvrRQS1lz3SFGvNR1Ck/gqxeA==", + "requires": {} + }, + "@firebase/util": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", + "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/webchannel-wrapper": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.6.2.tgz", + "integrity": "sha512-zThUKcqIU6utWzM93uEvhlh8qj8A5LMPFJPvk/ODb+8GSSif19xM2Lw1M2ijyBy8+6skSkQBbavPzOU5Oh/8tQ==" + }, + "@google-cloud/firestore": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.1.tgz", + "integrity": "sha512-5q4sl1XCL8NH2y82KZ4WQGHDOPnrSMYq3JpIeKD5C0OCSb4MfckOTB9LeAQ3p5tlL+7UsVRHj0SyzKz27XZJjw==", + "optional": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^3.5.1", + "protobufjs": "^7.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", "optional": true }, - "retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "optional": true, "requires": { - "debug": "^4.1.1", - "extend": "^3.0.2" + "@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" } } } @@ -3843,16 +4261,15 @@ } }, "@google-cloud/storage": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.2.2.tgz", - "integrity": "sha512-KhAOxmGfmELKKn6cdvgGfAi/YBLi19hI1jX3QI7xQmbeajSFMgUKrIPbbyfMIxQPOEQ9vG0MQX1uganlA/HTRA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "optional": true, "requires": { "@google-cloud/paginator": "^3.0.7", "@google-cloud/projectify": "^3.0.0", "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", "duplexify": "^4.0.0", @@ -3863,9 +4280,7 @@ "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", "retry-request": "^5.0.0", - "stream-events": "^1.0.4", "teeny-request": "^8.0.0", "uuid": "^8.0.0" }, @@ -3887,20 +4302,66 @@ "optional": true }, "@google-cloud/promisify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.0.tgz", - "integrity": "sha512-91ArYvRgXWb73YvEOBMmOcJc0bDRs5yiVHnqkwoG0f3nm7nZuipllz6e7BvFESBvjkDTBC0zMD8QxedUwNLc1A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "optional": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "optional": true } } }, "@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "requires": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + } + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "requires": { + "@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" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + } + } + } } }, "@grpc/proto-loader": { @@ -4048,6 +4509,11 @@ "@types/node": "*" } }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" + }, "@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -4059,6 +4525,20 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -4105,6 +4585,17 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -4141,6 +4632,11 @@ "color-convert": "^2.0.1" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -4175,6 +4671,11 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==" }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -4222,6 +4723,23 @@ "get-intrinsic": "^1.0.2" } }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "requires": { + "lodash": "^4.17.15" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -4309,6 +4827,11 @@ "ms": "2.0.0" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4367,6 +4890,11 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "optional": true }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -4377,6 +4905,60 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "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==" + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -4436,6 +5018,11 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, "fast-text-encoding": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", @@ -4535,55 +5122,91 @@ } }, "firebase-admin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.0.0.tgz", - "integrity": "sha512-x56u+Q1P8QDvQKaYRe29ZUM/3f829cP8tsKCDXOhaIX/GbGfgcdjRhPmCafzlwgCWP5wW9NkOgIhnrw94zucvw==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.4.1.tgz", + "integrity": "sha512-t5+Pf8rC01TW1KPD5U8Q45AEn7eK+FJaHlpzYStFb62J+MQmN/kB/PWUEsNn+7MNAQ0DZxFUCgJoi+bRmf83oQ==", "requires": { "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.2.0", - "@firebase/database-types": "^0.9.7", - "@google-cloud/firestore": "^5.0.2", - "@google-cloud/storage": "^6.1.0", + "@firebase/database-compat": "^0.2.6", + "@firebase/database-types": "^0.9.13", + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^2.1.4", "node-forge": "^1.3.1", - "uuid": "^8.3.2" + "uuid": "^9.0.0" }, "dependencies": { + "@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + }, + "@firebase/auth-interop-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", + "requires": {} + }, + "@firebase/component": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", + "requires": { + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, "@firebase/database": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.3.tgz", - "integrity": "sha512-ZE+QJqQUaCTZiIzGq3RJLo64HRMtbdaEwyDhfZyPEzMJV4kyLsw3cHdEHVCtBmdasTvwtpO2YRFmd4AXAoKtNw==", + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", "requires": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "@firebase/database-compat": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.3.tgz", - "integrity": "sha512-uwSMnbjlSQM5gQRq8OoBLs7uc7obwsl0D6kSDAnMOlPtPl9ert79Rq9faU/COjybsJ8l7tNXMVYYJo3mQ5XNrA==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", "requires": { - "@firebase/component": "0.5.17", - "@firebase/database": "0.13.3", - "@firebase/database-types": "0.9.11", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, "@firebase/database-types": { - "version": "0.9.11", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.11.tgz", - "integrity": "sha512-27V3eFomWCZqLR6qb3Q9eS2lsUtulhSHeDNaL6fImwnhvMYTmf6ZwMfRWupgi8AFwW4s91g9Oc1/fkQtJGHKQw==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "requires": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.6.3" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" + } + }, + "@firebase/logger": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", + "requires": { + "tslib": "^2.1.0" } } } @@ -4702,12 +5325,12 @@ } }, "google-gax": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.1.3.tgz", - "integrity": "sha512-hWF2WbfD3o1Fnfq3qf0Wnr3DuQczC/5ebGYGB5swUZXl8sT5y5mhDbxOKAg+xUzhiPgnQADNyFk0uIsA+NKRIw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "requires": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", @@ -4717,16 +5340,122 @@ "node-fetch": "^2.6.1", "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", - "protobufjs": "6.11.3", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", "retry-request": "^5.0.0" }, "dependencies": { + "@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, "proto3-json-serializer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.0.2.tgz", "integrity": "sha512-wHxf8jYZ/LUP3M7XmULDKnbxBn+Bvk6SM+tDCPVTp9vraIzUi9hHsOBb1n2Y0VV0ukx4zBN/2vzMQYs4KWwRpg==", "requires": { "protobufjs": "^6.11.3" + }, + "dependencies": { + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@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.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + } + } + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "requires": { + "@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" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + } + } + }, + "protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "requires": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" } } } @@ -4739,6 +5468,11 @@ "node-forge": "^1.3.1" } }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, "gtoken": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.0.tgz", @@ -4771,6 +5505,11 @@ "function-bind": "^1.1.1" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -4910,6 +5649,36 @@ "@panva/asn1.js": "^1.0.0" } }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "requires": { + "xmlcreate": "^2.0.4" + } + }, + "jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "requires": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + } + }, "json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -4919,20 +5688,14 @@ } }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "requires": { "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "jwa": { @@ -4958,11 +5721,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -5064,6 +5822,23 @@ "safe-buffer": "^5.0.1" } }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, "lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -5077,6 +5852,14 @@ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -5092,41 +5875,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "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==" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -5170,6 +5918,34 @@ } } }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "requires": {} + }, + "marked": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz", + "integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==" + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -5212,6 +5988,16 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5266,6 +6052,19 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "p-defer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", @@ -5300,6 +6099,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -5310,15 +6114,6 @@ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" }, - "proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", - "optional": true, - "requires": { - "protobufjs": "^6.11.2" - } - }, "protobufjs": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", @@ -5353,27 +6148,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -5413,6 +6187,14 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, + "requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "requires": { + "lodash": "^4.17.21" + } + }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -5471,6 +6253,14 @@ "ws": ">=7.4.6" } }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } + }, "send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -5534,6 +6324,12 @@ "object-inspect": "^1.9.0" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -5579,23 +6375,41 @@ "ansi-regex": "^5.0.1" } }, + "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==" + }, "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "optional": true }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" + }, "teeny-request": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.0.tgz", - "integrity": "sha512-6KEYxXI4lQPSDkXzXpPmJPNmo7oqduFFbhOEHf8sfsLbXyCsb+umUjBtMGAKhaSToD8JNCtQutTRefu29K64JA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "optional": true, "requires": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" } }, "text-decoding": { @@ -5626,6 +6440,14 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "requires": { + "prelude-ls": "~1.1.2" + } + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5635,6 +6457,21 @@ "mime-types": "~2.1.24" } }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==" + }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -5651,9 +6488,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" }, "vary": { "version": "1.1.2", @@ -5694,6 +6531,11 @@ "webidl-conversions": "^3.0.0" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -5715,6 +6557,11 @@ "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", "requires": {} }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", From 127ca3fbf7549d6c0cdf12e89e4465de9dd2b7c9 Mon Sep 17 00:00:00 2001 From: Sergey Maskalik Date: Tue, 10 Jan 2023 14:04:57 -0800 Subject: [PATCH 0752/1699] Bug fix: Cloud Functions Runtime Config Hash Value (#5355) Bug fix: convert runtime configuration into a sorted array --- CHANGELOG.md | 1 + .../functions/prepareFunctionsUpload.ts | 17 ++++++- .../functions/prepareFunctionsUpload.spec.ts | 48 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/test/deploy/functions/prepareFunctionsUpload.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a9dca357275..828dbc7a0f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Fix bug where CLI was unable to deploy Firebase Functions in some monorepo setups (#5391) - Upgrade Storage Rules Runtime to v1.1.3 to support ternary operators (#5370) +- Fixes an issue where already deployed functions with the same remote configuration do not get skipped (#5354) diff --git a/src/deploy/functions/prepareFunctionsUpload.ts b/src/deploy/functions/prepareFunctionsUpload.ts index 7337fb8ac32..bb65f92c235 100644 --- a/src/deploy/functions/prepareFunctionsUpload.ts +++ b/src/deploy/functions/prepareFunctionsUpload.ts @@ -21,6 +21,8 @@ interface PackagedSourceInfo { hash: string; } +type SortedConfig = string | { key: string; value: SortedConfig }[]; + // TODO(inlined): move to a file that's not about uploading source code export async function getFunctionsConfig(projectId: string): Promise> { try { @@ -88,8 +90,11 @@ async function packageSource( }); } if (typeof runtimeConfig !== "undefined") { + // In order for hash to be consistent, configuration object tree must be sorted by key, only possible with arrays. + const runtimeConfigHashString = JSON.stringify(convertToSortedKeyValueArray(runtimeConfig)); + hashes.push(runtimeConfigHashString); + const runtimeConfigString = JSON.stringify(runtimeConfig, null, 2); - hashes.push(runtimeConfigString); archive.append(runtimeConfigString, { name: CONFIG_DEST_FILE, mode: 420 /* 0o644 */, @@ -125,3 +130,13 @@ export async function prepareFunctionsUpload( ): Promise { return packageSource(sourceDir, config, runtimeConfig); } + +export function convertToSortedKeyValueArray(config: any): SortedConfig { + if (typeof config !== "object" || config === null) return config; + + return Object.keys(config) + .sort() + .map((key) => { + return { key, value: convertToSortedKeyValueArray(config[key]) }; + }); +} diff --git a/src/test/deploy/functions/prepareFunctionsUpload.spec.ts b/src/test/deploy/functions/prepareFunctionsUpload.spec.ts new file mode 100644 index 00000000000..35d14600033 --- /dev/null +++ b/src/test/deploy/functions/prepareFunctionsUpload.spec.ts @@ -0,0 +1,48 @@ +import { expect } from "chai"; +import * as prepareFunctionsUpload from "../../../deploy/functions/prepareFunctionsUpload"; + +describe("prepareFunctionsUpload", () => { + describe("convertToSortedKeyValueArray", () => { + it("should deep sort the resulting array when an input config object is not sorted", () => { + const config = { + b: "b", + a: { + b: { + c: "c", + a: "a", + }, + a: "a", + }, + }; + const expected = [ + { + key: "a", + value: [ + { key: "a", value: "a" }, + { + key: "b", + value: [ + { + key: "a", + value: "a", + }, + { + key: "c", + value: "c", + }, + ], + }, + ], + }, + { key: "b", value: "b" }, + ]; + expect(prepareFunctionsUpload.convertToSortedKeyValueArray(config)).to.deep.equal(expected); + }); + it("should return null when config input is null", () => { + expect(prepareFunctionsUpload.convertToSortedKeyValueArray(null)).to.be.equal(null); + }); + it("should return an empty array when config input is an empty object", () => { + expect(prepareFunctionsUpload.convertToSortedKeyValueArray({})).to.deep.equal([]); + }); + }); +}); From a1287dd55b284c3463f3e216db86b0b46260e237 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jan 2023 15:28:30 -0800 Subject: [PATCH 0753/1699] Bump jsonwebtoken from 8.5.1 to 9.0.0 (#5410) * Bump jsonwebtoken from 8.5.1 to 9.0.0 Bumps [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) from 8.5.1 to 9.0.0. - [Release notes](https://github.com/auth0/node-jsonwebtoken/releases) - [Changelog](https://github.com/auth0/node-jsonwebtoken/blob/master/CHANGELOG.md) - [Commits](https://github.com/auth0/node-jsonwebtoken/compare/v8.5.1...v9.0.0) --- updated-dependencies: - dependency-name: jsonwebtoken dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Use fake secret for jwt.sign() and fix tests Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lisa Jian --- npm-shrinkwrap.json | 148 +++++++++++++++----- package.json | 2 +- src/emulator/auth/operations.ts | 34 +++-- src/test/emulators/auth/customToken.spec.ts | 10 +- 4 files changed, 138 insertions(+), 56 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c2c5d561fa4..8af2e9fe708 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -35,7 +35,7 @@ "google-auth-library": "^7.11.0", "inquirer": "^8.2.0", "js-yaml": "^3.13.1", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "leven": "^3.1.0", "libsodium-wrappers": "^0.7.10", "lodash": "^4.17.21", @@ -7324,6 +7324,34 @@ "@firebase/util": "1.7.3" } }, + "node_modules/firebase-admin/node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dev": true, + "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" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/firebase-admin/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 + }, "node_modules/firebase-functions": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.1.0.tgz", @@ -9542,24 +9570,18 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/ms": { @@ -9567,6 +9589,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -9822,22 +9858,26 @@ "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true }, "node_modules/lodash.isobject": { "version": "2.4.1", @@ -9855,7 +9895,8 @@ "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -9866,7 +9907,8 @@ "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true }, "node_modules/lodash.set": { "version": "4.3.2", @@ -21227,6 +21269,30 @@ "@firebase/app-types": "0.8.1", "@firebase/util": "1.7.3" } + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dev": true, + "requires": { + "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" + } + }, + "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 } } }, @@ -22911,26 +22977,28 @@ "dev": true }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "requires": { "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -23158,22 +23226,26 @@ "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true }, "lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true }, "lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true }, "lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true }, "lodash.isobject": { "version": "2.4.1", @@ -23191,7 +23263,8 @@ "lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true }, "lodash.merge": { "version": "4.6.2", @@ -23202,7 +23275,8 @@ "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true }, "lodash.set": { "version": "4.3.2", diff --git a/package.json b/package.json index d7f6e13667f..b779814c00f 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "google-auth-library": "^7.11.0", "inquirer": "^8.2.0", "js-yaml": "^3.13.1", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "leven": "^3.1.0", "libsodium-wrappers": "^0.7.10", "lodash": "^4.17.21", diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index 386a4269230..cde34a11853 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -669,7 +669,7 @@ function createSessionCookie( exp: expiresAt, iss: `https://session.firebase.google.com/${payload.aud}`, }, - "", + "fake-secret", { // Generate a unsigned (insecure) JWT. Admin SDKs should treat this like // a real token (if in emulator mode). This won't work in production. @@ -2391,18 +2391,26 @@ function generateJwt( }, }; - const jwtStr = signJwt(customPayloadFields, "", { - // Generate a unsigned (insecure) JWT. This is accepted by many other - // emulators (e.g. Cloud Firestore Emulator) but will not work in - // production of course. This removes the need to sign / verify tokens. - algorithm: "none", - expiresIn: expiresInSeconds, + const jwtStr = signJwt( + customPayloadFields, + // secretOrPrivateKey is required for jsonwebtoken v9, see + // https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v8-to-v9 + // Tokens generated by the auth emulator are intentionally insecure and are + // not meant to be used in production. Thus, a fake secret is used here. + "fake-secret", + { + // Generate a unsigned (insecure) JWT. This is accepted by many other + // emulators (e.g. Cloud Firestore Emulator) but will not work in + // production of course. This removes the need to sign / verify tokens. + algorithm: "none", + expiresIn: expiresInSeconds, - subject: user.localId, - // TODO: Should this point to an emulator URL? - issuer: `https://securetoken.google.com/${projectId}`, - audience: projectId, - }); + subject: user.localId, + // TODO: Should this point to an emulator URL? + issuer: `https://securetoken.google.com/${projectId}`, + audience: projectId, + } + ); return jwtStr; } @@ -3245,7 +3253,7 @@ function generateBlockingFunctionJwt( jwt.oauth_refresh_token = oauthTokens.oauthRefreshToken; } - const jwtStr = signJwt(jwt, "", { + const jwtStr = signJwt(jwt, "fake-secret", { algorithm: "none", }); diff --git a/src/test/emulators/auth/customToken.spec.ts b/src/test/emulators/auth/customToken.spec.ts index d3e4ebedd25..06a51a8e7a0 100644 --- a/src/test/emulators/auth/customToken.spec.ts +++ b/src/test/emulators/auth/customToken.spec.ts @@ -15,7 +15,7 @@ describeAuthEmulator("sign-in with custom token", ({ authApi }) => { it("should create new account from custom token (unsigned)", async () => { const uid = "someuid"; const claims = { abc: "def", ultimate: { answer: 42 } }; - const token = signJwt({ uid, claims }, "", { + const token = signJwt({ uid, claims }, "fake-secret", { algorithm: "none", expiresIn: 3600, @@ -149,7 +149,7 @@ describeAuthEmulator("sign-in with custom token", ({ authApi }) => { }); it("should error if custom token addresses the wrong audience", async () => { - const token = signJwt({ uid: "foo" }, "", { + const token = signJwt({ uid: "foo" }, "fake-secret", { algorithm: "none", expiresIn: 3600, @@ -173,7 +173,7 @@ describeAuthEmulator("sign-in with custom token", ({ authApi }) => { { /* no uid */ }, - "", + "fake-secret", { algorithm: "none", expiresIn: 3600, @@ -252,7 +252,7 @@ describeAuthEmulator("sign-in with custom token", ({ authApi }) => { const tenant = await registerTenant(authApi(), PROJECT_ID, { disableAuth: false }); const uid = "someuid"; const claims = { abc: "def", ultimate: { answer: 42 } }; - const token = signJwt({ uid, claims, tenant_id: "not-matching-tenant-id" }, "", { + const token = signJwt({ uid, claims, tenant_id: "not-matching-tenant-id" }, "fake-secret", { algorithm: "none", expiresIn: 3600, @@ -275,7 +275,7 @@ describeAuthEmulator("sign-in with custom token", ({ authApi }) => { const tenant = await registerTenant(authApi(), PROJECT_ID, { disableAuth: false }); const uid = "someuid"; const claims = { abc: "def", ultimate: { answer: 42 } }; - const token = signJwt({ uid, claims, tenant_id: tenant.tenantId }, "", { + const token = signJwt({ uid, claims, tenant_id: tenant.tenantId }, "fake-secret", { algorithm: "none", expiresIn: 3600, From 177c99dcd6ca5e43e3ae37fa389d7d260c76a5b2 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Fri, 13 Jan 2023 11:59:47 -0800 Subject: [PATCH 0754/1699] audit fixes, replace update-notifier with update-notifier-cjs (#5420) * npm audit fixes * update update-notifier * switch to cjs * better type -cjs --- npm-shrinkwrap.json | 1571 ++++++++++------------------ package.json | 2 +- src/bin/firebase.ts | 2 +- src/types/update-notifier-cjs.d.ts | 4 + 4 files changed, 547 insertions(+), 1032 deletions(-) create mode 100644 src/types/update-notifier-cjs.d.ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8af2e9fe708..c3cec3cdbb1 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -64,7 +64,7 @@ "triple-beam": "^1.3.0", "universal-analytics": "^0.5.3", "unzipper": "^0.10.10", - "update-notifier": "^5.1.0", + "update-notifier-cjs": "^5.1.6", "uuid": "^8.3.2", "winston": "^3.0.0", "winston-transport": "^4.4.0", @@ -2516,14 +2516,6 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/@sinonjs/commons": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", @@ -2625,17 +2617,6 @@ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3612,12 +3593,12 @@ } }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "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==", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -4253,20 +4234,42 @@ "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dependencies": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/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==", + "dependencies": { + "ee-first": "1.1.1" }, "engines": { "node": ">= 0.8" @@ -4475,9 +4478,9 @@ } }, "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { "node": ">= 0.8" } @@ -4596,42 +4599,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "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" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -4651,7 +4618,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -4948,14 +4914,6 @@ "node": ">=0.8" } }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dependencies": { - "mimic-response": "^1.0.0" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5213,16 +5171,35 @@ "optional": true }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, + "node_modules/content-disposition/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" + } + ] + }, "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -5241,9 +5218,9 @@ } }, "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -5495,17 +5472,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -5556,11 +5522,6 @@ "clone": "^1.0.2" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, "node_modules/degenerator": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz", @@ -5593,14 +5554,19 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "devOptional": true, "engines": { "node": ">= 0.6" } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/devtools-protocol": { "version": "0.0.1056733", @@ -5609,9 +5575,9 @@ "dev": true }, "node_modules/dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "dependencies": { "asap": "^2.0.0", @@ -5692,11 +5658,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "node_modules/duplexify": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", @@ -6841,7 +6802,7 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } @@ -6934,37 +6895,38 @@ } }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.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", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -6973,6 +6935,69 @@ "node": ">= 0.10.0" } }, + "node_modules/express/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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "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/express/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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/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" + } + ] + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7516,36 +7541,24 @@ } }, "node_modules/formidable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", "dev": true, "dependencies": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" }, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/formidable/node_modules/qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "engines": { "node": ">= 0.6" } @@ -7705,8 +7718,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -7821,7 +7833,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -7840,17 +7851,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/get-uri": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", @@ -8365,27 +8365,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "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" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -8472,7 +8451,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -8493,7 +8471,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8577,27 +8554,39 @@ "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "optional": true }, "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "node_modules/http-errors/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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/http-parser-js": { "version": "0.5.8", @@ -8942,9 +8931,9 @@ } }, "node_modules/ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "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==", "engines": { "node": ">= 0.10" } @@ -9353,9 +9342,9 @@ } }, "node_modules/jose": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", - "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", + "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", "dev": true, "dependencies": { "@panva/asn1.js": "^1.0.0" @@ -9419,11 +9408,6 @@ "bignumber.js": "^9.0.0" } }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, "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", @@ -9496,9 +9480,9 @@ } }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -9681,14 +9665,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dependencies": { - "json-buffer": "3.0.0" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -9706,17 +9682,6 @@ "colornames": "^1.1.1" } }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -10048,14 +10013,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -10203,15 +10160,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "optional": true }, - "node_modules/make-fetch-happen/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==", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", @@ -10413,14 +10361,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -10431,9 +10371,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10575,9 +10515,9 @@ "dev": true }, "node_modules/mocha": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", - "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", @@ -10593,9 +10533,9 @@ "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "3.0.4", + "minimatch": "4.2.1", "ms": "2.1.3", - "nanoid": "3.2.0", + "nanoid": "3.3.1", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -10679,6 +10619,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/mocha/node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -10776,9 +10728,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -10794,9 +10746,9 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } @@ -11129,14 +11081,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "engines": { - "node": ">=8" - } - }, "node_modules/npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", @@ -11466,7 +11410,6 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11657,14 +11600,6 @@ "node": ">=0.10.0" } }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "engines": { - "node": ">=6" - } - }, "node_modules/p-defer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", @@ -11804,28 +11739,6 @@ "node": ">=8" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12065,14 +11978,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "engines": { - "node": ">=4" - } - }, "node_modules/prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", @@ -12230,12 +12135,12 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "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==", "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { "node": ">= 0.10" @@ -12456,11 +12361,17 @@ } }, "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/queue-microtask": { @@ -12510,12 +12421,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -12734,17 +12645,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/registry-auth-token": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", - "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/registry-url": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", @@ -12813,9 +12713,9 @@ } }, "node_modules/request/node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "engines": { "node": ">=0.6" } @@ -12877,14 +12777,6 @@ "node": ">=4" } }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -13005,11 +12897,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" }, - "node_modules/router/node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -13102,28 +12989,36 @@ } }, "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "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": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/send/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==", + "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", @@ -13136,9 +13031,28 @@ } }, "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/send/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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/serialize-javascript": { "version": "6.0.0", @@ -13150,14 +13064,14 @@ } }, "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -13175,9 +13089,9 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shebang-command": { "version": "1.2.0", @@ -13256,7 +13170,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -13742,21 +13655,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/superagent/node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/superagent/node_modules/semver": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", @@ -14327,14 +14225,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14347,9 +14237,9 @@ } }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "engines": { "node": ">=0.6" } @@ -14762,33 +14652,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, "node_modules/update-notifier-cjs": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/update-notifier-cjs/-/update-notifier-cjs-5.1.6.tgz", @@ -14947,127 +14810,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/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==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "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/update-notifier/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/update-notifier/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==" - }, - "node_modules/update-notifier/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/update-notifier/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==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -15087,17 +14829,6 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", "integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g=" }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/url-template": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", @@ -17516,11 +17247,6 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, "@sinonjs/commons": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", @@ -17604,14 +17330,6 @@ } } }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "requires": { - "defer-to-connect": "^1.0.1" - } - }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -18456,12 +18174,12 @@ } }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "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==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { @@ -18965,20 +18683,37 @@ "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + } } }, "boxen": { @@ -19120,9 +18855,9 @@ "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "cacache": { "version": "16.1.3", @@ -19210,35 +18945,6 @@ } } }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "requires": { - "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" - }, - "dependencies": { - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } - } - }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -19255,7 +18961,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -19463,14 +19168,6 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -19683,11 +19380,18 @@ "optional": true }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "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==" + } } }, "content-type": { @@ -19705,9 +19409,9 @@ } }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", @@ -19899,14 +19603,6 @@ } } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -19948,11 +19644,6 @@ "clone": "^1.0.2" } }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, "degenerator": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz", @@ -19978,12 +19669,13 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "devOptional": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "devtools-protocol": { "version": "0.0.1056733", @@ -19992,9 +19684,9 @@ "dev": true }, "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "requires": { "asap": "^2.0.0", @@ -20065,11 +19757,6 @@ } } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "duplexify": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", @@ -20820,7 +20507,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "event-target-shim": { "version": "5.0.1", @@ -20891,40 +20578,80 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.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", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + }, + "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==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } } }, "extend": { @@ -21370,29 +21097,21 @@ } }, "formidable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", "dev": true, "requires": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" - }, - "dependencies": { - "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true - } + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" } }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fresh": { "version": "0.5.2", @@ -21511,8 +21230,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -21604,7 +21322,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -21617,14 +21334,6 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, "get-uri": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", @@ -22038,24 +21747,6 @@ } } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@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": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -22128,7 +21819,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -22142,8 +21832,7 @@ "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-unicode": { "version": "2.0.1", @@ -22208,24 +21897,30 @@ "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "optional": true }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" } } }, @@ -22481,9 +22176,9 @@ "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" }, "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + "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==" }, "is-arrayish": { "version": "0.2.1", @@ -22801,9 +22496,9 @@ } }, "jose": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", - "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", + "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", "dev": true, "requires": { "@panva/asn1.js": "^1.0.0" @@ -22849,11 +22544,6 @@ "bignumber.js": "^9.0.0" } }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, "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", @@ -22920,9 +22610,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonc-parser": { @@ -23068,14 +22758,6 @@ "safe-buffer": "^5.0.1" } }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "requires": { - "json-buffer": "3.0.0" - } - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -23090,14 +22772,6 @@ "colornames": "^1.1.1" } }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "requires": { - "package-json": "^6.3.0" - } - }, "lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -23387,11 +23061,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -23511,12 +23180,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "optional": true }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "optional": true - }, "socks-proxy-agent": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", @@ -23658,11 +23321,6 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -23670,9 +23328,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -23781,9 +23439,9 @@ "dev": true }, "mocha": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", - "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -23799,9 +23457,9 @@ "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "3.0.4", + "minimatch": "4.2.1", "ms": "2.1.3", - "nanoid": "3.2.0", + "nanoid": "3.3.1", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -23856,6 +23514,15 @@ "argparse": "^2.0.1" } }, + "minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -23930,9 +23597,9 @@ "optional": true }, "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true }, "natural-compare": { @@ -23942,9 +23609,9 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "netmask": { "version": "2.0.2", @@ -24186,11 +23853,6 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" - }, "npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", @@ -24459,8 +24121,7 @@ "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "on-finished": { "version": "2.3.0", @@ -24602,11 +24263,6 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, "p-defer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", @@ -24709,24 +24365,6 @@ "release-zalgo": "^1.0.0" } }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -24902,11 +24540,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, "prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", @@ -25053,12 +24686,12 @@ } }, "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "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==", "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" } }, "proxy-agent": { @@ -25211,9 +24844,12 @@ } }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } }, "queue-microtask": { "version": "1.2.3", @@ -25242,12 +24878,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -25418,14 +25054,6 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, - "registry-auth-token": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", - "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", - "requires": { - "rc": "^1.2.8" - } - }, "registry-url": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", @@ -25481,9 +25109,9 @@ } }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "uuid": { "version": "3.4.0", @@ -25525,14 +25153,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -25618,11 +25238,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" } } }, @@ -25696,34 +25311,52 @@ } }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "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": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" } } }, @@ -25737,14 +25370,14 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" } }, "set-blocking": { @@ -25759,9 +25392,9 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "shebang-command": { "version": "1.2.0", @@ -25834,7 +25467,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -26216,15 +25848,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, "semver": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", @@ -26674,11 +26297,6 @@ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -26688,9 +26306,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tough-cookie": { "version": "2.5.0", @@ -26983,105 +26601,6 @@ "picocolors": "^1.0.0" } }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "requires": { - "color-name": "~1.1.4" - } - }, - "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==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "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==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - } - } - }, "update-notifier-cjs": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/update-notifier-cjs/-/update-notifier-cjs-5.1.6.tgz", @@ -27210,14 +26729,6 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", "integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g=" }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "requires": { - "prepend-http": "^2.0.0" - } - }, "url-template": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", diff --git a/package.json b/package.json index b779814c00f..ddf2a0bb09c 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "triple-beam": "^1.3.0", "universal-analytics": "^0.5.3", "unzipper": "^0.10.10", - "update-notifier": "^5.1.0", + "update-notifier-cjs": "^5.1.6", "uuid": "^8.3.2", "winston": "^3.0.0", "winston-transport": "^4.4.0", diff --git a/src/bin/firebase.ts b/src/bin/firebase.ts index 155b6d80b43..9c8c136a8f8 100755 --- a/src/bin/firebase.ts +++ b/src/bin/firebase.ts @@ -11,7 +11,7 @@ if (!semver.satisfies(nodeVersion, pkg.engines.node)) { process.exit(1); } -import * as updateNotifierPkg from "update-notifier"; +import * as updateNotifierPkg from "update-notifier-cjs"; import * as clc from "colorette"; import * as TerminalRenderer from "marked-terminal"; const updateNotifier = updateNotifierPkg({ pkg }); diff --git a/src/types/update-notifier-cjs.d.ts b/src/types/update-notifier-cjs.d.ts new file mode 100644 index 00000000000..4f083b15f6b --- /dev/null +++ b/src/types/update-notifier-cjs.d.ts @@ -0,0 +1,4 @@ +declare module "update-notifier-cjs" { + import m from "update-notifier"; + export = m; +} From 17562130995ee5ca4f995300a17218dfdafdc394 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 13 Jan 2023 14:16:12 -0800 Subject: [PATCH 0755/1699] Add support for system params when deploying extensions (#5414) * Add support for configuring system params when deploying extensions * Allow . and / in env keys * Make partitionRecord more flexible --- src/commands/ext-configure.ts | 2 +- src/commands/ext-install.ts | 2 +- src/deploy/extensions/planner.ts | 26 +++++++----- src/deploy/extensions/tasks.ts | 6 +++ src/extensions/extensionsApi.ts | 25 +++++++++-- src/extensions/paramHelper.ts | 5 +++ src/extensions/types.ts | 6 +-- src/functional.ts | 24 +++++++++-- src/functions/env.ts | 2 +- src/test/deploy/extensions/planner.spec.ts | 4 ++ .../emulators/extensions/validation.spec.ts | 2 + src/test/emulators/extensionsEmulator.spec.ts | 2 + .../emulators/functionsEmulatorShared.spec.ts | 2 + .../extensions/billingMigrationHelper.spec.ts | 3 ++ src/test/extensions/change-log.spec.ts | 1 + .../extensions/displayExtensionInfo.spec.ts | 1 + .../extensions/emulator/optionsHelper.spec.ts | 1 + src/test/extensions/export.spec.ts | 4 ++ src/test/extensions/extensionsApi.spec.ts | 41 +++++++++++++++++-- src/test/extensions/extensionsHelper.spec.ts | 6 +++ src/test/extensions/manifest.spec.ts | 14 +++++++ src/test/extensions/paramHelper.spec.ts | 3 ++ src/test/extensions/secretUtils.spec.ts | 2 + src/test/extensions/updateHelper.spec.ts | 1 + src/test/extensions/warnings.spec.ts | 2 + src/test/functional.spec.ts | 17 ++++++++ 26 files changed, 178 insertions(+), 26 deletions(-) diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index b94e6e59dd7..59acf297db8 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -78,7 +78,7 @@ export const command = new Command("ext:configure ") }); const [immutableParams, tbdParams] = partition( - spec.params, + spec.params.concat(spec.systemParams ?? []), (param) => param.immutable ?? false ); infoImmutableParams(immutableParams, oldParamValues); diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 653ab1cd61b..c0cbc298d2b 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -212,7 +212,7 @@ async function installToManifest(options: InstallExtensionOptions): Promise; + systemParams: Record; allowedEventTypes?: string[]; eventarcChannel?: string; etag?: string; @@ -107,6 +109,7 @@ export async function have(projectId: string): Promise const dep: DeploymentInstanceSpec = { instanceId: i.name.split("/").pop()!, params: i.config.params, + systemParams: i.config.systemParams ?? {}, allowedEventTypes: i.config.allowedEventTypes, eventarcChannel: i.config.eventarcChannel, etag: i.etag, @@ -145,7 +148,7 @@ export async function want(args: { try { const instanceId = e[0]; - const params = readInstanceParam({ + const rawParams = readInstanceParam({ projectDir: args.projectDir, instanceId, projectId: args.projectId, @@ -154,26 +157,28 @@ export async function want(args: { checkLocal: args.emulatorMode, }); const autoPopulatedParams = await getFirebaseProjectParams(args.projectId, args.emulatorMode); - const subbedParams = substituteParams(params, autoPopulatedParams); + const subbedParams = substituteParams(rawParams, autoPopulatedParams); + const [systemParams, params] = partitionRecord(subbedParams, isSystemParam); // ALLOWED_EVENT_TYPES can be undefined (user input not provided) or empty string (no events selected). // If empty string, we want to pass an empty array. If it's undefined we want to pass through undefined. const allowedEventTypes = - subbedParams.ALLOWED_EVENT_TYPES !== undefined - ? subbedParams.ALLOWED_EVENT_TYPES.split(",").filter((e) => e !== "") + params.ALLOWED_EVENT_TYPES !== undefined + ? params.ALLOWED_EVENT_TYPES.split(",").filter((e) => e !== "") : undefined; - const eventarcChannel = subbedParams.EVENTARC_CHANNEL; + const eventarcChannel = params.EVENTARC_CHANNEL; // Remove special params that are stored in the .env file but aren't actually params specified by the publisher. // Currently, only environment variables needed for Events features are considered special params stored in .env files. - delete subbedParams["EVENTARC_CHANNEL"]; - delete subbedParams["ALLOWED_EVENT_TYPES"]; + delete params["EVENTARC_CHANNEL"]; + delete params["ALLOWED_EVENT_TYPES"]; if (isLocalPath(e[1])) { instanceSpecs.push({ instanceId, localPath: e[1], - params: subbedParams, + params, + systemParams, allowedEventTypes: allowedEventTypes, eventarcChannel: eventarcChannel, }); @@ -183,7 +188,8 @@ export async function want(args: { instanceSpecs.push({ instanceId, ref, - params: subbedParams, + params, + systemParams, allowedEventTypes: allowedEventTypes, eventarcChannel: eventarcChannel, }); diff --git a/src/deploy/extensions/tasks.ts b/src/deploy/extensions/tasks.ts index 844190f1619..a34bd492dc1 100644 --- a/src/deploy/extensions/tasks.ts +++ b/src/deploy/extensions/tasks.ts @@ -49,6 +49,7 @@ export function createExtensionInstanceTask( projectId, instanceId: instanceSpec.instanceId, params: instanceSpec.params, + systemParams: instanceSpec.systemParams, extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref), allowedEventTypes: instanceSpec.allowedEventTypes, eventarcChannel: instanceSpec.eventarcChannel, @@ -60,6 +61,7 @@ export function createExtensionInstanceTask( projectId, instanceId: instanceSpec.instanceId, params: instanceSpec.params, + systemParams: instanceSpec.systemParams, extensionSource, allowedEventTypes: instanceSpec.allowedEventTypes, eventarcChannel: instanceSpec.eventarcChannel, @@ -92,6 +94,7 @@ export function updateExtensionInstanceTask( instanceId: instanceSpec.instanceId, extRef: refs.toExtensionVersionRef(instanceSpec.ref!), params: instanceSpec.params, + systemParams: instanceSpec.systemParams, canEmitEvents: !!instanceSpec.allowedEventTypes, allowedEventTypes: instanceSpec.allowedEventTypes, eventarcChannel: instanceSpec.eventarcChannel, @@ -104,6 +107,8 @@ export function updateExtensionInstanceTask( instanceId: instanceSpec.instanceId, extensionSource, validateOnly, + params: instanceSpec.params, + systemParams: instanceSpec.systemParams, canEmitEvents: !!instanceSpec.allowedEventTypes, allowedEventTypes: instanceSpec.allowedEventTypes, eventarcChannel: instanceSpec.eventarcChannel, @@ -134,6 +139,7 @@ export function configureExtensionInstanceTask( projectId, instanceId: instanceSpec.instanceId, params: instanceSpec.params, + systemParams: instanceSpec.systemParams, canEmitEvents: !!instanceSpec.allowedEventTypes, allowedEventTypes: instanceSpec.allowedEventTypes, eventarcChannel: instanceSpec.eventarcChannel, diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index 2a99867c7f2..9c8f224e8dd 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -73,13 +73,15 @@ export async function createInstance(args: { instanceId: string; extensionSource?: ExtensionSource; extensionVersionRef?: string; - params: { [key: string]: string }; + params: Record; + systemParams?: Record; allowedEventTypes?: string[]; eventarcChannel?: string; validateOnly?: boolean; }): Promise { const config: any = { params: args.params, + systemParams: args.systemParams ?? {}, allowedEventTypes: args.allowedEventTypes, eventarcChannel: args.eventarcChannel, }; @@ -188,7 +190,8 @@ export async function listInstances(projectId: string): Promise; + systemParams?: Record; canEmitEvents: boolean; allowedEventTypes?: string[]; eventarcChannel?: string; @@ -215,6 +218,10 @@ export async function configureInstance(args: { reqBody.data.config.eventarcChannel = args.eventarcChannel; } reqBody.updateMask += ",config.allowed_event_types,config.eventarc_channel"; + if (args.systemParams) { + reqBody.data.config.systemParams = args.systemParams; + reqBody.updateMask += ",config.system_params"; + } return patchInstance(reqBody); } @@ -233,7 +240,8 @@ export async function updateInstance(args: { projectId: string; instanceId: string; extensionSource: ExtensionSource; - params?: { [option: string]: string }; + params?: Record; + systemParams?: Record; canEmitEvents: boolean; allowedEventTypes?: string[]; eventarcChannel?: string; @@ -249,6 +257,10 @@ export async function updateInstance(args: { body.config.params = args.params; updateMask += ",config.params"; } + if (args.systemParams) { + body.config.systemParams = args.systemParams; + updateMask += ",config.system_params"; + } if (args.canEmitEvents) { if (args.allowedEventTypes === undefined || args.eventarcChannel === undefined) { throw new FirebaseError( @@ -283,7 +295,8 @@ export async function updateInstanceFromRegistry(args: { projectId: string; instanceId: string; extRef: string; - params?: { [option: string]: string }; + params?: Record; + systemParams?: Record; canEmitEvents: boolean; allowedEventTypes?: string[]; eventarcChannel?: string; @@ -301,6 +314,10 @@ export async function updateInstanceFromRegistry(args: { body.config.params = args.params; updateMask += ",config.params"; } + if (args.systemParams) { + body.config.systemParams = args.systemParams; + updateMask += ",config.system_params"; + } if (args.canEmitEvents) { if (args.allowedEventTypes === undefined || args.eventarcChannel === undefined) { throw new FirebaseError( diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 0e4a92aea3d..4ab26447699 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -265,3 +265,8 @@ export function readEnvFile(envPath: string): Record { } return result.envs; } + +export function isSystemParam(paramName: string): boolean { + const regex = /^firebaseextensions\.[a-zA-Z0-9\.]*\//; + return regex.test(paramName); +} diff --git a/src/extensions/types.ts b/src/extensions/types.ts index 2138e1643eb..a0de32e54c3 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -63,9 +63,8 @@ export interface ExtensionConfig { name: string; createTime: string; source: ExtensionSource; - params: { - [key: string]: any; - }; + params: Record; + systemParams: Record; populatedPostinstallContent?: string; extensionRef?: string; extensionVersion?: string; @@ -100,6 +99,7 @@ export interface ExtensionSpec { releaseNotesUrl?: string; sourceUrl?: string; params: Param[]; + systemParams: Param[]; preinstallContent?: string; postinstallContent?: string; readmeContent?: string; diff --git a/src/functional.ts b/src/functional.ts index 4746e130ab4..b6c638816cd 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -97,20 +97,38 @@ export function assertExhaustive(val: never): never { } /** - * Utility to partition an array into two based on callbackFn's truthiness for each element. + * Utility to partition an array into two based on predicate's truthiness for each element. * Returns a Array containing two Array. The first array contains all elements that returned true, * the second contains all elements that returned false. */ -export function partition(arr: T[], callbackFn: (elem: T) => boolean): [T[], T[]] { +export function partition(arr: T[], predicate: (elem: T) => boolean): [T[], T[]] { return arr.reduce<[T[], T[]]>( (acc, elem) => { - acc[callbackFn(elem) ? 0 : 1].push(elem); + acc[predicate(elem) ? 0 : 1].push(elem); return acc; }, [[], []] ); } +/** + * Utility to partition a Record into two based on predicate's truthiness for each element. + * Returns a Array containing two Record. The first array contains all elements that returned true, + * the second contains all elements that returned false. + */ +export function partitionRecord( + rec: Record, + predicate: (key: string, val: T) => boolean +): [Record, Record] { + return Object.entries(rec).reduce<[Record, Record]>( + (acc, [key, val]) => { + acc[predicate(key, val) ? 0 : 1][key] = val; + return acc; + }, + [{}, {}] + ); +} + /** * Create a map of transformed values for all keys. */ diff --git a/src/functions/env.ts b/src/functions/env.ts index 2bd72806e2c..b640aff443e 100644 --- a/src/functions/env.ts +++ b/src/functions/env.ts @@ -45,7 +45,7 @@ const RESERVED_KEYS = [ const LINE_RE = new RegExp( "^" + // begin line "\\s*" + // leading whitespaces - "(\\w+)" + // key + "([\\w./]+)" + // key "\\s*=[\\f\\t\\v]*" + // separator (=) "(" + // begin optional value "\\s*'(?:\\\\'|[^'])*'|" + // single quoted or diff --git a/src/test/deploy/extensions/planner.spec.ts b/src/test/deploy/extensions/planner.spec.ts index 1d4aef34917..c5ddffc6ca5 100644 --- a/src/test/deploy/extensions/planner.spec.ts +++ b/src/test/deploy/extensions/planner.spec.ts @@ -18,6 +18,7 @@ function extensionVersion(version?: string): any { resources: [], sourceUrl: "https://google.com", params: [], + systemParam: [], }, }; } @@ -105,6 +106,7 @@ describe("Extensions Deployment Planner", () => { name: "", sourceUrl: "", params: [], + systemParams: [], }; const INSTANCE_WITH_EVENTS: ExtensionInstance = { @@ -116,6 +118,7 @@ describe("Extensions Deployment Planner", () => { etag: "123456", config: { params: {}, + systemParams: {}, extensionRef: "firebase/image-resizer", extensionVersion: "0.1.0", name: "projects/my-test-proj/instances/image-resizer/configurations/95355951-397f-4821-a5c2-9c9788b2cc63", @@ -135,6 +138,7 @@ describe("Extensions Deployment Planner", () => { const INSTANCE_SPEC_WITH_EVENTS: planner.DeploymentInstanceSpec = { instanceId: "image-resizer", params: {}, + systemParams: {}, allowedEventTypes: ["google.firebase.custom-event-occurred"], eventarcChannel: "projects/my-test-proj/locations/us-central1/channels/firebase", etag: "123456", diff --git a/src/test/emulators/extensions/validation.spec.ts b/src/test/emulators/extensions/validation.spec.ts index 8be9e7eebd4..83ec72a1aad 100644 --- a/src/test/emulators/extensions/validation.spec.ts +++ b/src/test/emulators/extensions/validation.spec.ts @@ -30,6 +30,7 @@ function fakeInstanceSpecWithAPI(instanceId: string, apiName: string): Deploymen return { instanceId, params: {}, + systemParams: {}, ref: { publisherId: "test", extensionId: "test", @@ -47,6 +48,7 @@ function fakeInstanceSpecWithAPI(instanceId: string, apiName: string): Deploymen sourceUrl: "test.com", resources: [], params: [], + systemParams: [], apis: [{ apiName, reason: "because" }], }, }, diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts index baa0c6c5a05..93093146ea8 100644 --- a/src/test/emulators/extensionsEmulator.spec.ts +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -45,6 +45,7 @@ const TEST_EXTENSION_VERSION: ExtensionVersion = { }, ], params: [], + systemParams: [], version: "0.1.18", sourceUrl: "https://fake.test", }, @@ -80,6 +81,7 @@ describe("Extensions Emulator", () => { "google.firebase.image-resize-started,google.firebase.image-resize-completed", EVENTARC_CHANNEL: "projects/test-project/locations/us-central1/channels/firebase", }, + systemParams: {}, allowedEventTypes: [ "google.firebase.image-resize-started", "google.firebase.image-resize-completed", diff --git a/src/test/emulators/functionsEmulatorShared.spec.ts b/src/test/emulators/functionsEmulatorShared.spec.ts index 67321d1f18d..de84d4dfc3d 100644 --- a/src/test/emulators/functionsEmulatorShared.spec.ts +++ b/src/test/emulators/functionsEmulatorShared.spec.ts @@ -158,6 +158,7 @@ describe("FunctionsEmulatorShared", () => { resources: [], sourceUrl: "test.com", params: [], + systemParams: [], postinstallContent: "Should subsitute ${param:KEY}", }; const testSubbedSpec: ExtensionSpec = { @@ -166,6 +167,7 @@ describe("FunctionsEmulatorShared", () => { resources: [], sourceUrl: "test.com", params: [], + systemParams: [], postinstallContent: "Should subsitute value", }; const testExtension: Extension = { diff --git a/src/test/extensions/billingMigrationHelper.spec.ts b/src/test/extensions/billingMigrationHelper.spec.ts index 8cf215843bf..fb487fa1378 100644 --- a/src/test/extensions/billingMigrationHelper.spec.ts +++ b/src/test/extensions/billingMigrationHelper.spec.ts @@ -27,6 +27,7 @@ const NO_RUNTIME_SPEC: ExtensionSpec = { billingRequired: true, sourceUrl: "test.com", params: [], + systemParams: [], }; const NODE8_SPEC: ExtensionSpec = { @@ -49,6 +50,7 @@ const NODE8_SPEC: ExtensionSpec = { billingRequired: true, sourceUrl: "test.com", params: [], + systemParams: [], }; const NODE10_SPEC: ExtensionSpec = { @@ -71,6 +73,7 @@ const NODE10_SPEC: ExtensionSpec = { billingRequired: true, sourceUrl: "test.com", params: [], + systemParams: [], }; describe("billingMigrationHelper", () => { diff --git a/src/test/extensions/change-log.spec.ts b/src/test/extensions/change-log.spec.ts index 366ac3e2a51..bbce32ebfa1 100644 --- a/src/test/extensions/change-log.spec.ts +++ b/src/test/extensions/change-log.spec.ts @@ -20,6 +20,7 @@ function testExtensionVersion(version: string, releaseNotes?: string): Extension version, resources: [], params: [], + systemParams: [], sourceUrl: "https://google.com", }, }; diff --git a/src/test/extensions/displayExtensionInfo.spec.ts b/src/test/extensions/displayExtensionInfo.spec.ts index 9de305461bc..8057b149dba 100644 --- a/src/test/extensions/displayExtensionInfo.spec.ts +++ b/src/test/extensions/displayExtensionInfo.spec.ts @@ -29,6 +29,7 @@ const SPEC: ExtensionSpec = { billingRequired: true, sourceUrl: "test.com", params: [], + systemParams: [], }; const TASK_FUNCTION_RESOURCE: Resource = { diff --git a/src/test/extensions/emulator/optionsHelper.spec.ts b/src/test/extensions/emulator/optionsHelper.spec.ts index 8eaed9b6e1c..9565ba36222 100644 --- a/src/test/extensions/emulator/optionsHelper.spec.ts +++ b/src/test/extensions/emulator/optionsHelper.spec.ts @@ -28,6 +28,7 @@ describe("optionsHelper", () => { resources: [], sourceUrl: "https://my.stuff.com", params: [], + systemParams: [], }; readEnvFileStub = sinon.stub(paramHelper, "readEnvFile"); }); diff --git a/src/test/extensions/export.spec.ts b/src/test/extensions/export.spec.ts index 8e234ad7415..1bbb43f531e 100644 --- a/src/test/extensions/export.spec.ts +++ b/src/test/extensions/export.spec.ts @@ -52,11 +52,13 @@ describe("ext:export helpers", () => { const testSpec = { instanceId: "my-instance", params: t.in, + systemParams: {}, }; expect(parameterizeProject(TEST_PROJECT_ID, TEST_PROJECT_NUMBER, testSpec)).to.deep.equal({ instanceId: testSpec.instanceId, params: t.expected, + systemParams: {}, }); }); } @@ -80,6 +82,7 @@ describe("ext:export helpers", () => { const testSpec: DeploymentInstanceSpec = { instanceId: "my-instance", params: t.params, + systemParams: {}, extensionVersion: { name: "test", ref: "test/test@0.1.0", @@ -102,6 +105,7 @@ describe("ext:export helpers", () => { label: "blah", }, ], + systemParams: [], }, }, }; diff --git a/src/test/extensions/extensionsApi.spec.ts b/src/test/extensions/extensionsApi.spec.ts index db56437d31d..c2a623b1479 100644 --- a/src/test/extensions/extensionsApi.spec.ts +++ b/src/test/extensions/extensionsApi.spec.ts @@ -233,9 +233,18 @@ describe("extensions", () => { name: "sources/blah", packageUri: "https://test.fake/pacakge.zip", hash: "abc123", - spec: { name: "", version: "0.1.0", sourceUrl: "", roles: [], resources: [], params: [] }, + spec: { + name: "", + version: "0.1.0", + sourceUrl: "", + roles: [], + resources: [], + params: [], + systemParams: [], + }, }, params: {}, + systemParams: {}, }); expect(nock.isDone()).to.be.true; }); @@ -294,6 +303,7 @@ describe("extensions", () => { roles: [], resources: [], params: [], + systemParams: [], }, }, params: {}, @@ -409,6 +419,7 @@ describe("extensions", () => { version: "0.1.0", resources: [], params: [], + systemParams: [], sourceUrl: "www.google.com/pack.zip", }, }; @@ -416,7 +427,7 @@ describe("extensions", () => { nock.cleanAll(); }); - it("should include config.param in updateMask is params are changed", async () => { + it("should include config.params in updateMask is params are changed", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) .query({ @@ -440,7 +451,7 @@ describe("extensions", () => { expect(nock.isDone()).to.be.true; }); - it("should not include config.param in updateMask is params aren't changed", async () => { + it("should not include config.params or config.system_params in updateMask is params aren't changed", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) .query({ @@ -459,6 +470,30 @@ describe("extensions", () => { expect(nock.isDone()).to.be.true; }); + it("should include config.system_params in updateMask if system_params are changed", async () => { + nock(api.extensionsOrigin) + .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) + .query({ + updateMask: + "config.source.name,config.system_params,config.allowed_event_types,config.eventarc_channel", + validateOnly: "false", + }) + .reply(200, { name: "operations/abc123" }); + nock(api.extensionsOrigin).get(`/${VERSION}/operations/abc123`).reply(200, { done: true }); + + await extensionsApi.updateInstance({ + projectId: PROJECT_ID, + instanceId: INSTANCE_ID, + extensionSource: testSource, + systemParams: { + MY_PARAM: "value", + }, + canEmitEvents: false, + }); + + expect(nock.isDone()).to.be.true; + }); + it("should include config.allowed_event_types and config.eventarc_Channel in updateMask if events config is provided", async () => { nock(api.extensionsOrigin) .patch(`/${VERSION}/projects/${PROJECT_ID}/instances/${INSTANCE_ID}`) diff --git a/src/test/extensions/extensionsHelper.spec.ts b/src/test/extensions/extensionsHelper.spec.ts index 43ac8049703..b63a2ef15e8 100644 --- a/src/test/extensions/extensionsHelper.spec.ts +++ b/src/test/extensions/extensionsHelper.spec.ts @@ -32,6 +32,7 @@ const EXT_SPEC_1: ExtensionSpec = { ], sourceUrl: "www.google.com/cool-things-here", params: [], + systemParams: [], }; const EXT_SPEC_2: ExtensionSpec = { name: "cool-things", @@ -44,6 +45,7 @@ const EXT_SPEC_2: ExtensionSpec = { ], sourceUrl: "www.google.com/cool-things-here", params: [], + systemParams: [], }; const TEST_EXT_VERSION_1: ExtensionVersion = { name: "publishers/test-pub/extensions/ext-one/versions/0.0.1-rc.0", @@ -512,6 +514,7 @@ describe("extensionsHelper", () => { specVersion: "v1beta", resources: [], params: [], + systemParams: [], sourceUrl: "https://test-source.fake", license: "apache-2.0", }; @@ -527,6 +530,7 @@ describe("extensionsHelper", () => { specVersion: "v1beta", resources: [], params: [], + systemParams: [], sourceUrl: "https://test-source.fake", }; @@ -541,6 +545,7 @@ describe("extensionsHelper", () => { specVersion: "v1beta", resources: [], params: [], + systemParams: [], sourceUrl: "https://test-source.fake", license: "invalid-license", }; @@ -807,6 +812,7 @@ describe("extensionsHelper", () => { sourceUrl: testUrl, resources: [], params: [], + systemParams: [], }, }; const testArchivedFiles: ArchiveResult = { diff --git a/src/test/extensions/manifest.spec.ts b/src/test/extensions/manifest.spec.ts index 8857fc5e286..2bdbfd65732 100644 --- a/src/test/extensions/manifest.spec.ts +++ b/src/test/extensions/manifest.spec.ts @@ -175,6 +175,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, { @@ -202,6 +203,7 @@ describe("manifest", () => { type: ParamType.SECRET, }, ], + systemParams: [], }, }, ], @@ -258,6 +260,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, { @@ -285,6 +288,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, ], @@ -354,6 +358,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, { @@ -394,6 +399,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, ], @@ -459,6 +465,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, { @@ -486,6 +493,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, ], @@ -545,6 +553,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, ], @@ -610,6 +619,7 @@ describe("manifest", () => { type: ParamType.SECRET, }, ], + systemParams: [], }, }, { @@ -640,6 +650,7 @@ describe("manifest", () => { type: ParamType.SECRET, }, ], + systemParams: [], }, }, ], @@ -691,6 +702,7 @@ describe("manifest", () => { type: ParamType.SECRET, }, ], + systemParams: [], }, }, ], @@ -737,6 +749,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, ], @@ -784,6 +797,7 @@ describe("manifest", () => { type: ParamType.STRING, }, ], + systemParams: [], }, }, ], diff --git a/src/test/extensions/paramHelper.spec.ts b/src/test/extensions/paramHelper.spec.ts index 0539647c410..749567abbc3 100644 --- a/src/test/extensions/paramHelper.spec.ts +++ b/src/test/extensions/paramHelper.spec.ts @@ -72,6 +72,7 @@ const SPEC = { resources: [], sourceUrl: "test.com", params: TEST_PARAMS, + systemParams: [], }; describe("paramHelper", () => { @@ -305,12 +306,14 @@ describe("paramHelper", () => { roles: [], resources: [], params: [...TEST_PARAMS], + systemParams: [], sourceUrl: "", }, }, name: "test", createTime: "now", params, + systemParams: {}, }, name: "test", createTime: "now", diff --git a/src/test/extensions/secretUtils.spec.ts b/src/test/extensions/secretUtils.spec.ts index 739824353c2..b9d408716e1 100644 --- a/src/test/extensions/secretUtils.spec.ts +++ b/src/test/extensions/secretUtils.spec.ts @@ -43,12 +43,14 @@ const TEST_INSTANCE: ExtensionInstance = { type: ParamType.SECRET, }, ], + systemParams: [], }, }, params: { SECRET1: "projects/test-project/secrets/secret1/versions/1", SECRET2: "projects/test-project/secrets/secret2/versions/1", }, + systemParams: {}, }, }; diff --git a/src/test/extensions/updateHelper.spec.ts b/src/test/extensions/updateHelper.spec.ts index badcf7ebccc..cbc852bc612 100644 --- a/src/test/extensions/updateHelper.spec.ts +++ b/src/test/extensions/updateHelper.spec.ts @@ -33,6 +33,7 @@ const SPEC: ExtensionSpec = { billingRequired: true, sourceUrl: "test.com", params: [], + systemParams: [], }; const SOURCE = { diff --git a/src/test/extensions/warnings.spec.ts b/src/test/extensions/warnings.spec.ts index 58cc02694dd..d114d073736 100644 --- a/src/test/extensions/warnings.spec.ts +++ b/src/test/extensions/warnings.spec.ts @@ -23,6 +23,7 @@ const testExtensionVersion: ExtensionVersion = { version: "0.1.0", resources: [], params: [], + systemParams: [], sourceUrl: "github.com/test/meout", }, }; @@ -50,6 +51,7 @@ const testInstanceSpec = ( version: "0.1.0", }, params: {}, + systemParams: {}, extensionVersion: testExtensionVersion, extension: testExtension(publisherId, launchStage), }; diff --git a/src/test/functional.spec.ts b/src/test/functional.spec.ts index 63d12f02327..f9280614bbc 100644 --- a/src/test/functional.spec.ts +++ b/src/test/functional.spec.ts @@ -143,4 +143,21 @@ describe("functional", () => { expect(f.partition([], (s: string) => s.startsWith("T"))).to.deep.equal([[], []]); }); }); + + describe("partitionRecord", () => { + it("should split a record into true and false", () => { + const rec = { T1: 1, F1: 2, T2: 3, F2: 4 }; + expect(f.partitionRecord(rec, (s: string) => s.startsWith("T"))).to.deep.equal([ + { T1: 1, T2: 3 }, + { F1: 2, F2: 4 }, + ]); + }); + + it("can handle an empty record", () => { + expect(f.partitionRecord({}, (s: string) => s.startsWith("T"))).to.deep.equal([ + {}, + {}, + ]); + }); + }); }); From c52ae18a9709f98b803b3e6cf7ec04dba279d1b8 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 18 Jan 2023 10:25:34 -0800 Subject: [PATCH 0756/1699] Added status header for query upload requests (#5425) --- .../conformance/firebase.endpoints.test.ts | 100 +++++++++++++++++- src/emulator/storage/apis/firebase.ts | 1 + src/emulator/storage/server.ts | 2 +- src/emulator/storage/upload.ts | 6 +- 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/scripts/storage-emulator-integration/conformance/firebase.endpoints.test.ts b/scripts/storage-emulator-integration/conformance/firebase.endpoints.test.ts index dcd7e246411..06e46e650e5 100644 --- a/scripts/storage-emulator-integration/conformance/firebase.endpoints.test.ts +++ b/scripts/storage-emulator-integration/conformance/firebase.endpoints.test.ts @@ -17,6 +17,9 @@ import { const TEST_FILE_NAME = "testing/storage_ref/testFile"; const ENCODED_TEST_FILE_NAME = "testing%2Fstorage_ref%2FtestFile"; +// headers +const uploadStatusHeader = "x-goog-upload-status"; + // TODO(b/242314185): add more coverage. describe("Firebase Storage endpoint conformance tests", () => { // Temp directory to store generated files. @@ -179,7 +182,7 @@ describe("Firebase Storage endpoint conformance tests", () => { "X-Goog-Upload-Command": "finalize", }) .expect(200) - .then((res) => res.header["x-goog-upload-status"]); + .then((res) => res.header[uploadStatusHeader]); expect(uploadStatus).to.equal("final"); @@ -212,7 +215,7 @@ describe("Firebase Storage endpoint conformance tests", () => { }) .send({}) .expect(200) - .then((res) => res.header["x-goog-upload-status"]); + .then((res) => res.header[uploadStatusHeader]); expect(finalizeStatus).to.equal("final"); }); @@ -236,7 +239,7 @@ describe("Firebase Storage endpoint conformance tests", () => { "X-Goog-Upload-Offset": 0, }) .expect(403) - .then((res) => res.header["x-goog-upload-status"]); + .then((res) => res.header[uploadStatusHeader]); expect(uploadStatus).to.equal("final"); }); @@ -266,7 +269,7 @@ describe("Firebase Storage endpoint conformance tests", () => { "X-Goog-Upload-Command": "finalize", }) .expect(403) - .then((res) => res.header["x-goog-upload-status"]); + .then((res) => res.header[uploadStatusHeader]); expect(uploadStatus).to.equal("final"); }); @@ -360,7 +363,7 @@ describe("Firebase Storage endpoint conformance tests", () => { "X-Goog-Upload-Command": "finalize", }) .expect(200) - .then((res) => res.header["x-goog-upload-status"]); + .then((res) => res.header[uploadStatusHeader]); expect(uploadStatus).to.equal("final"); @@ -546,6 +549,93 @@ describe("Firebase Storage endpoint conformance tests", () => { }); }); + describe("upload status", () => { + it("should update the status to active after an upload is started", async () => { + const uploadURL = await supertest(firebaseHost) + .post(`/v0/b/${storageBucket}/o?name=${TEST_FILE_NAME}`) + .set(authHeader) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + const queryUploadStatus = await supertest(firebaseHost) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "query", + }) + .expect(200) + .then((res) => res.header[uploadStatusHeader]); + expect(queryUploadStatus).to.equal("active"); + }); + it("should update the status to cancelled after an upload is cancelled", async () => { + const uploadURL = await supertest(firebaseHost) + .post(`/v0/b/${storageBucket}/o/${ENCODED_TEST_FILE_NAME}`) + .set(authHeader) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + await supertest(firebaseHost) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "cancel", + }) + .expect(200); + const queryUploadStatus = await supertest(firebaseHost) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "query", + }) + .expect(200) + .then((res) => res.header[uploadStatusHeader]); + expect(queryUploadStatus).to.equal("cancelled"); + }); + it("should update the status to final after an upload is finalized", async () => { + const uploadURL = await supertest(firebaseHost) + .post(`/v0/b/${storageBucket}/o?name=${TEST_FILE_NAME}`) + .set(authHeader) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "start", + }) + .expect(200) + .then((res) => new URL(res.header["x-goog-upload-url"])); + + await supertest(firebaseHost) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "upload", + "X-Goog-Upload-Offset": 0, + }) + .expect(200); + await supertest(firebaseHost) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "finalize", + }) + .expect(200) + .then((res) => res.header[uploadStatusHeader]); + const queryUploadStatus = await supertest(firebaseHost) + .put(uploadURL.pathname + uploadURL.search) + .set({ + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Command": "query", + }) + .expect(200) + .then((res) => res.header[uploadStatusHeader]); + expect(queryUploadStatus).to.equal("final"); + }); + }); + describe("tokens", () => { beforeEach(async () => { await testBucket.upload(smallFilePath, { destination: TEST_FILE_NAME }); diff --git a/src/emulator/storage/apis/firebase.ts b/src/emulator/storage/apis/firebase.ts index 762a28bd3bd..6e06260f4e4 100644 --- a/src/emulator/storage/apis/firebase.ts +++ b/src/emulator/storage/apis/firebase.ts @@ -267,6 +267,7 @@ export function createFirebaseEndpoints(emulator: StorageEmulator): Router { throw err; } res.header("X-Goog-Upload-Size-Received", upload.size.toString()); + res.header("x-goog-upload-status", upload.status); return res.sendStatus(200); } diff --git a/src/emulator/storage/server.ts b/src/emulator/storage/server.ts index eb399ea9e1c..1489f5fb09f 100644 --- a/src/emulator/storage/server.ts +++ b/src/emulator/storage/server.ts @@ -44,8 +44,8 @@ export function createApp( exposedHeaders: [ "content-type", "x-firebase-storage-version", + "X-Goog-Upload-Size-Received", "x-goog-upload-url", - "x-goog-upload-status", "x-goog-upload-command", "x-gupload-uploadid", "x-goog-upload-header-content-length", diff --git a/src/emulator/storage/upload.ts b/src/emulator/storage/upload.ts index 5d0b0dd7a81..991a8b9c182 100644 --- a/src/emulator/storage/upload.ts +++ b/src/emulator/storage/upload.ts @@ -27,9 +27,9 @@ export enum UploadType { /** The status of an upload. Multipart uploads can only ever be FINISHED. */ export enum UploadStatus { - ACTIVE, - CANCELLED, - FINISHED, + ACTIVE = "active", + CANCELLED = "cancelled", + FINISHED = "final", } /** Request object for {@link UploadService#mediaUpload}. */ From 6ed2e7f3aa852fef863df82b6d6e9e22e7605072 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 18 Jan 2023 12:19:54 -0800 Subject: [PATCH 0757/1699] update jsonwebtoken in v2 test fixture (#5435) --- .../v1/package-lock.json | 2150 ++++++++++++----- .../v2/package-lock.json | 2111 +++++++++++----- 2 files changed, 3015 insertions(+), 1246 deletions(-) diff --git a/scripts/triggers-end-to-end-tests/v1/package-lock.json b/scripts/triggers-end-to-end-tests/v1/package-lock.json index 4dc57f32c36..330a6bb2b5e 100644 --- a/scripts/triggers-end-to-end-tests/v1/package-lock.json +++ b/scripts/triggers-end-to-end-tests/v1/package-lock.json @@ -17,6 +17,18 @@ "node": "16" } }, + "node_modules/@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "optional": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@fastify/busboy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", @@ -72,6 +84,7 @@ "version": "0.5.17", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", + "peer": true, "dependencies": { "@firebase/util": "1.6.3", "tslib": "^2.1.0" @@ -177,6 +190,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -185,23 +199,24 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", + "peer": true, "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@google-cloud/firestore": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-5.0.2.tgz", - "integrity": "sha512-xlGcNYaW0nvUMzNn2+pLfbEBVt6oysVqtM89faMgZWkWfEtvIQGS0h5PRdLlcqufNzRCX3yIGv29Pb+03ys+VA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.2.tgz", + "integrity": "sha512-f7xFwINJveaqTFcgy0G4o2CBPm0Gv9lTGQ4dQt+7skwaHs3ytdue9ma8oQZYXKNoWcAoDIMQ929Dk0KOIocxFg==", "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" + "google-gax": "^3.5.2", + "protobufjs": "^7.0.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=12.0.0" } }, "node_modules/@google-cloud/paginator": { @@ -227,25 +242,24 @@ } }, "node_modules/@google-cloud/promisify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.0.tgz", - "integrity": "sha512-91ArYvRgXWb73YvEOBMmOcJc0bDRs5yiVHnqkwoG0f3nm7nZuipllz6e7BvFESBvjkDTBC0zMD8QxedUwNLc1A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", "optional": true, "engines": { "node": ">=12" } }, "node_modules/@google-cloud/storage": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.2.2.tgz", - "integrity": "sha512-KhAOxmGfmELKKn6cdvgGfAi/YBLi19hI1jX3QI7xQmbeajSFMgUKrIPbbyfMIxQPOEQ9vG0MQX1uganlA/HTRA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "optional": true, "dependencies": { "@google-cloud/paginator": "^3.0.7", "@google-cloud/projectify": "^3.0.0", "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", "duplexify": "^4.0.0", @@ -256,9 +270,7 @@ "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", "retry-request": "^5.0.0", - "stream-events": "^1.0.4", "teeny-request": "^8.0.0", "uuid": "^8.0.0" }, @@ -266,13 +278,22 @@ "node": ">=12" } }, + "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==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "optional": true, "dependencies": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" }, "engines": { @@ -280,15 +301,15 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", "optional": true, "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.11.3", + "protobufjs": "^7.0.0", "yargs": "^16.2.0" }, "bin": { @@ -429,6 +450,12 @@ "@types/node": "*" } }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, "node_modules/@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -441,6 +468,22 @@ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", "optional": true }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -494,6 +537,27 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "optional": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -553,6 +617,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "optional": true + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -576,6 +646,12 @@ "retry": "0.13.1" } }, + "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==", + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -597,14 +673,20 @@ "optional": true }, "node_modules/bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "optional": true, "engines": { "node": "*" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "optional": true + }, "node_modules/body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -628,6 +710,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.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", @@ -653,6 +744,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "optional": 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/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -694,6 +813,12 @@ "node": ">= 0.6" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "optional": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -746,6 +871,12 @@ "ms": "2.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -817,6 +948,15 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "optional": true }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -831,6 +971,103 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "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==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "optional": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "optional": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "optional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -901,10 +1138,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "optional": true + }, "node_modules/fast-text-encoding": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", - "integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", "optional": true }, "node_modules/faye-websocket": { @@ -936,60 +1179,99 @@ } }, "node_modules/firebase-admin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.0.0.tgz", - "integrity": "sha512-x56u+Q1P8QDvQKaYRe29ZUM/3f829cP8tsKCDXOhaIX/GbGfgcdjRhPmCafzlwgCWP5wW9NkOgIhnrw94zucvw==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.4.1.tgz", + "integrity": "sha512-t5+Pf8rC01TW1KPD5U8Q45AEn7eK+FJaHlpzYStFb62J+MQmN/kB/PWUEsNn+7MNAQ0DZxFUCgJoi+bRmf83oQ==", "dependencies": { "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.2.0", - "@firebase/database-types": "^0.9.7", + "@firebase/database-compat": "^0.2.6", + "@firebase/database-types": "^0.9.13", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^2.1.4", "node-forge": "^1.3.1", - "uuid": "^8.3.2" + "uuid": "^9.0.0" }, "engines": { "node": ">=14" }, "optionalDependencies": { - "@google-cloud/firestore": "^5.0.2", - "@google-cloud/storage": "^6.1.0" + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2" } }, - "node_modules/firebase-admin/node_modules/@firebase/database": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.3.tgz", - "integrity": "sha512-ZE+QJqQUaCTZiIzGq3RJLo64HRMtbdaEwyDhfZyPEzMJV4kyLsw3cHdEHVCtBmdasTvwtpO2YRFmd4AXAoKtNw==", + "node_modules/firebase-admin/node_modules/@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + }, + "node_modules/firebase-admin/node_modules/@firebase/auth-interop-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/firebase-admin/node_modules/@firebase/component": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", "dependencies": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, + "node_modules/firebase-admin/node_modules/@firebase/database": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "node_modules/firebase-admin/node_modules/@firebase/database-compat": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.3.tgz", - "integrity": "sha512-uwSMnbjlSQM5gQRq8OoBLs7uc7obwsl0D6kSDAnMOlPtPl9ert79Rq9faU/COjybsJ8l7tNXMVYYJo3mQ5XNrA==", - "dependencies": { - "@firebase/component": "0.5.17", - "@firebase/database": "0.13.3", - "@firebase/database-types": "0.9.11", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, "node_modules/firebase-admin/node_modules/@firebase/database-types": { - "version": "0.9.11", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.11.tgz", - "integrity": "sha512-27V3eFomWCZqLR6qb3Q9eS2lsUtulhSHeDNaL6fImwnhvMYTmf6ZwMfRWupgi8AFwW4s91g9Oc1/fkQtJGHKQw==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "dependencies": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.6.3" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" + } + }, + "node_modules/firebase-admin/node_modules/@firebase/logger": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/firebase-admin/node_modules/@firebase/util": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", + "dependencies": { + "tslib": "^2.1.0" } }, "node_modules/firebase-functions": { @@ -1047,6 +1329,12 @@ "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==", + "optional": true + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1059,9 +1347,9 @@ "optional": true }, "node_modules/gaxios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", - "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", "optional": true, "dependencies": { "extend": "^3.0.2", @@ -1074,9 +1362,9 @@ } }, "node_modules/gcp-metadata": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", - "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", "optional": true, "dependencies": { "gaxios": "^5.0.0", @@ -1108,10 +1396,29 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-auth-library": { + "node_modules/glob": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.1.0.tgz", - "integrity": "sha512-J/fNXEnqLgbr3kmeUshZCtHQia6ZiNbbrebVzpt/+LTeY6Ka9CtbQvloTjVGVO7nyYbs0KYeuIwgUC/t2Gp1Jw==", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", "optional": true, "dependencies": { "arrify": "^2.0.0", @@ -1120,7 +1427,7 @@ "fast-text-encoding": "^1.0.0", "gaxios": "^5.0.0", "gcp-metadata": "^5.0.0", - "gtoken": "^6.0.0", + "gtoken": "^6.1.0", "jws": "^4.0.0", "lru-cache": "^6.0.0" }, @@ -1129,211 +1436,98 @@ } }, "node_modules/google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "optional": true, "dependencies": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", + "google-auth-library": "^8.0.2", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", + "retry-request": "^5.0.0" }, "bin": { - "compileProtos": "build/tools/compileProtos.js" + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/google-gax/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", "optional": true, "dependencies": { - "ms": "2.1.2" + "node-forge": "^1.3.1" }, - "engines": { - "node": ">=6.0" + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">=12.0.0" } }, - "node_modules/google-gax/node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", "optional": true, "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" } }, - "node_modules/google-gax/node_modules/gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "optional": true, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dependencies": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" + "function-bind": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4.0" } }, - "node_modules/google-gax/node_modules/google-auth-library": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", - "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "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==", "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/google-gax/node_modules/google-p12-pem": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", - "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", - "optional": true, - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", - "optional": true, - "dependencies": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "optional": true - }, - "node_modules/google-gax/node_modules/retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", - "optional": true, - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/google-p12-pem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.0.tgz", - "integrity": "sha512-lRTMn5ElBdDixv4a86bixejPSRk1boRtUowNepeKEVvYiFlkLuAJUVpEz6PfObDHYEKnZWq/9a2zC98xu62A9w==", - "optional": true, - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/gtoken": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.0.tgz", - "integrity": "sha512-WPZcFw34wh2LUvbCUWI70GDhOlO7qHpSvFHFqq7d3Wvsf8dIJedE0lnUdOmsKuC0NgflKmF0LxIF38vsGeHHiQ==", - "optional": true, - "dependencies": { - "gaxios": "^4.0.0", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/gtoken/node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/http-errors": { @@ -1446,6 +1640,16 @@ "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", "peer": true }, + "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==", + "optional": 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", @@ -1500,6 +1704,44 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -1510,24 +1752,18 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/jwa": { @@ -1623,11 +1859,42 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "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/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1644,41 +1911,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -1689,7 +1921,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -1720,6 +1951,50 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "optional": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "optional": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "optional": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1772,6 +2047,39 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.5.tgz", + "integrity": "sha512-CI8wwdrll4ehjPAqs8TL8lBPyNnpZlQI02Wn8C1weNz/QbUbjh3OMxgMKSnvqfKFdLlks3EzHB9tO0BqGc3phQ==", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1857,6 +2165,23 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1880,24 +2205,45 @@ "node": ">= 0.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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", + "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", "optional": true, "dependencies": { - "protobufjs": "^6.11.2" + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" } }, "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "hasInstallScript": true, "optional": true, "dependencies": { @@ -1911,15 +2257,47 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "optional": true, + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" }, "bin": { "pbjs": "bin/pbjs", "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" } }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "optional": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1937,27 +2315,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "node_modules/qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -2017,6 +2374,15 @@ "node": ">=0.10.0" } }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -2027,9 +2393,9 @@ } }, "node_modules/retry-request": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.1.tgz", - "integrity": "sha512-lxFKrlBt0OZzCWh/V0uPEN0vlr3OhdeXnpeY5OES+ckslm791Cb1D5P7lJUSnY7J5hiCjcyaUGmzCnIGDCUBig==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", "optional": true, "dependencies": { "debug": "^4.1.1", @@ -2062,6 +2428,63 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "optional": true }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": 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/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2087,11 +2510,17 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -2165,6 +2594,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2223,23 +2661,53 @@ "node": ">=8" } }, + "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==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "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==", + "optional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "optional": true + }, "node_modules/teeny-request": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.0.tgz", - "integrity": "sha512-6KEYxXI4lQPSDkXzXpPmJPNmo7oqduFFbhOEHf8sfsLbXyCsb+umUjBtMGAKhaSToD8JNCtQutTRefu29K64JA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "optional": true, "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" }, "engines": { "node": ">=12" @@ -2250,6 +2718,18 @@ "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2268,6 +2748,18 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2280,6 +2772,30 @@ "node": ">= 0.6" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2303,9 +2819,9 @@ } }, "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==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { "uuid": "dist/bin/uuid" } @@ -2353,6 +2869,15 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -2376,6 +2901,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "optional": true }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "optional": true + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -2388,8 +2919,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { "version": "16.2.0", @@ -2432,6 +2962,12 @@ } }, "dependencies": { + "@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "optional": true + }, "@fastify/busboy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", @@ -2481,6 +3017,7 @@ "version": "0.5.17", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", + "peer": true, "requires": { "@firebase/util": "1.6.3", "tslib": "^2.1.0" @@ -2589,6 +3126,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", + "peer": true, "requires": { "tslib": "^2.1.0" } @@ -2597,20 +3135,21 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", + "peer": true, "requires": { "tslib": "^2.1.0" } }, "@google-cloud/firestore": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-5.0.2.tgz", - "integrity": "sha512-xlGcNYaW0nvUMzNn2+pLfbEBVt6oysVqtM89faMgZWkWfEtvIQGS0h5PRdLlcqufNzRCX3yIGv29Pb+03ys+VA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.2.tgz", + "integrity": "sha512-f7xFwINJveaqTFcgy0G4o2CBPm0Gv9lTGQ4dQt+7skwaHs3ytdue9ma8oQZYXKNoWcAoDIMQ929Dk0KOIocxFg==", "optional": true, "requires": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" + "google-gax": "^3.5.2", + "protobufjs": "^7.0.0" } }, "@google-cloud/paginator": { @@ -2630,22 +3169,21 @@ "optional": true }, "@google-cloud/promisify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.0.tgz", - "integrity": "sha512-91ArYvRgXWb73YvEOBMmOcJc0bDRs5yiVHnqkwoG0f3nm7nZuipllz6e7BvFESBvjkDTBC0zMD8QxedUwNLc1A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", "optional": true }, "@google-cloud/storage": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.2.2.tgz", - "integrity": "sha512-KhAOxmGfmELKKn6cdvgGfAi/YBLi19hI1jX3QI7xQmbeajSFMgUKrIPbbyfMIxQPOEQ9vG0MQX1uganlA/HTRA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "optional": true, "requires": { "@google-cloud/paginator": "^3.0.7", "@google-cloud/projectify": "^3.0.0", "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", "duplexify": "^4.0.0", @@ -2656,33 +3194,39 @@ "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", "retry-request": "^5.0.0", - "stream-events": "^1.0.4", "teeny-request": "^8.0.0", "uuid": "^8.0.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true + } } }, "@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "optional": true, "requires": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", "optional": true, "requires": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.11.3", + "protobufjs": "^7.0.0", "yargs": "^16.2.0" } }, @@ -2811,6 +3355,12 @@ "@types/node": "*" } }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, "@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -2823,6 +3373,22 @@ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", "optional": true }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -2870,6 +3436,19 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "optional": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "optional": true, + "requires": {} + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -2911,6 +3490,12 @@ "color-convert": "^2.0.1" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "optional": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -2931,6 +3516,12 @@ "retry": "0.13.1" } }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "optional": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2938,9 +3529,15 @@ "optional": true }, "bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "optional": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "optional": true }, "body-parser": { @@ -2962,6 +3559,15 @@ "unpipe": "1.0.0" } }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "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", @@ -2981,6 +3587,25 @@ "get-intrinsic": "^1.0.2" } }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -3016,6 +3641,12 @@ "mime-db": ">= 1.43.0 < 2" } }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "optional": true + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3056,6 +3687,12 @@ "ms": "2.0.0" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "optional": true + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3117,16 +3754,84 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "optional": true }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "optional": true }, - "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==" + "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==" + }, + "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==", + "optional": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "optional": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "optional": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "optional": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true }, "etag": { "version": "1.8.1", @@ -3189,10 +3894,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "optional": true + }, "fast-text-encoding": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", - "integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", "optional": true }, "faye-websocket": { @@ -3218,55 +3929,91 @@ } }, "firebase-admin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.0.0.tgz", - "integrity": "sha512-x56u+Q1P8QDvQKaYRe29ZUM/3f829cP8tsKCDXOhaIX/GbGfgcdjRhPmCafzlwgCWP5wW9NkOgIhnrw94zucvw==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.4.1.tgz", + "integrity": "sha512-t5+Pf8rC01TW1KPD5U8Q45AEn7eK+FJaHlpzYStFb62J+MQmN/kB/PWUEsNn+7MNAQ0DZxFUCgJoi+bRmf83oQ==", "requires": { "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.2.0", - "@firebase/database-types": "^0.9.7", - "@google-cloud/firestore": "^5.0.2", - "@google-cloud/storage": "^6.1.0", + "@firebase/database-compat": "^0.2.6", + "@firebase/database-types": "^0.9.13", + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^2.1.4", "node-forge": "^1.3.1", - "uuid": "^8.3.2" + "uuid": "^9.0.0" }, "dependencies": { + "@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + }, + "@firebase/auth-interop-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", + "requires": {} + }, + "@firebase/component": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", + "requires": { + "@firebase/util": "1.7.3", + "tslib": "^2.1.0" + } + }, "@firebase/database": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.3.tgz", - "integrity": "sha512-ZE+QJqQUaCTZiIzGq3RJLo64HRMtbdaEwyDhfZyPEzMJV4kyLsw3cHdEHVCtBmdasTvwtpO2YRFmd4AXAoKtNw==", + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", "requires": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "@firebase/database-compat": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.3.tgz", - "integrity": "sha512-uwSMnbjlSQM5gQRq8OoBLs7uc7obwsl0D6kSDAnMOlPtPl9ert79Rq9faU/COjybsJ8l7tNXMVYYJo3mQ5XNrA==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", "requires": { - "@firebase/component": "0.5.17", - "@firebase/database": "0.13.3", - "@firebase/database-types": "0.9.11", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, "@firebase/database-types": { - "version": "0.9.11", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.11.tgz", - "integrity": "sha512-27V3eFomWCZqLR6qb3Q9eS2lsUtulhSHeDNaL6fImwnhvMYTmf6ZwMfRWupgi8AFwW4s91g9Oc1/fkQtJGHKQw==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", + "requires": { + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" + } + }, + "@firebase/logger": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", "requires": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.6.3" + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", + "requires": { + "tslib": "^2.1.0" } } } @@ -3304,6 +4051,12 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3316,9 +4069,9 @@ "optional": true }, "gaxios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", - "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", "optional": true, "requires": { "extend": "^3.0.2", @@ -3328,9 +4081,9 @@ } }, "gcp-metadata": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", - "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", "optional": true, "requires": { "gaxios": "^5.0.0", @@ -3353,10 +4106,23 @@ "has-symbols": "^1.0.3" } }, - "google-auth-library": { + "glob": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.1.0.tgz", - "integrity": "sha512-J/fNXEnqLgbr3kmeUshZCtHQia6ZiNbbrebVzpt/+LTeY6Ka9CtbQvloTjVGVO7nyYbs0KYeuIwgUC/t2Gp1Jw==", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", "optional": true, "requires": { "arrify": "^2.0.0", @@ -3365,152 +4131,57 @@ "fast-text-encoding": "^1.0.0", "gaxios": "^5.0.0", "gcp-metadata": "^5.0.0", - "gtoken": "^6.0.0", + "gtoken": "^6.1.0", "jws": "^4.0.0", "lru-cache": "^6.0.0" } }, "google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "optional": true, "requires": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", + "google-auth-library": "^8.0.2", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "optional": true, - "requires": { - "ms": "2.1.2" - } - }, - "gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - }, - "gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "optional": true, - "requires": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - } - }, - "google-auth-library": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", - "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "google-p12-pem": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", - "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", - "optional": true, - "requires": { - "node-forge": "^1.3.1" - } - }, - "gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", - "optional": true, - "requires": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", - "jws": "^4.0.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "optional": true - }, - "retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", - "optional": true, - "requires": { - "debug": "^4.1.1", - "extend": "^3.0.2" - } - } + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", + "retry-request": "^5.0.0" } }, "google-p12-pem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.0.tgz", - "integrity": "sha512-lRTMn5ElBdDixv4a86bixejPSRk1boRtUowNepeKEVvYiFlkLuAJUVpEz6PfObDHYEKnZWq/9a2zC98xu62A9w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", "optional": true, "requires": { "node-forge": "^1.3.1" } }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, "gtoken": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.0.tgz", - "integrity": "sha512-WPZcFw34wh2LUvbCUWI70GDhOlO7qHpSvFHFqq7d3Wvsf8dIJedE0lnUdOmsKuC0NgflKmF0LxIF38vsGeHHiQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", "optional": true, "requires": { - "gaxios": "^4.0.0", + "gaxios": "^5.0.1", "google-p12-pem": "^4.0.0", "jws": "^4.0.0" - }, - "dependencies": { - "gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - } } }, "has": { @@ -3521,6 +4192,12 @@ "function-bind": "^1.1.1" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "optional": true + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -3612,6 +4289,16 @@ "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", "peer": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3648,6 +4335,38 @@ "@panva/asn1.js": "^1.0.0" } }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, + "requires": { + "xmlcreate": "^2.0.4" + } + }, + "jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "optional": true, + "requires": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + } + }, "json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -3658,20 +4377,14 @@ } }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "requires": { "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "jwa": { @@ -3760,11 +4473,39 @@ "safe-buffer": "^5.0.1" } }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, "limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3781,41 +4522,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "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==" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -3826,7 +4532,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, "requires": { "yallist": "^4.0.0" } @@ -3856,6 +4561,38 @@ } } }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "optional": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "optional": true, + "requires": {} + }, + "marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "optional": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3890,6 +4627,27 @@ "mime-db": "1.52.0" } }, + "minimatch": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.5.tgz", + "integrity": "sha512-CI8wwdrll4ehjPAqs8TL8lBPyNnpZlQI02Wn8C1weNz/QbUbjh3OMxgMKSnvqfKFdLlks3EzHB9tO0BqGc3phQ==", + "optional": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "optional": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3946,6 +4704,20 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3960,24 +4732,36 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "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==", + "optional": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true + }, "proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", + "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", "optional": true, "requires": { - "protobufjs": "^6.11.2" + "protobufjs": "^7.0.0" } }, "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -3990,9 +4774,34 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "optional": true + } + } + }, + "protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "optional": true, + "requires": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" } }, "proxy-addr": { @@ -4009,27 +4818,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -4071,6 +4859,15 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "optional": true }, + "requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "requires": { + "lodash": "^4.17.21" + } + }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -4078,9 +4875,9 @@ "optional": true }, "retry-request": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.1.tgz", - "integrity": "sha512-lxFKrlBt0OZzCWh/V0uPEN0vlr3OhdeXnpeY5OES+ckslm791Cb1D5P7lJUSnY7J5hiCjcyaUGmzCnIGDCUBig==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", "optional": true, "requires": { "debug": "^4.1.1", @@ -4104,6 +4901,50 @@ } } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4115,9 +4956,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } }, "send": { "version": "0.18.0", @@ -4177,6 +5021,12 @@ "object-inspect": "^1.9.0" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4226,23 +5076,44 @@ "ansi-regex": "^5.0.1" } }, + "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==", + "optional": true + }, "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "optional": true }, + "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==", + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "optional": true + }, "teeny-request": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.0.tgz", - "integrity": "sha512-6KEYxXI4lQPSDkXzXpPmJPNmo7oqduFFbhOEHf8sfsLbXyCsb+umUjBtMGAKhaSToD8JNCtQutTRefu29K64JA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "optional": true, "requires": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" } }, "text-decoding": { @@ -4250,6 +5121,15 @@ "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4265,6 +5145,15 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -4274,6 +5163,24 @@ "mime-types": "~2.1.24" } }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true + }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4291,9 +5198,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" }, "vary": { "version": "1.1.2", @@ -4329,6 +5236,12 @@ "webidl-conversions": "^3.0.0" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4346,6 +5259,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "optional": true }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "optional": true + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4355,8 +5274,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "16.2.0", diff --git a/scripts/triggers-end-to-end-tests/v2/package-lock.json b/scripts/triggers-end-to-end-tests/v2/package-lock.json index 0a40c182dec..2b1898a5655 100644 --- a/scripts/triggers-end-to-end-tests/v2/package-lock.json +++ b/scripts/triggers-end-to-end-tests/v2/package-lock.json @@ -16,6 +16,18 @@ "node": "16" } }, + "node_modules/@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "optional": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@fastify/busboy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", @@ -28,92 +40,98 @@ } }, "node_modules/@firebase/app-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", - "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==", + "peer": true }, "node_modules/@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "node_modules/@firebase/component": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", - "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", "dependencies": { - "@firebase/util": "1.6.3", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, "node_modules/@firebase/database": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.3.tgz", - "integrity": "sha512-ZE+QJqQUaCTZiIzGq3RJLo64HRMtbdaEwyDhfZyPEzMJV4kyLsw3cHdEHVCtBmdasTvwtpO2YRFmd4AXAoKtNw==", - "dependencies": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", + "dependencies": { + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "node_modules/@firebase/database-compat": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.3.tgz", - "integrity": "sha512-uwSMnbjlSQM5gQRq8OoBLs7uc7obwsl0D6kSDAnMOlPtPl9ert79Rq9faU/COjybsJ8l7tNXMVYYJo3mQ5XNrA==", - "dependencies": { - "@firebase/component": "0.5.17", - "@firebase/database": "0.13.3", - "@firebase/database-types": "0.9.11", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", + "dependencies": { + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, "node_modules/@firebase/database-types": { - "version": "0.9.11", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.11.tgz", - "integrity": "sha512-27V3eFomWCZqLR6qb3Q9eS2lsUtulhSHeDNaL6fImwnhvMYTmf6ZwMfRWupgi8AFwW4s91g9Oc1/fkQtJGHKQw==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "dependencies": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.6.3" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" } }, + "node_modules/@firebase/database-types/node_modules/@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + }, "node_modules/@firebase/logger": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", - "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@firebase/util": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", - "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@google-cloud/firestore": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-5.0.2.tgz", - "integrity": "sha512-xlGcNYaW0nvUMzNn2+pLfbEBVt6oysVqtM89faMgZWkWfEtvIQGS0h5PRdLlcqufNzRCX3yIGv29Pb+03ys+VA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.2.tgz", + "integrity": "sha512-f7xFwINJveaqTFcgy0G4o2CBPm0Gv9lTGQ4dQt+7skwaHs3ytdue9ma8oQZYXKNoWcAoDIMQ929Dk0KOIocxFg==", "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" + "google-gax": "^3.5.2", + "protobufjs": "^7.0.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=12.0.0" } }, "node_modules/@google-cloud/paginator": { @@ -139,25 +157,24 @@ } }, "node_modules/@google-cloud/promisify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.0.tgz", - "integrity": "sha512-91ArYvRgXWb73YvEOBMmOcJc0bDRs5yiVHnqkwoG0f3nm7nZuipllz6e7BvFESBvjkDTBC0zMD8QxedUwNLc1A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", "optional": true, "engines": { "node": ">=12" } }, "node_modules/@google-cloud/storage": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.2.2.tgz", - "integrity": "sha512-KhAOxmGfmELKKn6cdvgGfAi/YBLi19hI1jX3QI7xQmbeajSFMgUKrIPbbyfMIxQPOEQ9vG0MQX1uganlA/HTRA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "optional": true, "dependencies": { "@google-cloud/paginator": "^3.0.7", "@google-cloud/projectify": "^3.0.0", "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", "duplexify": "^4.0.0", @@ -168,9 +185,7 @@ "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", "retry-request": "^5.0.0", - "stream-events": "^1.0.4", "teeny-request": "^8.0.0", "uuid": "^8.0.0" }, @@ -178,13 +193,22 @@ "node": ">=12" } }, + "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==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "optional": true, "dependencies": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" }, "engines": { @@ -192,15 +216,15 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", "optional": true, "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.11.3", + "protobufjs": "^7.0.0", "yargs": "^16.2.0" }, "bin": { @@ -341,6 +365,12 @@ "@types/node": "*" } }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, "node_modules/@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -353,6 +383,22 @@ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", "optional": true }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -406,6 +452,27 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "optional": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -465,6 +532,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "optional": true + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -488,6 +561,12 @@ "retry": "0.13.1" } }, + "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==", + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -509,14 +588,20 @@ "optional": true }, "node_modules/bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "optional": true, "engines": { "node": "*" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "optional": true + }, "node_modules/body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -540,6 +625,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.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", @@ -565,6 +659,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "optional": 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/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -606,6 +728,12 @@ "node": ">= 0.6" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "optional": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -658,6 +786,12 @@ "ms": "2.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -729,6 +863,15 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "optional": true }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -743,6 +886,103 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "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==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "optional": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "optional": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "optional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -813,10 +1053,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "optional": true + }, "node_modules/fast-text-encoding": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", - "integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", "optional": true }, "node_modules/faye-websocket": { @@ -848,25 +1094,25 @@ } }, "node_modules/firebase-admin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.0.0.tgz", - "integrity": "sha512-x56u+Q1P8QDvQKaYRe29ZUM/3f829cP8tsKCDXOhaIX/GbGfgcdjRhPmCafzlwgCWP5wW9NkOgIhnrw94zucvw==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.4.1.tgz", + "integrity": "sha512-t5+Pf8rC01TW1KPD5U8Q45AEn7eK+FJaHlpzYStFb62J+MQmN/kB/PWUEsNn+7MNAQ0DZxFUCgJoi+bRmf83oQ==", "dependencies": { "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.2.0", - "@firebase/database-types": "^0.9.7", + "@firebase/database-compat": "^0.2.6", + "@firebase/database-types": "^0.9.13", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^2.1.4", "node-forge": "^1.3.1", - "uuid": "^8.3.2" + "uuid": "^9.0.0" }, "engines": { "node": ">=14" }, "optionalDependencies": { - "@google-cloud/firestore": "^5.0.2", - "@google-cloud/storage": "^6.1.0" + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2" } }, "node_modules/firebase-functions": { @@ -924,6 +1170,12 @@ "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==", + "optional": true + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -936,9 +1188,9 @@ "optional": true }, "node_modules/gaxios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", - "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", "optional": true, "dependencies": { "extend": "^3.0.2", @@ -951,9 +1203,9 @@ } }, "node_modules/gcp-metadata": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", - "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", "optional": true, "dependencies": { "gaxios": "^5.0.0", @@ -985,10 +1237,29 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-auth-library": { + "node_modules/glob": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.1.0.tgz", - "integrity": "sha512-J/fNXEnqLgbr3kmeUshZCtHQia6ZiNbbrebVzpt/+LTeY6Ka9CtbQvloTjVGVO7nyYbs0KYeuIwgUC/t2Gp1Jw==", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", "optional": true, "dependencies": { "arrify": "^2.0.0", @@ -997,7 +1268,7 @@ "fast-text-encoding": "^1.0.0", "gaxios": "^5.0.0", "gcp-metadata": "^5.0.0", - "gtoken": "^6.0.0", + "gtoken": "^6.1.0", "jws": "^4.0.0", "lru-cache": "^6.0.0" }, @@ -1006,150 +1277,38 @@ } }, "node_modules/google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "optional": true, "dependencies": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", + "google-auth-library": "^8.0.2", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", + "retry-request": "^5.0.0" }, "bin": { - "compileProtos": "build/tools/compileProtos.js" + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" }, "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/google-gax/node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "optional": true, - "dependencies": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/google-auth-library": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", - "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/google-p12-pem": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", - "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", - "optional": true, - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", - "optional": true, - "dependencies": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "optional": true - }, - "node_modules/google-gax/node_modules/retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", - "optional": true, - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=8.10.0" + "node": ">=12" } }, "node_modules/google-p12-pem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.0.tgz", - "integrity": "sha512-lRTMn5ElBdDixv4a86bixejPSRk1boRtUowNepeKEVvYiFlkLuAJUVpEz6PfObDHYEKnZWq/9a2zC98xu62A9w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", "optional": true, "dependencies": { "node-forge": "^1.3.1" @@ -1161,13 +1320,19 @@ "node": ">=12.0.0" } }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, "node_modules/gtoken": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.0.tgz", - "integrity": "sha512-WPZcFw34wh2LUvbCUWI70GDhOlO7qHpSvFHFqq7d3Wvsf8dIJedE0lnUdOmsKuC0NgflKmF0LxIF38vsGeHHiQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", "optional": true, "dependencies": { - "gaxios": "^4.0.0", + "gaxios": "^5.0.1", "google-p12-pem": "^4.0.0", "jws": "^4.0.0" }, @@ -1175,22 +1340,6 @@ "node": ">=12.0.0" } }, - "node_modules/gtoken/node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1202,6 +1351,15 @@ "node": ">= 0.4.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==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -1317,6 +1475,16 @@ "node": ">=0.10.0" } }, + "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==", + "optional": 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", @@ -1371,6 +1539,44 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -1381,24 +1587,18 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/jwa": { @@ -1494,11 +1694,42 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "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/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1515,41 +1746,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -1560,7 +1756,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -1591,6 +1786,50 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "optional": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "optional": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "optional": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1643,6 +1882,39 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.5.tgz", + "integrity": "sha512-CI8wwdrll4ehjPAqs8TL8lBPyNnpZlQI02Wn8C1weNz/QbUbjh3OMxgMKSnvqfKFdLlks3EzHB9tO0BqGc3phQ==", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1728,6 +2000,23 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1751,24 +2040,45 @@ "node": ">= 0.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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", + "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", "optional": true, "dependencies": { - "protobufjs": "^6.11.2" + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" } }, "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "hasInstallScript": true, "optional": true, "dependencies": { @@ -1782,15 +2092,47 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "optional": true, + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" }, "bin": { "pbjs": "bin/pbjs", "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" } }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "optional": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1808,27 +2150,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "node_modules/qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -1888,6 +2209,15 @@ "node": ">=0.10.0" } }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -1898,9 +2228,9 @@ } }, "node_modules/retry-request": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.1.tgz", - "integrity": "sha512-lxFKrlBt0OZzCWh/V0uPEN0vlr3OhdeXnpeY5OES+ckslm791Cb1D5P7lJUSnY7J5hiCjcyaUGmzCnIGDCUBig==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", "optional": true, "dependencies": { "debug": "^4.1.1", @@ -1933,6 +2263,63 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "optional": true }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": 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/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1958,11 +2345,17 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -2036,6 +2429,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2094,23 +2496,53 @@ "node": ">=8" } }, + "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==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "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==", + "optional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "optional": true + }, "node_modules/teeny-request": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.0.tgz", - "integrity": "sha512-6KEYxXI4lQPSDkXzXpPmJPNmo7oqduFFbhOEHf8sfsLbXyCsb+umUjBtMGAKhaSToD8JNCtQutTRefu29K64JA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "optional": true, "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" }, "engines": { "node": ">=12" @@ -2121,6 +2553,18 @@ "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2135,9 +2579,21 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } }, "node_modules/type-is": { "version": "1.6.18", @@ -2151,6 +2607,30 @@ "node": ">= 0.6" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2174,9 +2654,9 @@ } }, "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==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { "uuid": "dist/bin/uuid" } @@ -2224,6 +2704,15 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -2247,6 +2736,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "optional": true }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "optional": true + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -2259,8 +2754,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { "version": "16.2.0", @@ -2303,6 +2797,12 @@ } }, "dependencies": { + "@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "optional": true + }, "@fastify/busboy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.1.0.tgz", @@ -2312,86 +2812,94 @@ } }, "@firebase/app-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", - "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==", + "peer": true }, "@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", + "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", "requires": {} }, "@firebase/component": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", - "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", + "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", "requires": { - "@firebase/util": "1.6.3", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, "@firebase/database": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.3.tgz", - "integrity": "sha512-ZE+QJqQUaCTZiIzGq3RJLo64HRMtbdaEwyDhfZyPEzMJV4kyLsw3cHdEHVCtBmdasTvwtpO2YRFmd4AXAoKtNw==", + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", + "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", "requires": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "@firebase/auth-interop-types": "0.1.7", + "@firebase/component": "0.5.21", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "@firebase/database-compat": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.3.tgz", - "integrity": "sha512-uwSMnbjlSQM5gQRq8OoBLs7uc7obwsl0D6kSDAnMOlPtPl9ert79Rq9faU/COjybsJ8l7tNXMVYYJo3mQ5XNrA==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", + "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", "requires": { - "@firebase/component": "0.5.17", - "@firebase/database": "0.13.3", - "@firebase/database-types": "0.9.11", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", + "@firebase/component": "0.5.21", + "@firebase/database": "0.13.10", + "@firebase/database-types": "0.9.17", + "@firebase/logger": "0.3.4", + "@firebase/util": "1.7.3", "tslib": "^2.1.0" } }, "@firebase/database-types": { - "version": "0.9.11", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.11.tgz", - "integrity": "sha512-27V3eFomWCZqLR6qb3Q9eS2lsUtulhSHeDNaL6fImwnhvMYTmf6ZwMfRWupgi8AFwW4s91g9Oc1/fkQtJGHKQw==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", + "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", "requires": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.6.3" + "@firebase/app-types": "0.8.1", + "@firebase/util": "1.7.3" + }, + "dependencies": { + "@firebase/app-types": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", + "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==" + } } }, "@firebase/logger": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", - "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", + "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", "requires": { "tslib": "^2.1.0" } }, "@firebase/util": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", - "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", + "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", "requires": { "tslib": "^2.1.0" } }, "@google-cloud/firestore": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-5.0.2.tgz", - "integrity": "sha512-xlGcNYaW0nvUMzNn2+pLfbEBVt6oysVqtM89faMgZWkWfEtvIQGS0h5PRdLlcqufNzRCX3yIGv29Pb+03ys+VA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.2.tgz", + "integrity": "sha512-f7xFwINJveaqTFcgy0G4o2CBPm0Gv9lTGQ4dQt+7skwaHs3ytdue9ma8oQZYXKNoWcAoDIMQ929Dk0KOIocxFg==", "optional": true, "requires": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" + "google-gax": "^3.5.2", + "protobufjs": "^7.0.0" } }, "@google-cloud/paginator": { @@ -2411,22 +2919,21 @@ "optional": true }, "@google-cloud/promisify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.0.tgz", - "integrity": "sha512-91ArYvRgXWb73YvEOBMmOcJc0bDRs5yiVHnqkwoG0f3nm7nZuipllz6e7BvFESBvjkDTBC0zMD8QxedUwNLc1A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", "optional": true }, "@google-cloud/storage": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.2.2.tgz", - "integrity": "sha512-KhAOxmGfmELKKn6cdvgGfAi/YBLi19hI1jX3QI7xQmbeajSFMgUKrIPbbyfMIxQPOEQ9vG0MQX1uganlA/HTRA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "optional": true, "requires": { "@google-cloud/paginator": "^3.0.7", "@google-cloud/projectify": "^3.0.0", "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", "duplexify": "^4.0.0", @@ -2437,33 +2944,39 @@ "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", "retry-request": "^5.0.0", - "stream-events": "^1.0.4", "teeny-request": "^8.0.0", "uuid": "^8.0.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true + } } }, "@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "optional": true, "requires": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", "optional": true, "requires": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.11.3", + "protobufjs": "^7.0.0", "yargs": "^16.2.0" } }, @@ -2592,6 +3105,12 @@ "@types/node": "*" } }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, "@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -2604,6 +3123,22 @@ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", "optional": true }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -2651,6 +3186,19 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "optional": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "optional": true, + "requires": {} + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -2692,6 +3240,12 @@ "color-convert": "^2.0.1" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "optional": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -2712,6 +3266,12 @@ "retry": "0.13.1" } }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "optional": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2719,9 +3279,15 @@ "optional": true }, "bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "optional": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "optional": true }, "body-parser": { @@ -2743,6 +3309,15 @@ "unpipe": "1.0.0" } }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "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", @@ -2762,6 +3337,25 @@ "get-intrinsic": "^1.0.2" } }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -2797,6 +3391,12 @@ "mime-db": ">= 1.43.0 < 2" } }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "optional": true + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2837,6 +3437,12 @@ "ms": "2.0.0" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "optional": true + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2898,6 +3504,12 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "optional": true }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2909,6 +3521,68 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "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==", + "optional": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "optional": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "optional": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "optional": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2970,10 +3644,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "optional": true + }, "fast-text-encoding": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", - "integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", "optional": true }, "faye-websocket": { @@ -2999,20 +3679,20 @@ } }, "firebase-admin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.0.0.tgz", - "integrity": "sha512-x56u+Q1P8QDvQKaYRe29ZUM/3f829cP8tsKCDXOhaIX/GbGfgcdjRhPmCafzlwgCWP5wW9NkOgIhnrw94zucvw==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.4.1.tgz", + "integrity": "sha512-t5+Pf8rC01TW1KPD5U8Q45AEn7eK+FJaHlpzYStFb62J+MQmN/kB/PWUEsNn+7MNAQ0DZxFUCgJoi+bRmf83oQ==", "requires": { "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.2.0", - "@firebase/database-types": "^0.9.7", - "@google-cloud/firestore": "^5.0.2", - "@google-cloud/storage": "^6.1.0", + "@firebase/database-compat": "^0.2.6", + "@firebase/database-types": "^0.9.13", + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^2.1.4", "node-forge": "^1.3.1", - "uuid": "^8.3.2" + "uuid": "^9.0.0" } }, "firebase-functions": { @@ -3048,6 +3728,12 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3060,9 +3746,9 @@ "optional": true }, "gaxios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", - "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", "optional": true, "requires": { "extend": "^3.0.2", @@ -3072,9 +3758,9 @@ } }, "gcp-metadata": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", - "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", "optional": true, "requires": { "gaxios": "^5.0.0", @@ -3097,10 +3783,23 @@ "has-symbols": "^1.0.3" } }, - "google-auth-library": { + "glob": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.1.0.tgz", - "integrity": "sha512-J/fNXEnqLgbr3kmeUshZCtHQia6ZiNbbrebVzpt/+LTeY6Ka9CtbQvloTjVGVO7nyYbs0KYeuIwgUC/t2Gp1Jw==", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", "optional": true, "requires": { "arrify": "^2.0.0", @@ -3109,152 +3808,57 @@ "fast-text-encoding": "^1.0.0", "gaxios": "^5.0.0", "gcp-metadata": "^5.0.0", - "gtoken": "^6.0.0", + "gtoken": "^6.1.0", "jws": "^4.0.0", "lru-cache": "^6.0.0" } }, "google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "optional": true, "requires": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", + "google-auth-library": "^8.0.2", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "optional": true, - "requires": { - "ms": "2.1.2" - } - }, - "gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - }, - "gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", - "optional": true, - "requires": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - } - }, - "google-auth-library": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", - "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "google-p12-pem": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", - "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", - "optional": true, - "requires": { - "node-forge": "^1.3.1" - } - }, - "gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", - "optional": true, - "requires": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", - "jws": "^4.0.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "optional": true - }, - "retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", - "optional": true, - "requires": { - "debug": "^4.1.1", - "extend": "^3.0.2" - } - } + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", + "retry-request": "^5.0.0" } }, "google-p12-pem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.0.tgz", - "integrity": "sha512-lRTMn5ElBdDixv4a86bixejPSRk1boRtUowNepeKEVvYiFlkLuAJUVpEz6PfObDHYEKnZWq/9a2zC98xu62A9w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", "optional": true, "requires": { "node-forge": "^1.3.1" } }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true + }, "gtoken": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.0.tgz", - "integrity": "sha512-WPZcFw34wh2LUvbCUWI70GDhOlO7qHpSvFHFqq7d3Wvsf8dIJedE0lnUdOmsKuC0NgflKmF0LxIF38vsGeHHiQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", "optional": true, "requires": { - "gaxios": "^4.0.0", + "gaxios": "^5.0.1", "google-p12-pem": "^4.0.0", "jws": "^4.0.0" - }, - "dependencies": { - "gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - } } }, "has": { @@ -3265,6 +3869,12 @@ "function-bind": "^1.1.1" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "optional": true + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -3350,6 +3960,16 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3386,6 +4006,38 @@ "@panva/asn1.js": "^1.0.0" } }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, + "requires": { + "xmlcreate": "^2.0.4" + } + }, + "jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "optional": true, + "requires": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + } + }, "json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -3396,20 +4048,14 @@ } }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "requires": { "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "jwa": { @@ -3498,11 +4144,39 @@ "safe-buffer": "^5.0.1" } }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, "limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3519,41 +4193,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "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==" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -3564,7 +4203,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, "requires": { "yallist": "^4.0.0" } @@ -3594,6 +4232,38 @@ } } }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "optional": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "optional": true, + "requires": {} + }, + "marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "optional": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3628,6 +4298,27 @@ "mime-db": "1.52.0" } }, + "minimatch": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.5.tgz", + "integrity": "sha512-CI8wwdrll4ehjPAqs8TL8lBPyNnpZlQI02Wn8C1weNz/QbUbjh3OMxgMKSnvqfKFdLlks3EzHB9tO0BqGc3phQ==", + "optional": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "optional": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3684,6 +4375,20 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3698,24 +4403,36 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "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==", + "optional": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true + }, "proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", + "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", "optional": true, "requires": { - "protobufjs": "^6.11.2" + "protobufjs": "^7.0.0" } }, "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -3728,9 +4445,34 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "optional": true + } + } + }, + "protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "optional": true, + "requires": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" } }, "proxy-addr": { @@ -3747,27 +4489,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -3809,6 +4530,15 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "optional": true }, + "requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "requires": { + "lodash": "^4.17.21" + } + }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -3816,9 +4546,9 @@ "optional": true }, "retry-request": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.1.tgz", - "integrity": "sha512-lxFKrlBt0OZzCWh/V0uPEN0vlr3OhdeXnpeY5OES+ckslm791Cb1D5P7lJUSnY7J5hiCjcyaUGmzCnIGDCUBig==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", "optional": true, "requires": { "debug": "^4.1.1", @@ -3842,6 +4572,50 @@ } } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3853,9 +4627,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } }, "send": { "version": "0.18.0", @@ -3915,6 +4692,12 @@ "object-inspect": "^1.9.0" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -3964,23 +4747,44 @@ "ansi-regex": "^5.0.1" } }, + "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==", + "optional": true + }, "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "optional": true }, + "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==", + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "optional": true + }, "teeny-request": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.0.tgz", - "integrity": "sha512-6KEYxXI4lQPSDkXzXpPmJPNmo7oqduFFbhOEHf8sfsLbXyCsb+umUjBtMGAKhaSToD8JNCtQutTRefu29K64JA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "optional": true, "requires": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" } }, "text-decoding": { @@ -3988,6 +4792,15 @@ "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -3999,9 +4812,18 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2" + } }, "type-is": { "version": "1.6.18", @@ -4012,6 +4834,24 @@ "mime-types": "~2.1.24" } }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true + }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4029,9 +4869,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" }, "vary": { "version": "1.1.2", @@ -4067,6 +4907,12 @@ "webidl-conversions": "^3.0.0" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4084,6 +4930,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "optional": true }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "optional": true + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4093,8 +4945,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "16.2.0", From 661b3aa86696df1b18622727ef43a11277e8789d Mon Sep 17 00:00:00 2001 From: Emmanuel Jimenez <32231345+mannyjimenez0810@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:28:30 -0500 Subject: [PATCH 0758/1699] Add support for new links added to the release object (release links) (#5405) (#5438) * [WIP] Adding release links to firebase cli * Update client.spec.ts * Update appdistribution-distribute.ts * Update appdistribution-distribute.ts Co-authored-by: Manny Jimenez Co-authored-by: Manny Jimenez --- src/appdistribution/client.ts | 3 +++ src/commands/appdistribution-distribute.ts | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/appdistribution/client.ts b/src/appdistribution/client.ts index 432a379fb63..b85a5719c1e 100644 --- a/src/appdistribution/client.ts +++ b/src/appdistribution/client.ts @@ -46,6 +46,9 @@ export interface Release { displayVersion: string; buildVersion: string; createTime: Date; + firebaseConsoleUri: string; + testingUri: string; + binaryDownloadUri: string; } export interface ReleaseNotes { diff --git a/src/commands/appdistribution-distribute.ts b/src/commands/appdistribution-distribute.ts index 17721663dc3..572b8b6a216 100644 --- a/src/commands/appdistribution-distribute.ts +++ b/src/commands/appdistribution-distribute.ts @@ -129,6 +129,11 @@ export const command = new Command("appdistribution:distribute Date: Wed, 18 Jan 2023 16:03:56 -0800 Subject: [PATCH 0759/1699] Fix typo. (#5439) --- src/firestore/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firestore/README.md b/src/firestore/README.md index bdc86baed8f..74e87ac9282 100644 --- a/src/firestore/README.md +++ b/src/firestore/README.md @@ -61,7 +61,7 @@ The schema for one object in the `fieldOverrides` array is as follows. Optional Note that Cloud Firestore document fields can only be indexed in one [mode](https://firebase.google.com/docs/firestore/query-data/index-overview#index_modes), thus a field object cannot contain both the `order` and `arrayConfig` properties. -For more information about time-to-live (TTL) policies review the [official documention](https://cloud.google.com/firestore/docs/ttl). +For more information about time-to-live (TTL) policies review the [official documentation](https://cloud.google.com/firestore/docs/ttl). ```javascript collectionGroup: string // Labeled "Collection ID" in the Firebase console From 1a8633ad35f29721c0322d7494b6af41ccd20f0d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 19 Jan 2023 10:35:44 -0800 Subject: [PATCH 0760/1699] Remove go discovery module. (#5419) Maybe one day we will go back and re-invest here. For now let's remove the code to avoid extra effort to keep these modules up-to-date as we extend capabilities of runtime delegate module. --- src/deploy/functions/runtimes/golang/gomod.ts | 87 ---------- src/deploy/functions/runtimes/golang/index.ts | 152 ------------------ src/deploy/functions/runtimes/index.ts | 5 +- src/init/features/functions/golang.ts | 73 --------- .../functions/runtimes/golang/gomod.spec.ts | 60 ------- templates/init/functions/golang/_gitignore | 12 -- templates/init/functions/golang/functions.go | 38 ----- 7 files changed, 1 insertion(+), 426 deletions(-) delete mode 100644 src/deploy/functions/runtimes/golang/gomod.ts delete mode 100644 src/deploy/functions/runtimes/golang/index.ts delete mode 100644 src/init/features/functions/golang.ts delete mode 100644 src/test/deploy/functions/runtimes/golang/gomod.spec.ts delete mode 100644 templates/init/functions/golang/_gitignore delete mode 100644 templates/init/functions/golang/functions.go diff --git a/src/deploy/functions/runtimes/golang/gomod.ts b/src/deploy/functions/runtimes/golang/gomod.ts deleted file mode 100644 index c99a9df4226..00000000000 --- a/src/deploy/functions/runtimes/golang/gomod.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { logger } from "../../../../logger"; - -// A module can be much more complicated than this, but this is all we need so far. -// For a full reference, see https://golang.org/doc/modules/gomod-ref -export interface Module { - module: string; - version: string; - dependencies: Record; - replaces: Record; -} - -export function parseModule(mod: string): Module { - const module: Module = { - module: "", - version: "", - dependencies: {}, - replaces: {}, - }; - const lines = mod.split("\n"); - let inBlock: Record | undefined = undefined; - for (const line of lines) { - if (inBlock) { - const endRequireMatch = /\)/.exec(line); - if (endRequireMatch) { - inBlock = undefined; - continue; - } - - let regex: RegExp; - if (inBlock === module.dependencies) { - regex = /([^ ]+) ([^ ]+)/; - } else { - regex = /([^ ]+) => ([^ ]+)/; - } - const mapping = regex.exec(line); - if (mapping) { - (inBlock as Record)[mapping[1]] = mapping[2]; - continue; - } - - if (line.trim()) { - logger.debug("Don't know how to handle line", line, "inside a mod.go require block"); - } - continue; - } - const modMatch = /^module (.*)$/.exec(line); - if (modMatch) { - module.module = modMatch[1]; - continue; - } - const versionMatch = /^go (\d+\.\d+)$/.exec(line); - if (versionMatch) { - module.version = versionMatch[1]; - continue; - } - - const requireMatch = /^require ([^ ]+) ([^ ]+)/.exec(line); - if (requireMatch) { - module.dependencies[requireMatch[1]] = requireMatch[2]; - continue; - } - - const replaceMatch = /^replace ([^ ]+) => ([^ ]+)$/.exec(line); - if (replaceMatch) { - module.replaces[replaceMatch[1]] = replaceMatch[2]; - continue; - } - - const requireBlockMatch = /^require +\(/.exec(line); - if (requireBlockMatch) { - inBlock = module.dependencies; - continue; - } - - const replaceBlockMatch = /^replace +\(/.exec(line); - if (replaceBlockMatch) { - inBlock = module.replaces; - continue; - } - - if (line.trim()) { - logger.debug("Don't know how to handle line", line, "in mod.go"); - } - } - - return module; -} diff --git a/src/deploy/functions/runtimes/golang/index.ts b/src/deploy/functions/runtimes/golang/index.ts deleted file mode 100644 index d879dd81271..00000000000 --- a/src/deploy/functions/runtimes/golang/index.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { promisify } from "util"; -import fetch from "node-fetch"; -import * as fs from "fs"; -import * as path from "path"; -import * as spawn from "cross-spawn"; - -import { FirebaseError } from "../../../../error"; -import { logger } from "../../../../logger"; -import * as backend from "../../backend"; -import * as build from "../../build"; -import * as gomod from "./gomod"; -import * as runtimes from ".."; - -const VERSION_TO_RUNTIME: Record = { - "1.13": "go113", -}; -export const ADMIN_SDK = "firebase.google.com/go/v4"; -export const FUNCTIONS_SDK = "github.com/FirebaseExtended/firebase-functions-go"; - -// Because codegen is a separate binary we won't automatically import it -// when we import the library. -export const FUNCTIONS_CODEGEN = FUNCTIONS_SDK + "/support/codegen"; -export const FUNCTIONS_RUNTIME = FUNCTIONS_SDK + "/support/runtime"; - -/** - * - */ -export async function tryCreateDelegate( - context: runtimes.DelegateContext -): Promise { - const goModPath = path.join(context.sourceDir, "go.mod"); - - let module: gomod.Module; - try { - const modBuffer = await promisify(fs.readFile)(goModPath); - module = gomod.parseModule(modBuffer.toString("utf8")); - } catch (err: any) { - logger.debug("Customer code is not Golang code (or they aren't using gomod)"); - return; - } - - let runtime = context.runtime; - if (!runtime) { - if (!module.version) { - throw new FirebaseError("Could not detect Golang version from go.mod"); - } - if (!VERSION_TO_RUNTIME[module.version]) { - throw new FirebaseError( - `go.mod specifies Golang version ${ - module.version - } which is unsupported by Google Cloud Functions. Valid values are ${Object.keys( - VERSION_TO_RUNTIME - ).join(", ")}` - ); - } - runtime = VERSION_TO_RUNTIME[module.version]; - } - - return new Delegate(context.projectId, context.sourceDir, runtime, module); -} - -export class Delegate { - public readonly name = "golang"; - - constructor( - private readonly projectId: string, - private readonly sourceDir: string, - public readonly runtime: runtimes.Runtime, - private readonly module: gomod.Module - ) {} - validate(): Promise { - return Promise.resolve(); - } - - async build(): Promise { - try { - await promisify(fs.mkdir)(path.join(this.sourceDir, "autogen")); - } catch (err: any) { - if (err?.code !== "EEXIST") { - throw new FirebaseError("Failed to create codegen directory", { children: [err] }); - } - } - const genBinary = spawn.sync("go", ["run", FUNCTIONS_CODEGEN, this.module.module], { - cwd: this.sourceDir, - env: { - ...process.env, - HOME: process.env.HOME, - PATH: process.env.PATH, - GOPATH: process.env.GOPATH, - }, - stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "pipe"], - }); - if (genBinary.status !== 0) { - throw new FirebaseError("Failed to run codegen", { - children: [new Error(genBinary.stderr.toString())], - }); - } - await promisify(fs.writeFile)( - path.join(this.sourceDir, "autogen", "main.go"), - genBinary.stdout - ); - } - - // Watch isn't supported for Go - watch(): Promise<() => Promise> { - return Promise.resolve(() => Promise.resolve()); - } - - serve( - port: number, - adminPort: number, - envs: backend.EnvironmentVariables - ): Promise<() => Promise> { - const childProcess = spawn("go", ["run", "./autogen"], { - env: { - ...process.env, - ...envs, - PORT: port.toString(), - ADMIN_PORT: adminPort.toString(), - HOME: process.env.HOME, - PATH: process.env.PATH, - GOPATH: process.env.GOPATH, - }, - cwd: this.sourceDir, - stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "inherit"], - }); - childProcess.stdout?.on("data", (chunk) => { - logger.debug(chunk.toString()); - }); - return Promise.resolve(async () => { - const p = new Promise((resolve, reject) => { - childProcess.once("exit", resolve); - childProcess.once("error", reject); - }); - - // If we SIGKILL the child process we're actually going to kill the go - // runner and the webserver it launched will keep running. - await fetch(`http://localhost:${adminPort}/__/quitquitquit`); - setTimeout(() => { - if (!childProcess.killed) { - childProcess.kill("SIGKILL"); - } - }, 10_000); - return p; - }); - } - - async discoverBuild(): Promise { - // Unimplemented. Build discovery is not currently supported in Go. - return Promise.resolve({ requiredAPIs: [], endpoints: {}, params: [] }); - } -} diff --git a/src/deploy/functions/runtimes/index.ts b/src/deploy/functions/runtimes/index.ts index 68bfb4125a0..d664d7d1624 100644 --- a/src/deploy/functions/runtimes/index.ts +++ b/src/deploy/functions/runtimes/index.ts @@ -9,7 +9,7 @@ const RUNTIMES: string[] = ["nodejs10", "nodejs12", "nodejs14", "nodejs16", "nod // Experimental runtimes are part of the Runtime type, but are in a // different list to help guard against some day accidentally iterating over // and printing a hidden runtime to the user. -const EXPERIMENTAL_RUNTIMES = ["go113"]; +const EXPERIMENTAL_RUNTIMES: string[] = []; export type Runtime = typeof RUNTIMES[number] | typeof EXPERIMENTAL_RUNTIMES[number]; /** Runtimes that can be found in existing backends but not used for new functions. */ @@ -34,7 +34,6 @@ const MESSAGE_FRIENDLY_RUNTIMES: Record = { nodejs14: "Node.js 14", nodejs16: "Node.js 16", nodejs18: "Node.js 18", - go113: "Go 1.13", }; /** @@ -109,8 +108,6 @@ export interface DelegateContext { } type Factory = (context: DelegateContext) => Promise; -// Note: golang has been removed from delegates because it does not work and it -// is not worth having an experiment for yet. const factories: Factory[] = [node.tryCreateDelegate]; /** diff --git a/src/init/features/functions/golang.ts b/src/init/features/functions/golang.ts deleted file mode 100644 index 18a8b92ee04..00000000000 --- a/src/init/features/functions/golang.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { promisify } from "util"; -import * as fs from "fs"; -import * as path from "path"; -import * as spawn from "cross-spawn"; -import * as clc from "colorette"; - -import { FirebaseError } from "../../../error"; -import { Config } from "../../../config"; -import { promptOnce } from "../../../prompt"; -import * as utils from "../../../utils"; -import * as go from "../../../deploy/functions/runtimes/golang"; -import { logger } from "../../../logger"; - -const RUNTIME_VERSION = "1.13"; - -const TEMPLATE_ROOT = path.resolve(__dirname, "../../../../templates/init/functions/golang"); -const MAIN_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "functions.go"), "utf8"); -const GITIGNORE_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "_gitignore"), "utf8"); - -async function init(setup: unknown, config: Config) { - await writeModFile(config); - - const modName = config.get("functions.go.module") as string; - const [pkg] = modName.split("/").slice(-1); - await config.askWriteProjectFile("functions/functions.go", MAIN_TEMPLATE.replace("PACKAGE", pkg)); - await config.askWriteProjectFile("functions/.gitignore", GITIGNORE_TEMPLATE); -} - -// writeModFile is meant to look like askWriteProjectFile but it generates the contents -// dynamically using the go tool -async function writeModFile(config: Config) { - const modPath = config.path("functions/go.mod"); - if (await promisify(fs.exists)(modPath)) { - const shoudlWriteModFile = await promptOnce({ - type: "confirm", - message: "File " + clc.underline("functions/go.mod") + " already exists. Overwrite?", - default: false, - }); - if (!shoudlWriteModFile) { - return; - } - - // Go will refuse to overwrite an existing mod file. - await promisify(fs.unlink)(modPath); - } - - // Nit(inlined) can we look at functions code and see if there's a domain mapping? - const modName = await promptOnce({ - type: "input", - message: "What would you like to name your module?", - default: "acme.com/functions", - }); - config.set("functions.go.module", modName); - - // Manually create a go mod file because (A) it's easier this way and (B) it seems to be the only - // way to set the min Go version to anything but what the user has installed. - config.writeProjectFile("functions/go.mod", `module ${modName} \n\ngo ${RUNTIME_VERSION}\n\n`); - utils.logSuccess("Wrote " + clc.bold("functions/go.mod")); - - for (const dep of [go.FUNCTIONS_SDK, go.ADMIN_SDK, go.FUNCTIONS_CODEGEN, go.FUNCTIONS_RUNTIME]) { - const result = spawn.sync("go", ["get", dep], { - cwd: config.path("functions"), - stdio: "inherit", - }); - if (result.error) { - logger.debug("Full output from go get command:", JSON.stringify(result, null, 2)); - throw new FirebaseError("Error installing dependencies", { children: [result.error] }); - } - } - utils.logSuccess("Installed dependencies"); -} - -module.exports = init; diff --git a/src/test/deploy/functions/runtimes/golang/gomod.spec.ts b/src/test/deploy/functions/runtimes/golang/gomod.spec.ts deleted file mode 100644 index e2e61d62e36..00000000000 --- a/src/test/deploy/functions/runtimes/golang/gomod.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { expect } from "chai"; -import * as gomod from "../../../../../deploy/functions/runtimes/golang/gomod"; -import * as go from "../../../../../deploy/functions/runtimes/golang"; - -const MOD_NAME = "acme.com/fucntions"; -const GO_VERSION = "1.13"; -const FUNCTIONS_MOD = "firebase.google.com/firebase-functions-go"; -const MIN_MODULE = `module ${MOD_NAME} - -go ${GO_VERSION} -`; - -const INLINE_MODULE = `${MIN_MODULE} -require ${go.ADMIN_SDK} v4.6.0 // indirect - -replace ${FUNCTIONS_MOD} => ${go.FUNCTIONS_SDK} -`; - -const BLOCK_MODULE = `${MIN_MODULE} - -require ( - ${go.ADMIN_SDK} v4.6.0 // indirect -) - -replace ( - ${FUNCTIONS_MOD} => ${go.FUNCTIONS_SDK} -) -`; - -describe("Modules", () => { - it("Should parse a bare minimum module", () => { - const mod = gomod.parseModule(MIN_MODULE); - expect(mod.module).to.equal(MOD_NAME); - expect(mod.version).to.equal(GO_VERSION); - }); - - it("Should parse inline statements", () => { - const mod = gomod.parseModule(INLINE_MODULE); - expect(mod.module).to.equal(MOD_NAME); - expect(mod.version).to.equal(GO_VERSION); - expect(mod.dependencies).to.deep.equal({ - [go.ADMIN_SDK]: "v4.6.0", - }); - expect(mod.replaces).to.deep.equal({ - [FUNCTIONS_MOD]: go.FUNCTIONS_SDK, - }); - }); - - it("Should parse block statements", () => { - const mod = gomod.parseModule(BLOCK_MODULE); - expect(mod.module).to.equal(MOD_NAME); - expect(mod.version).to.equal(GO_VERSION); - expect(mod.dependencies).to.deep.equal({ - [go.ADMIN_SDK]: "v4.6.0", - }); - expect(mod.replaces).to.deep.equal({ - [FUNCTIONS_MOD]: go.FUNCTIONS_SDK, - }); - }); -}); diff --git a/templates/init/functions/golang/_gitignore b/templates/init/functions/golang/_gitignore deleted file mode 100644 index f2dd9554a12..00000000000 --- a/templates/init/functions/golang/_gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out diff --git a/templates/init/functions/golang/functions.go b/templates/init/functions/golang/functions.go deleted file mode 100644 index b8795b7a4d3..00000000000 --- a/templates/init/functions/golang/functions.go +++ /dev/null @@ -1,38 +0,0 @@ -package PACKAGE - -// Welcome to Cloud Functions for Firebase for Golang! -// To get started, uncomment the below code or create your own. -// Deploy with `firebase deploy` - -/* -import ( - "context" - "fmt" - - "github.com/FirebaseExtended/firebase-functions-go/https" - "github.com/FirebaseExtended/firebase-functions-go/pubsub" - "github.com/FirebaseExtended/firebase-functions-go/runwith" -) - -var HelloWorld = https.Function{ - RunWith: https.Options{ - AvailableMemoryMB: 256, - }, - Callback: func(w https.ResponseWriter, req *https.Request) { - fmt.Println("Hello, world!") - fmt.Fprintf(w, "Hello, world!\n") - }, -} - -var PubSubFunction = pubsub.Function{ - EventType: pubsub.MessagePublished, - Topic: "topic", - RunWith: runwith.Options{ - AvailableMemoryMB: 256, - }, - Callback: func(ctx context.Context, message pubsub.Message) error { - fmt.Printf("Got Pub/Sub event %+v", message) - return nil - }, -} -*/ From a0f1a7f7cf991eee9d7457c558bc007bf3a1ca1e Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 19 Jan 2023 21:24:04 +0000 Subject: [PATCH 0761/1699] 11.21.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c3cec3cdbb1..01945d1a547 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.20.0", + "version": "11.21.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.20.0", + "version": "11.21.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index ddf2a0bb09c..ca69b142cb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.20.0", + "version": "11.21.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 3f0414b56abb76575017055ea953515f95884841 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 19 Jan 2023 21:24:18 +0000 Subject: [PATCH 0762/1699] [firebase-release] Removed change log and reset repo after 11.21.0 release --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 828dbc7a0f6..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +0,0 @@ -- Fix bug where CLI was unable to deploy Firebase Functions in some monorepo setups (#5391) -- Upgrade Storage Rules Runtime to v1.1.3 to support ternary operators (#5370) -- Fixes an issue where already deployed functions with the same remote configuration do not get skipped (#5354) From d4a2ce803f60fee4b8b10df34510e82fca4ce5dd Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 19 Jan 2023 15:18:28 -0800 Subject: [PATCH 0763/1699] Refactor how node binary is retrieved for functions/extensions. (#5422) Today, Functions Emulator make strong assumption that all functions target node runtime and pass around references like `nodeBinary`, `nodeMajorVersion`, etc. This PR refactors the logic to abstract away `node` into `bin` and `runtime`. The logic for choosing the appropriate `bin` for the function is lifted to `RuntimeDelegate`, same piece of code that holds together other `nodejs` runtime specific logic. The refactored required small changes to the extensions emulator - looping in @joehan to see if he has any objections. --- CHANGELOG.md | 1 + .../emulator-tests/functionsEmulator.spec.ts | 6 +- src/deploy/functions/runtimes/index.ts | 5 + src/deploy/functions/runtimes/node/index.ts | 73 +++++++- src/emulator/controller.ts | 4 +- src/emulator/extensionsEmulator.ts | 5 +- src/emulator/functionsEmulator.ts | 164 +++++++----------- src/extensions/emulator/optionsHelper.ts | 19 +- src/extensions/emulator/specHelper.ts | 44 +++-- src/serve/functions.ts | 4 +- .../functions/runtimes/node/index.spec.ts | 74 ++++++++ src/test/emulators/extensionsEmulator.spec.ts | 4 +- .../extensions/emulator/specHelper.spec.ts | 82 +++++++++ 13 files changed, 348 insertions(+), 137 deletions(-) create mode 100644 src/test/deploy/functions/runtimes/node/index.spec.ts create mode 100644 src/test/extensions/emulator/specHelper.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..8850284b597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Refactor Functions Emulator. (#5422) diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index 08e5ea9bfb8..30d336bc0b4 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -37,9 +37,9 @@ const TEST_BACKEND = { env: {}, secretEnv: [], codebase: "default", - nodeBinary: process.execPath, - // NOTE: Use the following nodeBinary path if you want to run test cases directly from your IDE. - // nodeBinary: path.join(MODULE_ROOT, "node_modules/.bin/ts-node"), + bin: process.execPath, + // NOTE: Use the following node bin path if you want to run test cases directly from your IDE. + // bin: path.join(MODULE_ROOT, "node_modules/.bin/ts-node"), }; async function useFunction( diff --git a/src/deploy/functions/runtimes/index.ts b/src/deploy/functions/runtimes/index.ts index d664d7d1624..ce327dd4f8c 100644 --- a/src/deploy/functions/runtimes/index.ts +++ b/src/deploy/functions/runtimes/index.ts @@ -62,6 +62,11 @@ export interface RuntimeDelegate { */ runtime: Runtime; + /** + * Path to the bin used to run the source code. + */ + bin: string; + /** * Validate makes sure the customers' code is actually viable. * This includes checks like making sure a package.json file is diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 8241118caeb..3810ec1dbda 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -9,7 +9,7 @@ import fetch from "node-fetch"; import { FirebaseError } from "../../../../error"; import { getRuntimeChoice } from "./parseRuntimeAndValidateSDK"; import { logger } from "../../../../logger"; -import { logLabeledWarning } from "../../../../utils"; +import { logLabeledSuccess, logLabeledWarning } from "../../../../utils"; import * as backend from "../../backend"; import * as build from "../../build"; import * as discovery from "../discovery"; @@ -66,13 +66,74 @@ export class Delegate { // to decide whether to use the JS export method of discovery or the HTTP container contract // method of discovery. _sdkVersion: string | undefined = undefined; - get sdkVersion() { + get sdkVersion(): string { if (this._sdkVersion === undefined) { this._sdkVersion = versioning.getFunctionsSDKVersion(this.sourceDir) || ""; } return this._sdkVersion; } + _bin = ""; + get bin(): string { + if (this._bin === "") { + this._bin = this.getNodeBinary(); + } + return this._bin; + } + + getNodeBinary(): string { + const requestedVersion = semver.coerce(this.runtime); + if (!requestedVersion) { + throw new FirebaseError( + `Could not determine version of the requested runtime: ${this.runtime}` + ); + } + const hostVersion = process.versions.node; + + const localNodePath = path.join(this.sourceDir, "node_modules/node"); + const localNodeVersion = versioning.findModuleVersion("node", localNodePath); + + if (localNodeVersion) { + if (semver.major(requestedVersion) === semver.major(localNodeVersion)) { + logLabeledSuccess( + "functions", + `Using node@${semver.major(localNodeVersion)} from local cache.` + ); + return localNodePath; + } + } + + if (semver.major(requestedVersion) === semver.major(hostVersion)) { + logLabeledSuccess("functions", `Using node@${semver.major(hostVersion)} from host.`); + return process.execPath; + } + + if (!process.env.FIREPIT_VERSION) { + logLabeledWarning( + "functions", + `Your requested "node" version "${semver.major( + requestedVersion + )}" doesn't match your global version "${semver.major( + hostVersion + )}". Using node@${semver.major(hostVersion)} from host.` + ); + return process.execPath; + } + + // Otherwise we'll warn and use the version that is currently running this process. + logLabeledWarning( + "functions", + `You've requested "node" version "${semver.major( + requestedVersion + )}", but the standalone Firebase CLI comes with bundled Node "${semver.major(hostVersion)}".` + ); + logLabeledSuccess( + "functions", + `To use a different Node.js version, consider removing the standalone Firebase CLI and switching to "firebase-tools" on npm.` + ); + return process.execPath; + } + validate(): Promise { versioning.checkFunctionsSDKVersion(this.sdkVersion); @@ -92,14 +153,14 @@ export class Delegate { return Promise.resolve(() => Promise.resolve()); } - serve( - port: number, + serveAdmin( + port: string, config: backend.RuntimeConfigValues, envs: backend.EnvironmentVariables ): Promise<() => Promise> { const env: NodeJS.ProcessEnv = { ...envs, - PORT: port.toString(), + PORT: port, FUNCTIONS_CONTROL_API: "true", HOME: process.env.HOME, PATH: process.env.PATH, @@ -161,7 +222,7 @@ export class Delegate { if (!discovered) { const getPort = promisify(portfinder.getPort) as () => Promise; const port = await getPort(); - const kill = await this.serve(port, config, env); + const kill = await this.serveAdmin(port.toString(), config, env); try { discovered = await discovery.detectFromPort(port, this.projectId, this.runtime); } finally { diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 2411374e52f..b2bb0a91ee4 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -17,7 +17,6 @@ import { } from "./types"; import { Constants, FIND_AVAILBLE_PORT_BY_DEFAULT } from "./constants"; import { EmulatableBackend, FunctionsEmulator } from "./functionsEmulator"; -import { parseRuntimeVersion } from "./functionsEmulatorUtils"; import { AuthEmulator, SingleProjectMode } from "./auth"; import { DatabaseEmulator, DatabaseEmulatorArgs } from "./databaseEmulator"; import { FirestoreEmulator, FirestoreEmulatorArgs } from "./firestoreEmulator"; @@ -476,8 +475,10 @@ export async function startAll( for (const cfg of functionsCfg) { const functionsDir = path.join(projectDir, cfg.source); + const runtime = (options.extDevRuntime as string | undefined) ?? cfg.runtime; emulatableBackends.push({ functionsDir, + runtime, codebase: cfg.codebase, env: { ...options.extDevEnv, @@ -486,7 +487,6 @@ export async function startAll( // TODO(b/213335255): predefinedTriggers and nodeMajorVersion are here to support ext:dev:emulators:* commands. // Ideally, we should handle that case via ExtensionEmulator. predefinedTriggers: options.extDevTriggers as ParsedTriggerDefinition[] | undefined, - nodeMajorVersion: parseRuntimeVersion((options.extDevNodeVersion as string) || cfg.runtime), }); } } diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index d62a1c53ec0..4aeb6e7b0c6 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -230,15 +230,16 @@ export class ExtensionsEmulator implements EmulatorInstance { const functionsDir = path.join(extensionDir, "functions"); // TODO(b/213335255): For local extensions, this should include extensionSpec instead of extensionVersion const env = Object.assign(this.autoPopulatedParams(instance), instance.params); - const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = + const { extensionTriggers, runtime, nonSecretEnv, secretEnvVariables } = await getExtensionFunctionInfo(instance, env); const emulatableBackend: EmulatableBackend = { functionsDir, + runtime, + bin: process.execPath, env: nonSecretEnv, codebase: instance.instanceId, // Give each extension its own codebase name so that they don't share workerPools. secretEnv: secretEnvVariables, predefinedTriggers: extensionTriggers, - nodeMajorVersion: nodeMajorVersion, extensionInstanceId: instance.instanceId, }; if (instance.ref) { diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 984a94955cc..d8124797e19 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -5,6 +5,7 @@ import * as clc from "colorette"; import * as http from "http"; import * as jwt from "jsonwebtoken"; import * as cors from "cors"; +import * as semver from "semver"; import { URL } from "url"; import { EventEmitter } from "events"; @@ -85,8 +86,8 @@ export interface EmulatableBackend { secretEnv: backend.SecretEnvVar[]; codebase: string; predefinedTriggers?: ParsedTriggerDefinition[]; - nodeMajorVersion?: number; - nodeBinary?: string; + runtime?: string; + bin?: string; extensionInstanceId?: string; extension?: Extension; // Only present for published extensions extensionVersion?: ExtensionVersion; // Only present for published extensions @@ -336,7 +337,12 @@ export class FunctionsEmulator implements EmulatorInstance { const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger)); const pool = this.workerPools[record.backend.codebase]; if (!pool.readyForWork(trigger.id)) { - await this.startRuntime(record.backend, trigger); + try { + await this.startRuntime(record.backend, trigger); + } catch (e: any) { + this.logger.logLabeled("ERROR", `Failed to start runtime for ${trigger.id}: ${e}`); + return; + } } const worker = pool.getIdleWorker(trigger.id)!; const reqBody = JSON.stringify(body); @@ -360,9 +366,6 @@ export class FunctionsEmulator implements EmulatorInstance { } async start(): Promise { - for (const backend of this.args.emulatableBackends) { - backend.nodeBinary = this.getNodeBinary(backend); - } const credentialEnv = await this.getCredentialsEnvironment(); for (const e of this.args.emulatableBackends) { e.env = { ...credentialEnv, ...e.env }; @@ -452,16 +455,18 @@ export class FunctionsEmulator implements EmulatorInstance { projectId: this.args.projectId, projectDir: this.args.projectDir, sourceDir: emulatableBackend.functionsDir, + runtime: emulatableBackend.runtime, }; - if (emulatableBackend.nodeMajorVersion) { - runtimeDelegateContext.runtime = `nodejs${emulatableBackend.nodeMajorVersion}`; - } const runtimeDelegate = await runtimes.getRuntimeDelegate(runtimeDelegateContext); logger.debug(`Validating ${runtimeDelegate.name} source`); await runtimeDelegate.validate(); logger.debug(`Building ${runtimeDelegate.name} source`); await runtimeDelegate.build(); + // Retrieve information from the runtime delegate. + emulatableBackend.runtime = runtimeDelegate.runtime; + emulatableBackend.bin = runtimeDelegate.bin; + // Don't include user envs when parsing triggers. Do include user envs when resolving parameter values const firebaseConfig = this.getFirebaseConfig(); const environment = { @@ -499,12 +504,6 @@ export class FunctionsEmulator implements EmulatorInstance { * TODO(b/216167890): Gracefully handle removal of deleted function definitions */ async loadTriggers(emulatableBackend: EmulatableBackend, force = false): Promise { - if (!emulatableBackend.nodeBinary) { - throw new FirebaseError( - `No node binary for ${emulatableBackend.functionsDir}. This should never happen.` - ); - } - let triggerDefinitions: EmulatedTriggerDefinition[] = []; try { triggerDefinitions = await this.discoverTriggers(emulatableBackend); @@ -674,7 +673,14 @@ export class FunctionsEmulator implements EmulatorInstance { {} ) ); - await this.startRuntime(emulatableBackend); + try { + await this.startRuntime(emulatableBackend); + } catch (e: any) { + this.logger.logLabeled( + "ERROR", + `Failed to start functions in ${emulatableBackend.functionsDir}: ${e}` + ); + } } } @@ -1029,72 +1035,6 @@ export class FunctionsEmulator implements EmulatorInstance { triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false })); } - getNodeBinary(backend: EmulatableBackend): string { - const pkg = require(path.join(backend.functionsDir, "package.json")); - // If the developer hasn't specified a Node to use, inform them that it's an option and use default - if ((!pkg.engines || !pkg.engines.node) && !backend.nodeMajorVersion) { - this.logger.log( - "WARN", - `Your functions directory ${backend.functionsDir} does not specify a Node version.\n ` + - "- Learn more at https://firebase.google.com/docs/functions/manage-functions#set_runtime_options" - ); - return process.execPath; - } - - const hostMajorVersion = process.versions.node.split(".")[0]; - const requestedMajorVersion: string = backend.nodeMajorVersion - ? `${backend.nodeMajorVersion}` - : pkg.engines.node; - let localMajorVersion = "0"; - const localNodePath = path.join(backend.functionsDir, "node_modules/.bin/node"); - - // Next check if we have a Node install in the node_modules folder - try { - const localNodeOutput = spawn.sync(localNodePath, ["--version"]).stdout.toString(); - localMajorVersion = localNodeOutput.slice(1).split(".")[0]; - } catch (err: any) { - // Will happen if we haven't asked about local version yet - } - - // If the requested version is already locally available, let's use that - if (requestedMajorVersion === localMajorVersion) { - this.logger.logLabeled( - "SUCCESS", - "functions", - `Using node@${requestedMajorVersion} from local cache.` - ); - return localNodePath; - } - - // If the requested version is the same as the host, let's use that - if (requestedMajorVersion === hostMajorVersion) { - this.logger.logLabeled( - "SUCCESS", - "functions", - `Using node@${requestedMajorVersion} from host.` - ); - } else { - // Otherwise we'll warn and use the version that is currently running this process. - if (process.env.FIREPIT_VERSION) { - this.logger.log( - "WARN", - `You've requested "node" version "${requestedMajorVersion}", but the standalone Firebase CLI comes with bundled Node "${hostMajorVersion}".` - ); - this.logger.log( - "INFO", - `To use a different Node.js version, consider removing the standalone Firebase CLI and switching to "firebase-tools" on npm.` - ); - } else { - this.logger.log( - "WARN", - `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}". Using node@${hostMajorVersion} from host.` - ); - } - } - - return process.execPath; - } - getRuntimeConfig(backend: EmulatableBackend): Record { const configPath = `${backend.functionsDir}/.runtimeconfig.json`; try { @@ -1268,18 +1208,18 @@ export class FunctionsEmulator implements EmulatorInstance { return secretEnvs; } - async startRuntime( + async startNode( backend: EmulatableBackend, - trigger?: EmulatedTriggerDefinition - ): Promise { - const emitter = new EventEmitter(); + envs: Record + ): Promise { const args = [path.join(__dirname, "functionsEmulatorRuntime")]; - if (this.args.debugPort) { - if (process.env.FIREPIT_VERSION && process.execPath === backend.nodeBinary) { + if (process.env.FIREPIT_VERSION) { this.logger.log( "WARN", - `To enable function inspection, please run "${process.execPath} is:npm i node@${backend.nodeMajorVersion} --save-dev" in your functions directory` + `To enable function inspection, please run "npm i node@${semver.coerce( + backend.runtime || "18.0.0" + )} --save-dev" in your functions directory` ); } else { const { host } = this.getInfo(); @@ -1301,28 +1241,43 @@ export class FunctionsEmulator implements EmulatorInstance { "See https://yarnpkg.com/getting-started/migration#step-by-step for more information." ); } - const runtimeEnv = this.getRuntimeEnvs(backend, trigger); - const secretEnvs = await this.resolveSecretEnvs(backend, trigger); - const socketPath = getTemporarySocketPath(); - const childProcess = spawn(backend.nodeBinary!, args, { + const bin = backend.bin; + if (!bin) { + throw new Error( + `No binary associated with ${backend.functionsDir}. ` + + "Make sure function runtime is configured correctly in firebase.json." + ); + } + + const socketPath = getTemporarySocketPath(); + const childProcess = spawn(bin, args, { cwd: backend.functionsDir, env: { - node: backend.nodeBinary, + node: backend.bin, ...process.env, - ...runtimeEnv, - ...secretEnvs, + ...envs, PORT: socketPath, }, stdio: ["pipe", "pipe", "pipe", "ipc"], }); - const runtime: FunctionsRuntimeInstance = { + return Promise.resolve({ process: childProcess, - events: emitter, + events: new EventEmitter(), cwd: backend.functionsDir, socketPath, - }; + }); + } + + async startRuntime( + backend: EmulatableBackend, + trigger?: EmulatedTriggerDefinition + ): Promise { + const runtimeEnv = this.getRuntimeEnvs(backend, trigger); + const secretEnvs = await this.resolveSecretEnvs(backend, trigger); + + const runtime = await this.startNode(backend, { ...runtimeEnv, ...secretEnvs }); const extensionLogInfo = { instanceId: backend.extensionInstanceId, ref: backend.extensionVersion?.ref, @@ -1482,7 +1437,16 @@ export class FunctionsEmulator implements EmulatorInstance { const pool = this.workerPools[record.backend.codebase]; if (!pool.readyForWork(trigger.id)) { - await this.startRuntime(record.backend, trigger); + try { + await this.startRuntime(record.backend, trigger); + } catch (e: any) { + this.logger.logLabeled("ERROR", `Failed to handle request for function ${trigger.id}`); + this.logger.logLabeled( + "ERROR", + `Failed to start functions in ${record.backend.functionsDir}: ${e}` + ); + return; + } } const debugBundle = this.args.debugPort ? { diff --git a/src/extensions/emulator/optionsHelper.ts b/src/extensions/emulator/optionsHelper.ts index b5721991359..9d48aa6f42c 100644 --- a/src/extensions/emulator/optionsHelper.ts +++ b/src/extensions/emulator/optionsHelper.ts @@ -15,6 +15,9 @@ import { needProjectId } from "../../projectUtils"; import { Emulators } from "../../emulator/types"; import { SecretEnvVar } from "../../deploy/functions/backend"; +/** + * Build firebase options based on the extension configuration. + */ export async function buildOptions(options: any): Promise { const extDevDir = localHelper.findExtensionYaml(process.cwd()); options.extDevDir = extDevDir; @@ -37,16 +40,18 @@ export async function buildOptions(options: any): Promise { triggerHelper.functionResourceToEmulatedTriggerDefintion(r) ); options.extDevTriggers = functionEmuTriggerDefs; - options.extDevNodeVersion = specHelper.getNodeVersion(functionResources); + options.extDevRuntime = specHelper.getRuntime(functionResources); return options; } -// TODO: Better name? Also, should this be in extensionsEmulator instead? +/** + * TODO: Better name? Also, should this be in extensionsEmulator instead? + */ export async function getExtensionFunctionInfo( instance: planner.InstanceSpec, paramValues: Record ): Promise<{ - nodeMajorVersion: number; + runtime: string; extensionTriggers: ParsedTriggerDefinition[]; nonSecretEnv: Record; secretEnvVariables: SecretEnvVar[]; @@ -59,12 +64,12 @@ export async function getExtensionFunctionInfo( trigger.name = `ext-${instance.instanceId}-${trigger.name}`; return trigger; }); - const nodeMajorVersion = specHelper.getNodeVersion(functionResources); + const runtime = specHelper.getRuntime(functionResources); const nonSecretEnv = getNonSecretEnv(spec.params, paramValues); const secretEnvVariables = getSecretEnvVars(spec.params, paramValues); return { extensionTriggers, - nodeMajorVersion, + runtime, nonSecretEnv, secretEnvVariables, }; @@ -116,7 +121,9 @@ export function getSecretEnvVars( return secretEnvVar; } -// Exported for testing +/** + * Exported for testing + */ export function getParams(options: any, extensionSpec: ExtensionSpec) { const projectId = needProjectId(options); const userParams = paramHelper.readEnvFile(options.testParams); diff --git a/src/extensions/emulator/specHelper.ts b/src/extensions/emulator/specHelper.ts index 139a1c2c60e..47aeef51ba8 100644 --- a/src/extensions/emulator/specHelper.ts +++ b/src/extensions/emulator/specHelper.ts @@ -6,7 +6,6 @@ import { ExtensionSpec, Resource } from "../types"; import { FirebaseError } from "../../error"; import { substituteParams } from "../extensionsHelper"; import { getResourceRuntime } from "../utils"; -import { parseRuntimeVersion } from "../../emulator/functionsEmulatorUtils"; const SPEC_FILE = "extension.yaml"; const POSTINSTALL_FILE = "POSTINSTALL.md"; @@ -80,6 +79,9 @@ export function readFileFromDirectory( }); } +/** + * Substitue parameters of function resources in the extensions spec. + */ export function getFunctionResourcesWithParamSubstitution( extensionSpec: ExtensionSpec, params: { [key: string]: string } @@ -90,25 +92,36 @@ export function getFunctionResourcesWithParamSubstitution( return substituteParams(rawResources, params); } +/** + * Get properties associated with the function resource. + */ export function getFunctionProperties(resources: Resource[]) { return resources.map((r) => r.properties); } -export function getNodeVersion(resources: Resource[]): number { +export const DEFAULT_RUNTIME = "nodejs14"; + +/** + * Get runtime associated with the resources. If multiple runtimes exists, choose the latest runtime. + * e.g. prefer nodejs14 over nodejs12. + */ +export function getRuntime(resources: Resource[]): string { + if (resources.length === 0) { + return DEFAULT_RUNTIME; + } + const invalidRuntimes: string[] = []; - const versions = resources.map((r: Resource) => { - if (getResourceRuntime(r)) { - const runtimeName = getResourceRuntime(r) as string; - const runtime = parseRuntimeVersion(runtimeName); - if (!runtime) { - invalidRuntimes.push(runtimeName); - } else { - return runtime; - } + const runtimes = resources.map((r: Resource) => { + const runtime = getResourceRuntime(r); + if (!runtime) { + return DEFAULT_RUNTIME; + } + if (!/^(nodejs)?([0-9]+)/.test(runtime)) { + invalidRuntimes.push(runtime); + return DEFAULT_RUNTIME; } - return 14; + return runtime; }); - if (invalidRuntimes.length) { throw new FirebaseError( `The following runtimes are not supported by the Emulator Suite: ${invalidRuntimes.join( @@ -116,5 +129,8 @@ export function getNodeVersion(resources: Resource[]): number { )}. \n Only Node runtimes are supported.` ); } - return Math.max(...versions); + // Assumes that all runtimes target the nodejs. + // Rely on lexicographically order of nodejs runtime to pick the latest version. + // e.g. nodejs12 < nodejs14 < nodejs18 < nodejs20 ... + return runtimes.sort()[runtimes.length - 1]; } diff --git a/src/serve/functions.ts b/src/serve/functions.ts index 6424eed384c..921ee9f4c06 100644 --- a/src/serve/functions.ts +++ b/src/serve/functions.ts @@ -4,7 +4,6 @@ import { FunctionsEmulator, FunctionsEmulatorArgs, } from "../emulator/functionsEmulator"; -import { parseRuntimeVersion } from "../emulator/functionsEmulatorUtils"; import { needProjectId } from "../projectUtils"; import { getProjectDefaultAccount } from "../auth"; import { Options } from "../options"; @@ -30,11 +29,10 @@ export class FunctionsServer { const backends: EmulatableBackend[] = []; for (const cfg of config) { const functionsDir = path.join(options.config.projectDir, cfg.source); - const nodeMajorVersion = parseRuntimeVersion(cfg.runtime); backends.push({ functionsDir, codebase: cfg.codebase, - nodeMajorVersion, + runtime: cfg.runtime, env: {}, secretEnv: [], }); diff --git a/src/test/deploy/functions/runtimes/node/index.spec.ts b/src/test/deploy/functions/runtimes/node/index.spec.ts new file mode 100644 index 00000000000..5ad9aa3c387 --- /dev/null +++ b/src/test/deploy/functions/runtimes/node/index.spec.ts @@ -0,0 +1,74 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import * as path from "path"; + +import * as node from "../../../../../deploy/functions/runtimes/node"; +import * as versioning from "../../../../../deploy/functions/runtimes/node/versioning"; +import * as utils from "../../../../../utils"; +import { FirebaseError } from "../../../../../error"; + +const PROJECT_ID = "test-project"; +const PROJECT_DIR = "/some/path"; +const SOURCE_DIR = "/some/path/fns"; + +describe("NodeDelegate", () => { + describe("getNodeBinary", () => { + let warnSpy: sinon.SinonSpy; + let successSpy: sinon.SinonSpy; + let hostVersionMock: sinon.SinonStub; + let localVersionMock: sinon.SinonStub; + + beforeEach(() => { + warnSpy = sinon.spy(utils, "logLabeledWarning"); + successSpy = sinon.spy(utils, "logLabeledSuccess"); + hostVersionMock = sinon.stub(process, "versions"); + localVersionMock = sinon.stub(versioning, "findModuleVersion"); + }); + + afterEach(() => { + warnSpy.restore(); + successSpy.restore(); + hostVersionMock.restore(); + localVersionMock.restore(); + }); + + it("prefers locally cached node version if matched with requested version", () => { + localVersionMock.returns("12.0.0"); + hostVersionMock.value({ node: "14.5.0" }); + const requestedRuntime = "nodejs12"; + const delegate = new node.Delegate(PROJECT_ID, PROJECT_DIR, SOURCE_DIR, requestedRuntime); + expect(delegate.getNodeBinary()).to.equal(path.join(SOURCE_DIR, "node_modules", "node")); + expect(successSpy).to.have.been.calledWith( + "functions", + sinon.match("node@12 from local cache.") + ); + expect(warnSpy).to.not.have.been.called; + }); + + it("checks if requested and hosted runtime version matches", () => { + hostVersionMock.value({ node: "12.5.0" }); + const requestedRuntime = "nodejs12"; + const delegate = new node.Delegate(PROJECT_ID, PROJECT_DIR, SOURCE_DIR, requestedRuntime); + expect(delegate.getNodeBinary()).to.equal(process.execPath); + expect(successSpy).to.have.been.calledWith("functions", sinon.match("node@12 from host.")); + expect(warnSpy).to.not.have.been.called; + }); + + it("warns users if hosted and requested runtime version differs", () => { + hostVersionMock.value({ node: "12.0.0" }); + const requestedRuntime = "nodejs10"; + const delegate = new node.Delegate(PROJECT_ID, PROJECT_DIR, SOURCE_DIR, requestedRuntime); + + expect(delegate.getNodeBinary()).to.equal(process.execPath); + expect(successSpy).to.not.have.been.called; + expect(warnSpy).to.have.been.calledWith("functions", sinon.match("doesn't match")); + }); + + it("throws errors if requested runtime version is invalid", () => { + const invalidRuntime = "foobar"; + const delegate = new node.Delegate(PROJECT_ID, PROJECT_DIR, SOURCE_DIR, invalidRuntime); + + expect(() => delegate.getNodeBinary()).to.throw(FirebaseError); + }); + }); +}); diff --git a/src/test/emulators/extensionsEmulator.spec.ts b/src/test/emulators/extensionsEmulator.spec.ts index 93093146ea8..2b6db2488f3 100644 --- a/src/test/emulators/extensionsEmulator.spec.ts +++ b/src/test/emulators/extensionsEmulator.spec.ts @@ -109,7 +109,7 @@ describe("Extensions Emulator", () => { // so test also runs on win machines // eslint-disable-next-line prettier/prettier functionsDir: join("src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions"), - nodeMajorVersion: 10, + runtime: "nodejs10", predefinedTriggers: [ { entryPoint: "generateResizedImage", @@ -140,6 +140,8 @@ describe("Extensions Emulator", () => { }); const result = await e.toEmulatableBackend(testCase.input); + // ignore result.bin, as it is platform dependent + delete result.bin; expect(result).to.deep.equal(testCase.expected); }); } diff --git a/src/test/extensions/emulator/specHelper.spec.ts b/src/test/extensions/emulator/specHelper.spec.ts new file mode 100644 index 00000000000..de4c917326c --- /dev/null +++ b/src/test/extensions/emulator/specHelper.spec.ts @@ -0,0 +1,82 @@ +import { expect } from "chai"; + +import * as specHelper from "../../../extensions/emulator/specHelper"; +import { Resource } from "../../../extensions/types"; +import { FirebaseError } from "../../../error"; + +const testResource: Resource = { + name: "test-resource", + entryPoint: "functionName", + type: "firebaseextensions.v1beta.function", + properties: { + timeout: "3s", + location: "us-east1", + availableMemoryMb: 1024, + }, +}; + +describe("getRuntime", () => { + it("gets runtime of resources", () => { + const r1 = { + ...testResource, + properties: { + runtime: "nodejs14", + }, + }; + const r2 = { + ...testResource, + properties: { + runtime: "nodejs14", + }, + }; + expect(specHelper.getRuntime([r1, r2])).to.equal("nodejs14"); + }); + + it("chooses the latest runtime if many runtime exists", () => { + const r1 = { + ...testResource, + properties: { + runtime: "nodejs12", + }, + }; + const r2 = { + ...testResource, + properties: { + runtime: "nodejs14", + }, + }; + expect(specHelper.getRuntime([r1, r2])).to.equal("nodejs14"); + }); + + it("returns default runtime if none specified", () => { + const r1 = { + ...testResource, + properties: {}, + }; + const r2 = { + ...testResource, + properties: {}, + }; + expect(specHelper.getRuntime([r1, r2])).to.equal(specHelper.DEFAULT_RUNTIME); + }); + + it("returns default runtime given no resources", () => { + expect(specHelper.getRuntime([])).to.equal(specHelper.DEFAULT_RUNTIME); + }); + + it("throws error given invalid runtime", () => { + const r1 = { + ...testResource, + properties: { + runtime: "dotnet6", + }, + }; + const r2 = { + ...testResource, + properties: { + runtime: "nodejs14", + }, + }; + expect(() => specHelper.getRuntime([r1, r2])).to.throw(FirebaseError); + }); +}); From 58f81d046a19e550025e71d0ca0b66317645282b Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 20 Jan 2023 20:52:20 -0800 Subject: [PATCH 0764/1699] Randomize port used for function discovery to reduce race conditions. (#5444) --- CHANGELOG.md | 1 + src/deploy/functions/runtimes/node/index.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8850284b597..527a409d2f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Refactor Functions Emulator. (#5422) +- Fix race condition when discovering functions. (#5444) diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 3810ec1dbda..cd387503d9b 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -9,7 +9,7 @@ import fetch from "node-fetch"; import { FirebaseError } from "../../../../error"; import { getRuntimeChoice } from "./parseRuntimeAndValidateSDK"; import { logger } from "../../../../logger"; -import { logLabeledSuccess, logLabeledWarning } from "../../../../utils"; +import { logLabeledSuccess, logLabeledWarning, randomInt } from "../../../../utils"; import * as backend from "../../backend"; import * as build from "../../build"; import * as discovery from "../discovery"; @@ -220,8 +220,8 @@ export class Delegate { let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime); if (!discovered) { - const getPort = promisify(portfinder.getPort) as () => Promise; - const port = await getPort(); + const basePort = 8000 + randomInt(0, 1000); // Add a jitter to reduce likelihood of race condition + const port = await portfinder.getPortPromise({ port: basePort }); const kill = await this.serveAdmin(port.toString(), config, env); try { discovered = await discovery.detectFromPort(port, this.projectId, this.runtime); From a447270e8ab2ae9c50a3572d7f5aa2d8b9dee209 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 24 Jan 2023 11:10:37 -0800 Subject: [PATCH 0765/1699] Add support for advanced params during ext:install and ext:configure (#5437) * Add support for advanced params during ext:install and ext:configure * Correct condition for advanced params --- src/extensions/askUserForParam.ts | 33 ++++++++++++++++++++++++++++++- src/extensions/types.ts | 1 + 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index 394bc326739..ed5472b746f 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -13,6 +13,7 @@ import { promptOnce } from "../prompt"; import * as utils from "../utils"; import { ParamBindingOptions } from "./paramHelper"; import { needProjectId } from "../projectUtils"; +import { partition } from "../functional"; /** * Location where the secret value is stored. @@ -91,8 +92,9 @@ export async function ask(args: { utils.logLabeledBullet(logPrefix, "answer the questions below to configure your extension:"); const substituted = substituteParams(args.paramSpecs, args.firebaseProjectParams); + const [advancedParams, standardParams] = partition(substituted, (p) => p.advanced ?? false); const result: { [key: string]: ParamBindingOptions } = {}; - const promises = substituted.map((paramSpec) => { + const promises = standardParams.map((paramSpec) => { return async () => { result[paramSpec.param] = await askForParam({ projectId: args.projectId, @@ -102,8 +104,37 @@ export async function ask(args: { }); }; }); + if (advancedParams.length) { + promises.push(async () => { + const shouldPrompt = await promptOnce({ + type: "confirm", + message: "Do you want to configure any advanced parameters for this instance?", + default: false, + }); + if (shouldPrompt) { + const advancedPromises = advancedParams.map((paramSpec) => { + return async () => { + result[paramSpec.param] = await askForParam({ + projectId: args.projectId, + instanceId: args.instanceId, + paramSpec: paramSpec, + reconfiguring: args.reconfiguring, + }); + }; + }); + await advancedPromises.reduce((prev, cur) => prev.then(cur as any), Promise.resolve()); + } else { + for (const paramSpec of advancedParams) { + if (paramSpec.required && paramSpec.default) { + result[paramSpec.param] = { baseValue: paramSpec.default }; + } + } + } + }); + } // chaining together the promises so they get executed one after another await promises.reduce((prev, cur) => prev.then(cur as any), Promise.resolve()); + logger.info(); return result; } diff --git a/src/extensions/types.ts b/src/extensions/types.ts index a0de32e54c3..e5bc4b5481b 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -221,6 +221,7 @@ export interface Param { validationErrorMessage?: string; immutable?: boolean; example?: string; + advanced?: boolean; } export enum ParamType { From 9c61285674c708e21bc02a9617e3f451dd1dd078 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 24 Jan 2023 13:03:37 -0800 Subject: [PATCH 0766/1699] upgrade swagger2openapi (#5458) * upgrade swagger2openapi * regenerate auth-api --- npm-shrinkwrap.json | 677 +++++++++++------------------------ package.json | 2 +- src/emulator/auth/apiSpec.ts | 4 + src/emulator/auth/schema.ts | 4 + 4 files changed, 210 insertions(+), 477 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 01945d1a547..8d664761f7a 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -152,7 +152,7 @@ "sinon-chai": "^3.6.0", "source-map-support": "^0.5.9", "supertest": "^6.2.3", - "swagger2openapi": "^6.0.3", + "swagger2openapi": "^7.0.8", "ts-node": "^10.4.0", "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1", @@ -567,18 +567,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", @@ -837,9 +825,9 @@ } }, "node_modules/@exodus/schemasafe": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.2.tgz", - "integrity": "sha512-W98NvvOe/Med3o66xTO03pd7a2omZebH79PV64gSE+ceDdU8uxQhFTa7ISiD1kseyqyOrMyW5/MNdsGEU02i3Q==", + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.9.tgz", + "integrity": "sha512-dGGHpb61hLwifAu7sotuHFDBw6GTdpG8aKC0fsK17EuTzMRvUrH7lEAr6LTJ+sx3AZYed9yZ77rltVDHyg2hRg==", "dev": true }, "node_modules/@fastify/busboy": { @@ -4164,24 +4152,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/better-ajv-errors": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-0.6.7.tgz", - "integrity": "sha512-PYgt/sCzR4aGpyNy5+ViSQ77ognMnWq7745zM+/flYO4/Yisdtp9wDQW2IKCyVYPUxQt3E/b5GBSwfhd1LPdlg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/runtime": "^7.0.0", - "chalk": "^2.4.1", - "core-js": "^3.2.1", - "json-to-ast": "^2.0.3", - "jsonpointer": "^4.0.1", - "leven": "^3.1.0" - }, - "peerDependencies": { - "ajv": "4.11.8 - 6" - } - }, "node_modules/big-integer": { "version": "1.6.48", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", @@ -4914,25 +4884,6 @@ "node": ">=0.8" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/code-error-fragment": { - "version": "0.0.230", - "resolved": "https://registry.npmjs.org/code-error-fragment/-/code-error-fragment-0.0.230.tgz", - "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", @@ -8370,12 +8321,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -8643,9 +8588,9 @@ } }, "node_modules/http2-client": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.3.tgz", - "integrity": "sha512-nUxLymWQ9pzkzTmir24p2RtsgruLmhje7lH3hLX1IpwvyTg77fW+1brenPPP3USAR+rQ36p5sTA/x7sjCJVkAA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "dev": true }, "node_modules/https-proxy-agent": { @@ -9466,19 +9411,6 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "node_modules/json-to-ast": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json-to-ast/-/json-to-ast-2.1.0.tgz", - "integrity": "sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==", - "dev": true, - "dependencies": { - "code-error-fragment": "0.0.230", - "grapheme-splitter": "^1.0.4" - }, - "engines": { - "node": ">= 4" - } - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9544,15 +9476,6 @@ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", "dev": true }, - "node_modules/jsonpointer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", - "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", @@ -11287,9 +11210,9 @@ } }, "node_modules/oas-linter": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.0.tgz", - "integrity": "sha512-LP5F1dhjULEJV5oGRg6ROztH2FddzttrrUEwq5J2GB2Zy938mg0vwt1+Rthn/qqDHtj4Qgq21duNGHh+Ew1wUg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", "dev": true, "dependencies": { "@exodus/schemasafe": "^1.0.0-rc.2", @@ -11301,16 +11224,16 @@ } }, "node_modules/oas-resolver": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.3.tgz", - "integrity": "sha512-y4gP5tabqP3YcNVHNAEJAlcqZ40Y9lxemzmXvt54evbrvuGiK5dEhuE33Rf+191TOwzlxMoIgbwMYeuOM7BwjA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", "dev": true, "dependencies": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", - "reftools": "^1.1.7", + "reftools": "^1.1.9", "yaml": "^1.10.0", - "yargs": "^16.1.1" + "yargs": "^17.0.1" }, "bin": { "resolve": "resolve.js" @@ -11319,13 +11242,54 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/oas-resolver/node_modules/reftools": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.7.tgz", - "integrity": "sha512-I+KZFkQvZjMZqVWxRezTC/kQ2kLhGRZ7C+4ARbgmb5WJbvFUlbrZ/6qlz6mb+cGcPNYib+xqL8kZlxCsSZ7Hew==", + "node_modules/oas-resolver/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==", "dev": true, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/oas-resolver/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/oas-resolver/node_modules/yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/oas-resolver/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==", + "dev": true, + "engines": { + "node": ">=12" } }, "node_modules/oas-schema-walker": { @@ -11338,50 +11302,24 @@ } }, "node_modules/oas-validator": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-4.0.8.tgz", - "integrity": "sha512-bIt8erTyclF7bkaySTtQ9sppqyVc+mAlPi7vPzCLVHJsL9nrivQjc/jHLX/o+eGbxHd6a6YBwuY/Vxa6wGsiuw==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", "dev": true, "dependencies": { - "ajv": "^5.5.2", - "better-ajv-errors": "^0.6.7", "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.8", - "oas-linter": "^3.1.3", - "oas-resolver": "^2.4.3", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", "oas-schema-walker": "^1.1.5", - "reftools": "^1.1.5", + "reftools": "^1.1.9", "should": "^13.2.1", - "yaml": "^1.8.3" + "yaml": "^1.10.0" }, "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/oas-validator/node_modules/ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", - "dev": true, - "dependencies": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "node_modules/oas-validator/node_modules/fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "node_modules/oas-validator/node_modules/json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -12619,20 +12557,14 @@ } }, "node_modules/reftools": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.6.tgz", - "integrity": "sha512-rQfJ025lvPjw9qyQuNPqE+cRs5qVs7BMrZwgRJnmuMcX/8r/eJE8f5/RCunJWViXKHmN5K2DFafYzglLOHE/tw==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", "dev": true, "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -13137,7 +13069,7 @@ "node_modules/should-format": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", "dev": true, "dependencies": { "should-type": "^1.3.0", @@ -13147,7 +13079,7 @@ "node_modules/should-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", "dev": true }, "node_modules/should-type-adaptors": { @@ -13813,22 +13745,22 @@ } }, "node_modules/swagger2openapi": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-6.2.3.tgz", - "integrity": "sha512-cUUktzLpK69UwpMbcTzjMw2ns9RZChfxh56AHv6+hTx3StPOX2foZjPgds3HlJcINbxosYYBn/D3cG8nwcCWwQ==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", "dev": true, "dependencies": { - "better-ajv-errors": "^0.6.1", "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", "node-fetch-h2": "^2.3.0", "node-readfiles": "^0.2.0", "oas-kit-common": "^1.0.8", - "oas-resolver": "^2.4.3", + "oas-resolver": "^2.5.6", "oas-schema-walker": "^1.1.5", - "oas-validator": "^4.0.8", - "reftools": "^1.1.5", - "yaml": "^1.8.3", - "yargs": "^15.3.1" + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" }, "bin": { "boast": "boast.js", @@ -13839,135 +13771,54 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, - "node_modules/swagger2openapi/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/swagger2openapi/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "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==", "dev": true, "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/swagger2openapi/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/swagger2openapi/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==", - "dev": true - }, - "node_modules/swagger2openapi/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, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/swagger2openapi/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, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/swagger2openapi/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, - "dependencies": { - "p-limit": "^2.2.0" + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/swagger2openapi/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/swagger2openapi/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/swagger2openapi/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", "dev": true, "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "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.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/swagger2openapi/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, "engines": { - "node": ">=6" + "node": ">=12" } }, "node_modules/tar": { @@ -15706,15 +15557,6 @@ "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", "dev": true }, - "@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "@babel/template": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", @@ -15895,9 +15737,9 @@ } }, "@exodus/schemasafe": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.2.tgz", - "integrity": "sha512-W98NvvOe/Med3o66xTO03pd7a2omZebH79PV64gSE+ceDdU8uxQhFTa7ISiD1kseyqyOrMyW5/MNdsGEU02i3Q==", + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.9.tgz", + "integrity": "sha512-dGGHpb61hLwifAu7sotuHFDBw6GTdpG8aKC0fsK17EuTzMRvUrH7lEAr6LTJ+sx3AZYed9yZ77rltVDHyg2hRg==", "dev": true }, "@fastify/busboy": { @@ -18628,21 +18470,6 @@ "tweetnacl": "^0.14.3" } }, - "better-ajv-errors": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-0.6.7.tgz", - "integrity": "sha512-PYgt/sCzR4aGpyNy5+ViSQ77ognMnWq7745zM+/flYO4/Yisdtp9wDQW2IKCyVYPUxQt3E/b5GBSwfhd1LPdlg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/runtime": "^7.0.0", - "chalk": "^2.4.1", - "core-js": "^3.2.1", - "json-to-ast": "^2.0.3", - "jsonpointer": "^4.0.1", - "leven": "^3.1.0" - } - }, "big-integer": { "version": "1.6.48", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", @@ -19168,18 +18995,6 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-error-fragment": { - "version": "0.0.230", - "resolved": "https://registry.npmjs.org/code-error-fragment/-/code-error-fragment-0.0.230.tgz", - "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==", - "dev": true - }, "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", @@ -21752,12 +21567,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -21966,9 +21775,9 @@ } }, "http2-client": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.3.tgz", - "integrity": "sha512-nUxLymWQ9pzkzTmir24p2RtsgruLmhje7lH3hLX1IpwvyTg77fW+1brenPPP3USAR+rQ36p5sTA/x7sjCJVkAA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "dev": true }, "https-proxy-agent": { @@ -22599,16 +22408,6 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "json-to-ast": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json-to-ast/-/json-to-ast-2.1.0.tgz", - "integrity": "sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==", - "dev": true, - "requires": { - "code-error-fragment": "0.0.230", - "grapheme-splitter": "^1.0.4" - } - }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -22660,12 +22459,6 @@ } } }, - "jsonpointer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", - "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", - "dev": true - }, "jsonwebtoken": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", @@ -24022,9 +23815,9 @@ } }, "oas-linter": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.0.tgz", - "integrity": "sha512-LP5F1dhjULEJV5oGRg6ROztH2FddzttrrUEwq5J2GB2Zy938mg0vwt1+Rthn/qqDHtj4Qgq21duNGHh+Ew1wUg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", "dev": true, "requires": { "@exodus/schemasafe": "^1.0.0-rc.2", @@ -24033,22 +23826,54 @@ } }, "oas-resolver": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.3.tgz", - "integrity": "sha512-y4gP5tabqP3YcNVHNAEJAlcqZ40Y9lxemzmXvt54evbrvuGiK5dEhuE33Rf+191TOwzlxMoIgbwMYeuOM7BwjA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", "dev": true, "requires": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", - "reftools": "^1.1.7", + "reftools": "^1.1.9", "yaml": "^1.10.0", - "yargs": "^16.1.1" + "yargs": "^17.0.1" }, "dependencies": { - "reftools": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.7.tgz", - "integrity": "sha512-I+KZFkQvZjMZqVWxRezTC/kQ2kLhGRZ7C+4ARbgmb5WJbvFUlbrZ/6qlz6mb+cGcPNYib+xqL8kZlxCsSZ7Hew==", + "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==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "dev": true, + "requires": { + "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" + } + }, + "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==", "dev": true } } @@ -24060,47 +23885,19 @@ "dev": true }, "oas-validator": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-4.0.8.tgz", - "integrity": "sha512-bIt8erTyclF7bkaySTtQ9sppqyVc+mAlPi7vPzCLVHJsL9nrivQjc/jHLX/o+eGbxHd6a6YBwuY/Vxa6wGsiuw==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", "dev": true, "requires": { - "ajv": "^5.5.2", - "better-ajv-errors": "^0.6.7", "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.8", - "oas-linter": "^3.1.3", - "oas-resolver": "^2.4.3", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", "oas-schema-walker": "^1.1.5", - "reftools": "^1.1.5", + "reftools": "^1.1.9", "should": "^13.2.1", - "yaml": "^1.8.3" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - } + "yaml": "^1.10.0" } }, "oauth-sign": { @@ -25037,15 +24834,9 @@ } }, "reftools": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.6.tgz", - "integrity": "sha512-rQfJ025lvPjw9qyQuNPqE+cRs5qVs7BMrZwgRJnmuMcX/8r/eJE8f5/RCunJWViXKHmN5K2DFafYzglLOHE/tw==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", "dev": true }, "regexpp": { @@ -25434,7 +25225,7 @@ "should-format": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", "dev": true, "requires": { "should-type": "^1.3.0", @@ -25444,7 +25235,7 @@ "should-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", "dev": true }, "should-type-adaptors": { @@ -25971,127 +25762,61 @@ "dev": true }, "swagger2openapi": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-6.2.3.tgz", - "integrity": "sha512-cUUktzLpK69UwpMbcTzjMw2ns9RZChfxh56AHv6+hTx3StPOX2foZjPgds3HlJcINbxosYYBn/D3cG8nwcCWwQ==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", "dev": true, "requires": { - "better-ajv-errors": "^0.6.1", "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", "node-fetch-h2": "^2.3.0", "node-readfiles": "^0.2.0", "oas-kit-common": "^1.0.8", - "oas-resolver": "^2.4.3", + "oas-resolver": "^2.5.6", "oas-schema-walker": "^1.1.5", - "oas-validator": "^4.0.8", - "reftools": "^1.1.5", - "yaml": "^1.8.3", - "yargs": "^15.3.1" + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "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==", "dev": true, "requires": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" } }, - "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==", + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, - "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, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "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, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "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.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" } }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true } } }, diff --git a/package.json b/package.json index ca69b142cb7..9653e259f2a 100644 --- a/package.json +++ b/package.json @@ -234,7 +234,7 @@ "sinon-chai": "^3.6.0", "source-map-support": "^0.5.9", "supertest": "^6.2.3", - "swagger2openapi": "^6.0.3", + "swagger2openapi": "^7.0.8", "ts-node": "^10.4.0", "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1", diff --git a/src/emulator/auth/apiSpec.ts b/src/emulator/auth/apiSpec.ts index 125afaa542d..c62f8f2387e 100644 --- a/src/emulator/auth/apiSpec.ts +++ b/src/emulator/auth/apiSpec.ts @@ -4733,6 +4733,10 @@ export default { description: "Response message for GetRecaptchaParam.", properties: { kind: { type: "string" }, + producerProjectNumber: { + description: "The producer project number used to generate PIA tokens", + type: "string", + }, recaptchaSiteKey: { description: "The reCAPTCHA v2 site key used to invoke the reCAPTCHA service. Always present.", diff --git a/src/emulator/auth/schema.ts b/src/emulator/auth/schema.ts index 2f977a49c4f..fa8d7a47174 100644 --- a/src/emulator/auth/schema.ts +++ b/src/emulator/auth/schema.ts @@ -511,6 +511,10 @@ export interface components { */ GoogleCloudIdentitytoolkitV1GetRecaptchaParamResponse: { kind?: string; + /** + * The producer project number used to generate PIA tokens + */ + producerProjectNumber?: string; /** * The reCAPTCHA v2 site key used to invoke the reCAPTCHA service. Always present. */ From 992ba5c7caddff4b44adba4c48431e360f24de00 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 24 Jan 2023 14:10:33 -0800 Subject: [PATCH 0767/1699] update firebase and firebase-admin (#5459) * blindly upgrade fireabse and firebase-admin * fix the tests! * clean up * remove type check --- npm-shrinkwrap.json | 4634 ++++++++++------- package.json | 4 +- .../conformance/firebase-js-sdk.test.ts | 88 +- .../conformance/gcs-js-sdk.test.ts | 5 +- .../conformance/persistence.test.ts | 7 +- scripts/storage-emulator-integration/run.sh | 1 - src/test/hosting/initMiddleware.spec.ts | 2 +- 7 files changed, 2826 insertions(+), 1915 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8d664761f7a..7269a446197 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -134,8 +134,8 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-jsdoc": "^39.2.9", "eslint-plugin-prettier": "^4.0.0", - "firebase": "^7.24.0", - "firebase-admin": "^10.0.0", + "firebase": "^9.16.0", + "firebase-admin": "^11.5.0", "firebase-functions": "^4.1.0", "google-discovery-to-swagger": "^2.1.0", "googleapis": "^105.0.0", @@ -559,7 +559,6 @@ "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -843,162 +842,223 @@ } }, "node_modules/@firebase/analytics": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.6.0.tgz", - "integrity": "sha512-6qYEOPUVYrMhqvJ46Z5Uf1S4uULd6d7vGpMP5Qz+u8kIWuOQGcPdJKQap+Hla6Rq164or9gC2HRXuYXKlgWfpw==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.9.1.tgz", + "integrity": "sha512-ARXtNHDrjDhVrs5MqmFDpr5yyCw89r1eHLd+Dw9fotAufxL1WTmo6O9bJqKb7QulIJaA84vsFokA3NaO2DNCnQ==", "dev": true, "dependencies": { - "@firebase/analytics-types": "0.4.0", - "@firebase/component": "0.1.19", - "@firebase/installations": "0.4.17", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.1.tgz", + "integrity": "sha512-qfFAGS4YFsBbmZwVa7xaDnGh7k9BKF4o/piyjySAv0lxRYd74/tSrm3kMk1YM7GCti7PdbgKvl6oSR70zMFQjw==", + "dev": true, + "dependencies": { + "@firebase/analytics": "0.9.1", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.1", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, + "node_modules/@firebase/analytics-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, "node_modules/@firebase/analytics-types": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.4.0.tgz", - "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==", + "dev": true + }, + "node_modules/@firebase/analytics/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@firebase/analytics/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "node_modules/@firebase/app": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.1.tgz", + "integrity": "sha512-Z8wOSol+pvp4CFyY1mW+aqdZlrwhW/ha2YXQ6/avJ56c5Hnvt4k6GktZE6o5NyzvfJTgNHryhMtnEJMIuLaT4w==", "dev": true, "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "idb": "7.0.1", + "tslib": "^2.1.0" } }, - "node_modules/@firebase/analytics/node_modules/@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", - "dev": true - }, - "node_modules/@firebase/analytics/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "node_modules/@firebase/app-check": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.6.1.tgz", + "integrity": "sha512-gDG4Gr4n3MnBZAAwLMynU9u/b+f1y87lCezfwlmN1gUxD85mJcvp4hLf87fACTyRkdVfe8hqSXm+MOYn2bMGLg==", "dev": true, "dependencies": { - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@firebase/app": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.11.tgz", - "integrity": "sha512-FH++PaoyTzfTAVuJ0gITNYEIcjT5G+D0671La27MU8Vvr6MTko+5YUZ4xS9QItyotSeRF4rMJ1KR7G8LSyySiA==", + "node_modules/@firebase/app-check-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.1.tgz", + "integrity": "sha512-IaSYdmaoQgWUrN6rjAYJs1TGXj38Wl9damtrDEyJBf7+rrvKshPAP/CP6e2bd89XOMZKbvy8rKoe1CqX1K3ZjQ==", "dev": true, "dependencies": { - "@firebase/app-types": "0.6.1", - "@firebase/component": "0.1.19", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "dom-storage": "2.1.0", - "tslib": "^1.11.1", - "xmlhttprequest": "1.8.0" + "@firebase/app-check": "0.6.1", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/app-types": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", - "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==", + "node_modules/@firebase/app-check-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@firebase/app/node_modules/@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "node_modules/@firebase/app-check-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.2.0.tgz", + "integrity": "sha512-+3PQIeX6/eiVK+x/yg8r6xTNR97fN7MahFDm+jiQmDjcyvSefoGuTTNQuuMScGyx3vYUBeZn+Cp9kC0yY/9uxQ==", "dev": true }, - "node_modules/@firebase/app/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } + "node_modules/@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==", + "dev": true }, - "node_modules/@firebase/app/node_modules/@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "node_modules/@firebase/app-check/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@firebase/app/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "node_modules/@firebase/app-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.1.tgz", + "integrity": "sha512-UgPy2ZO0li0j4hAkaZKY9P1TuJEx5RylhUWPzCb8DZhBm+uHdfsFI9Yr+wMlu6qQH2sWoweFtYU6ljGzxwdctw==", "dev": true, "dependencies": { - "tslib": "^1.11.1" + "@firebase/app": "0.9.1", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" } }, + "node_modules/@firebase/app-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==", + "dev": true + }, + "node_modules/@firebase/app/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, "node_modules/@firebase/auth": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.15.0.tgz", - "integrity": "sha512-IFuzhxS+HtOQl7+SZ/Mhaghy/zTU7CENsJFWbC16tv2wfLZbayKF5jYGdAU3VFLehgC8KjlcIWd10akc3XivfQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.21.1.tgz", + "integrity": "sha512-/ap7eT9X7kZTD4Fn2m+nJyC1a9DfFo0H4euoJDN8U+JCMN+GOqkPbkMWCey7wV510WNoPCZQ05+nsAqKkbEVJw==", "dev": true, "dependencies": { - "@firebase/auth-types": "0.10.1" + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", - "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", + "node_modules/@firebase/auth-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.3.1.tgz", + "integrity": "sha512-Ndcaam+IL1TuJ6hZ0EcQ+v261cK3kPm4mvUtouoTfl3FNinm9XvhccN8ojuaRtIV9TiY18mzGjONKF5ZCXLIZw==", "dev": true, + "dependencies": { + "@firebase/auth": "0.21.1", + "@firebase/auth-types": "0.12.0", + "@firebase/component": "0.6.1", + "@firebase/util": "1.9.0", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" + "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/auth/node_modules/@firebase/auth-types": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", - "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", + "node_modules/@firebase/auth-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==", + "dev": true + }, + "node_modules/@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", "dev": true, "peerDependencies": { "@firebase/app-types": "0.x", - "@firebase/util": "0.x" - } - }, - "node_modules/@firebase/auth/node_modules/@firebase/util": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.4.1.tgz", - "integrity": "sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.1.0" + "@firebase/util": "1.x" } }, "node_modules/@firebase/auth/node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@firebase/component": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", - "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.1.tgz", + "integrity": "sha512-yvKthG0InjFx9aOPnh6gk0lVNfNVEtyq3LwXgZr+hOwD0x/CtXq33XCpqv0sQj5CA4FdMy8OO+y9edI+ZUw8LA==", "dev": true, "dependencies": { - "@firebase/util": "1.7.3", + "@firebase/util": "1.9.0", "tslib": "^2.1.0" } }, @@ -1009,43 +1069,33 @@ "dev": true }, "node_modules/@firebase/database": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", - "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.1.tgz", + "integrity": "sha512-iX6/p7hoxUMbYAGZD+D97L05xQgpkslF2+uJLZl46EdaEfjVMEwAdy7RS/grF96kcFZFg502LwPYTXoIdrZqOA==", "dev": true, "dependencies": { - "@firebase/auth-interop-types": "0.1.7", - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "node_modules/@firebase/database-compat": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", - "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.1.tgz", + "integrity": "sha512-sI7LNh0C8PCq9uUKjrBKLbZvqHTSjsf2LeZRxin+rHVegomjsOAYk9OzYwxETWh3URhpMkCM8KcTl7RVwAldog==", "dev": true, "dependencies": { - "@firebase/component": "0.5.21", - "@firebase/database": "0.13.10", - "@firebase/database-types": "0.9.17", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", + "@firebase/component": "0.6.1", + "@firebase/database": "0.14.1", + "@firebase/database-types": "0.10.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", "tslib": "^2.1.0" } }, - "node_modules/@firebase/database-compat/node_modules/@firebase/database-types": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", - "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", - "dev": true, - "dependencies": { - "@firebase/app-types": "0.8.1", - "@firebase/util": "1.7.3" - } - }, "node_modules/@firebase/database-compat/node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", @@ -1053,20 +1103,15 @@ "dev": true }, "node_modules/@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.1.tgz", + "integrity": "sha512-UgUx9VakTHbP2WrVUdYrUT2ofTFVfWjGW2O1fwuvvMyo6WSnuSyO5nB1u0cyoMPvO25dfMIUVerfK7qFfwGL3Q==", "dev": true, "dependencies": { - "@firebase/app-types": "0.6.1" + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.0" } }, - "node_modules/@firebase/database-types/node_modules/@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", - "dev": true - }, "node_modules/@firebase/database/node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", @@ -1074,185 +1119,173 @@ "dev": true }, "node_modules/@firebase/firestore": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.18.0.tgz", - "integrity": "sha512-maMq4ltkrwjDRusR2nt0qS4wldHQMp+0IDSfXIjC+SNmjnWY/t/+Skn9U3Po+dB38xpz3i7nsKbs+8utpDnPSw==", - "dev": true, - "dependencies": { - "@firebase/component": "0.1.19", - "@firebase/firestore-types": "1.14.0", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "@firebase/webchannel-wrapper": "0.4.0", - "@grpc/grpc-js": "^1.0.0", - "@grpc/proto-loader": "^0.5.0", - "node-fetch": "2.6.1", - "tslib": "^1.11.1" + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.8.1.tgz", + "integrity": "sha512-oc2HMkUnq/zF+g9o974tp5RVCdXCnrU8e5S98ajfWG/hGV+8pr4i6vIa4z0yEXKWGi4X0FguxrC69z1dxEJbNg==", + "dev": true, + "dependencies": { + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "@firebase/webchannel-wrapper": "0.9.0", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=10.10.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" - } - }, - "node_modules/@firebase/firestore-types": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.14.0.tgz", - "integrity": "sha512-WF8IBwHzZDhwyOgQnmB0pheVrLNP78A8PGxk1nxb/Nrgh1amo4/zYvFMGgSsTeaQK37xMYS/g7eS948te/dJxw==", - "dev": true, - "peerDependencies": { - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" } }, - "node_modules/@firebase/firestore/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "node_modules/@firebase/firestore-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.1.tgz", + "integrity": "sha512-7eE4O2ASyy5X2h4a+KCRt0ZpliUAKo2jrKxKl1ZVCnOOjSCkXXeRVRG9eNZRqBwukhdwskJTM9acs0WxmKOYLA==", "dev": true, "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/firestore": "3.8.1", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/firestore/node_modules/@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "node_modules/@firebase/firestore-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@firebase/firestore/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "node_modules/@firebase/firestore-types": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", "dev": true, - "dependencies": { - "tslib": "^1.11.1" + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" } }, - "node_modules/@firebase/firestore/node_modules/@grpc/proto-loader": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz", - "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", + "node_modules/@firebase/firestore/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, + "node_modules/@firebase/functions": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.9.1.tgz", + "integrity": "sha512-xCSSU4aVSqYU+lCqhn9o5jJcE1KLUOOKyJfCTdCSCyTn2J3vl9Vk4TDm3JSb1Eu6XsNWtxeMW188F/GYxuMWcw==", "dev": true, "dependencies": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" + "@firebase/app-check-interop-types": "0.2.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.1", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.0", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@firebase/firestore/node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true, - "engines": { - "node": "4.x || >=6.0.0" + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@firebase/functions": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.5.1.tgz", - "integrity": "sha512-yyjPZXXvzFPjkGRSqFVS5Hc2Y7Y48GyyMH+M3i7hLGe69r/59w6wzgXKqTiSYmyE1pxfjxU4a1YqBDHNkQkrYQ==", + "node_modules/@firebase/functions-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.1.tgz", + "integrity": "sha512-f2D2XoRN+QCziCrUL7UrLaBEoG3v2iAeyNwbbOQ3vv0rI0mtku2/yeB2OINz5/iI6oIrBPUMNLr5fitofj7FpQ==", "dev": true, "dependencies": { - "@firebase/component": "0.1.19", - "@firebase/functions-types": "0.3.17", - "@firebase/messaging-types": "0.5.0", - "node-fetch": "2.6.1", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/functions": "0.9.1", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app-compat": "0.x" } }, + "node_modules/@firebase/functions-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, "node_modules/@firebase/functions-types": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.17.tgz", - "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==", "dev": true }, - "node_modules/@firebase/functions/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } + "node_modules/@firebase/functions/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, - "node_modules/@firebase/functions/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "node_modules/@firebase/installations": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.1.tgz", + "integrity": "sha512-gpobP09LLLakBfNCL04fyblfyb3oX1pn+iNmELygrcAkXTO13IAMuOzThI+Xk4NHQZMX1p5GFSAiGbG4yfsSUQ==", "dev": true, "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@firebase/functions/node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true, - "engines": { - "node": "4.x || >=6.0.0" + "@firebase/component": "0.6.1", + "@firebase/util": "1.9.0", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@firebase/installations": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.17.tgz", - "integrity": "sha512-AE/TyzIpwkC4UayRJD419xTqZkKzxwk0FLht3Dci8WI2OEKHSwoZG9xv4hOBZebe+fDzoV2EzfatQY8c/6Avig==", + "node_modules/@firebase/installations-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.1.tgz", + "integrity": "sha512-X4IBVKajEeaE45zWX0Y1q8ey39aPFLa+BsUoYzsduMzCxcMBIPZd5/lV1EVGt8SN3+unnC2J75flYkxXVlhBoQ==", "dev": true, "dependencies": { - "@firebase/component": "0.1.19", - "@firebase/installations-types": "0.3.4", - "@firebase/util": "0.3.2", - "idb": "3.0.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app-compat": "0.x" } }, + "node_modules/@firebase/installations-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, "node_modules/@firebase/installations-types": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", - "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", "dev": true, "peerDependencies": { "@firebase/app-types": "0.x" } }, - "node_modules/@firebase/installations/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "node_modules/@firebase/installations/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } + "node_modules/@firebase/installations/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, "node_modules/@firebase/logger": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", - "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -1265,209 +1298,214 @@ "dev": true }, "node_modules/@firebase/messaging": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.7.1.tgz", - "integrity": "sha512-iev/ST9v0xd/8YpGYrZtDcqdD9J6ZWzSuceRn8EKy5vIgQvW/rk2eTQc8axzvDpQ36ZfphMYuhW6XuNrR3Pd2Q==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.1.tgz", + "integrity": "sha512-/F+2OWarR8TcJJVlQS6zBoHHfXMgfgR0/ukQ3h7Ow3WZ3WZ9+Sj/gvxzothXZm+WtBylfXuhiANFgHEDFL0J0w==", "dev": true, "dependencies": { - "@firebase/component": "0.1.19", - "@firebase/installations": "0.4.17", - "@firebase/messaging-types": "0.5.0", - "@firebase/util": "0.3.2", - "idb": "3.0.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.0", + "idb": "7.0.1", + "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" } }, - "node_modules/@firebase/messaging-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.5.0.tgz", - "integrity": "sha512-QaaBswrU6umJYb/ZYvjR5JDSslCGOH6D9P136PhabFAHLTR4TWjsaACvbBXuvwrfCXu10DtcjMxqfhdNIB1Xfg==", + "node_modules/@firebase/messaging-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.1.tgz", + "integrity": "sha512-BykvXtAWOs0W4Ik79lNfMKSxaUCtOJ47PJ9Vw2ySHZ14vFFNuDAtRTOBOlAFhUpsHqRoQFvFCkBGsRIQYq8hzw==", "dev": true, + "dependencies": { + "@firebase/component": "0.6.1", + "@firebase/messaging": "0.12.1", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, "peerDependencies": { - "@firebase/app-types": "0.x" + "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/messaging/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } + "node_modules/@firebase/messaging-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, - "node_modules/@firebase/messaging/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==", + "dev": true + }, + "node_modules/@firebase/messaging/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, "node_modules/@firebase/performance": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.4.2.tgz", - "integrity": "sha512-irHTCVWJ/sxJo0QHg+yQifBeVu8ZJPihiTqYzBUz/0AGc51YSt49FZwqSfknvCN2+OfHaazz/ARVBn87g7Ex8g==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.1.tgz", + "integrity": "sha512-mT/CWz3CLgyn/a3sO/TJgrTt+RA3DfuvWwGXY9zmIiuBZY2bDi1M2uMefJdJKc9sBUPRajNF6RL10nGYq3BAuQ==", "dev": true, "dependencies": { - "@firebase/component": "0.1.19", - "@firebase/installations": "0.4.17", - "@firebase/logger": "0.2.6", - "@firebase/performance-types": "0.0.13", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" } }, - "node_modules/@firebase/performance-types": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", - "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==", - "dev": true - }, - "node_modules/@firebase/performance/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "node_modules/@firebase/performance-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.1.tgz", + "integrity": "sha512-4mn6eS7r2r+ZAHvU0OHE+3ZO+x6gOVhf2ypBoijuDNaRNjSn9GcvA8udD4IbJ8FNv/k7mbbtA9AdxVb701Lr1g==", "dev": true, "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.1", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/performance/node_modules/@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "node_modules/@firebase/performance-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@firebase/performance/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } + "node_modules/@firebase/performance-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==", + "dev": true }, - "node_modules/@firebase/polyfill": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", - "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", - "dev": true, - "dependencies": { - "core-js": "3.6.5", - "promise-polyfill": "8.1.3", - "whatwg-fetch": "2.0.4" - } + "node_modules/@firebase/performance/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, "node_modules/@firebase/remote-config": { - "version": "0.1.28", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.28.tgz", - "integrity": "sha512-4zSdyxpt94jAnFhO8toNjG8oMKBD+xTuBIcK+Nw8BdQWeJhEamgXlupdBARUk1uf3AvYICngHH32+Si/dMVTbw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.1.tgz", + "integrity": "sha512-RCzBH3FjAPRSP3M1T7jdxLYBesIdLtNIQ0fR9ywJpGSSa0kIXEJ9iSZMTP+9pJtaCxz8db07FvjEqg7Y+lgjzg==", "dev": true, "dependencies": { - "@firebase/component": "0.1.19", - "@firebase/installations": "0.4.17", - "@firebase/logger": "0.2.6", - "@firebase/remote-config-types": "0.1.9", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" } }, - "node_modules/@firebase/remote-config-types": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", - "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==", - "dev": true - }, - "node_modules/@firebase/remote-config/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.1.tgz", + "integrity": "sha512-RPCj7c2Q3QxMgJH3YCt0iD57KppFApghxAGETzlr6Jm6vT7k0vqvk2KgRBgKa4koJBsgwlUtRn2roaCqUEadyg==", "dev": true, "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.1", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/remote-config/node_modules/@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "node_modules/@firebase/remote-config-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/@firebase/remote-config/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } + "node_modules/@firebase/remote-config-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==", + "dev": true + }, + "node_modules/@firebase/remote-config/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, "node_modules/@firebase/storage": { - "version": "0.3.43", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.43.tgz", - "integrity": "sha512-Jp54jcuyimLxPhZHFVAhNbQmgTu3Sda7vXjXrNpPEhlvvMSq4yuZBR6RrZxe/OrNVprLHh/6lTCjwjOVSo3bWA==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.10.1.tgz", + "integrity": "sha512-eN4ME+TFCh5KfyG9uo8PhE6cgKjK5Rb9eucQg1XEyLHMiaZiUv2xSuWehJn0FaL+UdteoaWKuRUZ4WXRDskXrA==", "dev": true, "dependencies": { - "@firebase/component": "0.1.19", - "@firebase/storage-types": "0.3.13", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/util": "1.9.0", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" + "@firebase/app": "0.x" } }, - "node_modules/@firebase/storage/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "node_modules/@firebase/storage-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.2.1.tgz", + "integrity": "sha512-H0oFdYsMn2Z6tP9tlVERBkJiZsCbFAcl3Li1dnpvDg9g323egdjCnUUgH/tJODRR/Y84iZSNRkg4FvHDVI/o7Q==", "dev": true, "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/storage": "0.10.1", + "@firebase/storage-types": "0.7.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@firebase/storage/node_modules/@firebase/storage-types": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz", - "integrity": "sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==", + "node_modules/@firebase/storage-compat/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, + "node_modules/@firebase/storage-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.7.0.tgz", + "integrity": "sha512-n/8pYd82hc9XItV3Pa2KGpnuJ/2h/n/oTAaBberhe6GeyWQPnsmwwRK94W3GxUwBA/ZsszBAYZd7w7tTE+6XXA==", "dev": true, "peerDependencies": { "@firebase/app-types": "0.x", - "@firebase/util": "0.x" + "@firebase/util": "1.x" } }, - "node_modules/@firebase/storage/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } + "node_modules/@firebase/storage/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true }, "node_modules/@firebase/util": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", - "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.0.tgz", + "integrity": "sha512-oeoq/6Sr9btbwUQs5HPfeww97bf7qgBbkknbDTXpRaph2LZ23O9XLCE5tJy856SBmGQfO4xBZP8dyryLLM2nSQ==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -1480,9 +1518,9 @@ "dev": true }, "node_modules/@firebase/webchannel-wrapper": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", - "integrity": "sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz", + "integrity": "sha512-BpiZLBWdLFw+qFel9p3Zs1jD6QmH7Ii4aTDu6+vx8ShdidChZUXqDhYJly4ZjSgQh54miXbBgBrk0S+jTIh/Qg==", "dev": true }, "node_modules/@gar/promisify": { @@ -1492,19 +1530,51 @@ "optional": true }, "node_modules/@google-cloud/firestore": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", - "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.2.tgz", + "integrity": "sha512-f7xFwINJveaqTFcgy0G4o2CBPm0Gv9lTGQ4dQt+7skwaHs3ytdue9ma8oQZYXKNoWcAoDIMQ929Dk0KOIocxFg==", "dev": true, "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" + "google-gax": "^3.5.2", + "protobufjs": "^7.0.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/firestore/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "dev": true, + "optional": true + }, + "node_modules/@google-cloud/firestore/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "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/@google-cloud/paginator": { @@ -1582,22 +1652,6 @@ "node": ">=12.0.0" } }, - "node_modules/@google-cloud/pubsub/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@google-cloud/pubsub/node_modules/gaxios": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.0.tgz", @@ -1644,32 +1698,6 @@ "node": ">=12" } }, - "node_modules/@google-cloud/pubsub/node_modules/google-gax": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.0.3.tgz", - "integrity": "sha512-oS1x9DWOmC2xX6pl2W92KtyR50x1zh1aEh0dxUCpu7GCv62aQ/MMkax2kJb/T6OKj/Yn8MYT8bIGyU+trk/p2Q==", - "dependencies": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "6.11.3", - "retry-request": "^5.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@google-cloud/pubsub/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -1700,159 +1728,310 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/@google-cloud/pubsub/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/@google-cloud/pubsub/node_modules/proto3-json-serializer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.0.0.tgz", - "integrity": "sha512-uEylKn4a7I6ZtLZ0fwCJCdQqr2vMsGtxxwKZIoqy4VwYeK9HKpCiG8WMBdtodV+1UO5YHHvHvb39b5CyRWT+9g==", - "dependencies": { - "protobufjs": "^6.11.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/pubsub/node_modules/retry-request": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.0.tgz", - "integrity": "sha512-vBZdBxUordje9253imlmGtppC5gdcwZmNz7JnU2ui+KKFPk25keR+0c020AVV20oesYxIFOI0Kh3HE88/59ieg==", - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@google-cloud/storage": { - "version": "5.20.5", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", - "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "dev": true, "optional": true, "dependencies": { "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", - "configstore": "^5.0.0", "duplexify": "^4.0.0", "ent": "^2.2.0", "extend": "^3.0.2", - "gaxios": "^4.0.0", - "google-auth-library": "^7.14.1", - "hash-stream-validation": "^0.2.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", - "retry-request": "^4.2.2", - "stream-events": "^1.0.4", - "teeny-request": "^7.1.3", - "uuid": "^8.0.0", - "xdg-basedir": "^4.0.0" + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@google-cloud/storage/node_modules/mime": { + "node_modules/@google-cloud/storage/node_modules/@google-cloud/projectify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", "dev": true, "optional": true, - "bin": { - "mime": "cli.js" - }, "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" } }, - "node_modules/@google-cloud/storage/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==", + "node_modules/@google-cloud/storage/node_modules/@google-cloud/promisify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage/node_modules/gaxios": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", "dev": true, "optional": true, "dependencies": { - "yocto-queue": "^0.1.0" + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/@google/events": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@google/events/-/events-5.1.1.tgz", - "integrity": "sha512-97u6AUfEXo6TxoBAdbziuhSL56+l69WzFahR6eTQE/bSjGPqT1+W4vS7eKaR7r60pGFrZZfqdFZ99uMbns3qgA==", + "node_modules/@google-cloud/storage/node_modules/gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", "dev": true, + "optional": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "node_modules/@google-cloud/storage/node_modules/google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", + "dev": true, + "optional": true, "dependencies": { - "@grpc/proto-loader": "^0.6.4", - "@types/node": ">=12.12.47" + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" }, "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=12" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "node_modules/@google-cloud/storage/node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dev": true, + "optional": true, "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.11.3", - "yargs": "^16.2.0" + "node-forge": "^1.3.1" }, "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "gp12-pem": "build/src/bin/gp12-pem.js" }, "engines": { - "node": ">=6" + "node": ">=12.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", - "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", + "node_modules/@google-cloud/storage/node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", "dev": true, + "optional": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=12.0.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "node_modules/@google-cloud/storage/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==", "dev": true, - "dependencies": { - "ms": "2.1.2" - }, + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google-cloud/storage/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@google-cloud/storage/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==", + "dev": true, + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/storage/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==", + "dev": true, + "optional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google/events": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@google/events/-/events-5.1.1.tgz", + "integrity": "sha512-97u6AUfEXo6TxoBAdbziuhSL56+l69WzFahR6eTQE/bSjGPqT1+W4vS7eKaR7r60pGFrZZfqdFZ99uMbns3qgA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, + "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/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dev": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, "engines": { "node": ">=6.0" }, @@ -2418,15 +2597,6 @@ "node": ">=8.12.0" } }, - "node_modules/@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@pnpm/network.ca-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", @@ -2752,30 +2922,21 @@ "dev": true }, "node_modules/@types/express": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.0.tgz", - "integrity": "sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==", + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.16.tgz", + "integrity": "sha512-LkKpqRZ7zqXJuvoELakaFYuETHjZkSol8EV6cNnyishutDBCCdv6+dsKPbKkCcIk57qRphOLY5sEgClw1bO3gA==", "dev": true, "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.31", + "@types/qs": "*", "@types/serve-static": "*" } }, - "node_modules/@types/express-jwt": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", - "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", - "dev": true, - "dependencies": { - "@types/express": "*", - "@types/express-unless": "*" - } - }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", - "integrity": "sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==", + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", "dev": true, "dependencies": { "@types/node": "*", @@ -2783,15 +2944,6 @@ "@types/range-parser": "*" } }, - "node_modules/@types/express-unless": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.3.tgz", - "integrity": "sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/firebase": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/firebase/-/firebase-3.2.1.tgz", @@ -2859,6 +3011,11 @@ "integrity": "sha512-LisgKLlYQk19baQwjkBZZXdJL0KbeTpdEnrAfz5hQACbklCY0gVFnsKUyjfNWF1UQsCSjw93Sj5jSbiO8RPfdw==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" + }, "node_modules/@types/lodash": { "version": "4.14.149", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", @@ -2870,6 +3027,15 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, "node_modules/@types/marked": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz", @@ -2892,6 +3058,11 @@ "integrity": "sha512-ZgAr847Wl68W+B0sWH7F4fDPxTzerLnRuUXjUpp1n4NjGSs8hgPAjAp7NQIXblG34MXTrf5wWkAK8PVJ2LIlVg==", "dev": true }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -3607,7 +3778,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -4669,6 +4839,17 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/chai": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", @@ -5182,22 +5363,11 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "node_modules/cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true }, - "node_modules/core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true, - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -5578,15 +5748,6 @@ "node": ">=6.0.0" } }, - "node_modules/dom-storage": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", - "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -5717,6 +5878,14 @@ "dev": true, "optional": true }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -6390,7 +6559,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -6670,7 +6838,6 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", - "dev": true, "dependencies": { "acorn": "^8.7.0", "acorn-jsx": "^5.3.1", @@ -7243,91 +7410,71 @@ } }, "node_modules/firebase": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.24.0.tgz", - "integrity": "sha512-j6jIyGFFBlwWAmrlUg9HyQ/x+YpsPkc/TTkbTyeLwwAJrpAmmEHNPT6O9xtAnMV4g7d3RqLL/u9//aZlbY4rQA==", - "dev": true, - "dependencies": { - "@firebase/analytics": "0.6.0", - "@firebase/app": "0.6.11", - "@firebase/app-types": "0.6.1", - "@firebase/auth": "0.15.0", - "@firebase/database": "0.6.13", - "@firebase/firestore": "1.18.0", - "@firebase/functions": "0.5.1", - "@firebase/installations": "0.4.17", - "@firebase/messaging": "0.7.1", - "@firebase/performance": "0.4.2", - "@firebase/polyfill": "0.3.36", - "@firebase/remote-config": "0.1.28", - "@firebase/storage": "0.3.43", - "@firebase/util": "0.3.2" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.16.0.tgz", + "integrity": "sha512-nNLpDwJvfP3crRc6AjnHH46TAkFzk8zimNVMJfYRCwAf5amOSGyU8duuc3IsJF6dQGiYLSfzfr2tMCsQa+rhKQ==", + "dev": true, + "dependencies": { + "@firebase/analytics": "0.9.1", + "@firebase/analytics-compat": "0.2.1", + "@firebase/app": "0.9.1", + "@firebase/app-check": "0.6.1", + "@firebase/app-check-compat": "0.3.1", + "@firebase/app-compat": "0.2.1", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "0.21.1", + "@firebase/auth-compat": "0.3.1", + "@firebase/database": "0.14.1", + "@firebase/database-compat": "0.3.1", + "@firebase/firestore": "3.8.1", + "@firebase/firestore-compat": "0.3.1", + "@firebase/functions": "0.9.1", + "@firebase/functions-compat": "0.3.1", + "@firebase/installations": "0.6.1", + "@firebase/installations-compat": "0.2.1", + "@firebase/messaging": "0.12.1", + "@firebase/messaging-compat": "0.2.1", + "@firebase/performance": "0.6.1", + "@firebase/performance-compat": "0.2.1", + "@firebase/remote-config": "0.4.1", + "@firebase/remote-config-compat": "0.2.1", + "@firebase/storage": "0.10.1", + "@firebase/storage-compat": "0.2.1", + "@firebase/util": "1.9.0" } }, "node_modules/firebase-admin": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.3.0.tgz", - "integrity": "sha512-A0wgMLEjyVyUE+heyMJYqHRkPVjpebhOYsa47RHdrTM4ltApcx8Tn86sUmjqxlfh09gNnILAm7a8q5+FmgBYpg==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.5.0.tgz", + "integrity": "sha512-bBdlYtNvXx8yZGdCd00NrfZl1o1A0aXOw5h8q5PwC8RXikOLNXq8vYtSKW44dj8zIaafVP6jFdcUXZem/LMsHA==", "dev": true, "dependencies": { "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.2.0", - "@firebase/database-types": "^0.9.7", + "@firebase/database-compat": "^0.3.0", + "@firebase/database-types": "^0.10.0", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", "node-forge": "^1.3.1", - "uuid": "^8.3.2" + "uuid": "^9.0.0" }, "engines": { - "node": ">=12.7.0" + "node": ">=14" }, "optionalDependencies": { - "@google-cloud/firestore": "^4.15.1", - "@google-cloud/storage": "^5.18.3" - } - }, - "node_modules/firebase-admin/node_modules/@firebase/database-types": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", - "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", - "dev": true, - "dependencies": { - "@firebase/app-types": "0.8.1", - "@firebase/util": "1.7.3" + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2" } }, - "node_modules/firebase-admin/node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "node_modules/firebase-admin/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "dev": true, - "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" - }, - "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/firebase-admin/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 - }, "node_modules/firebase-functions": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.1.0.tgz", @@ -7361,94 +7508,26 @@ "@types/serve-static": "*" } }, - "node_modules/firebase/node_modules/@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", - "dev": true - }, - "node_modules/firebase/node_modules/@firebase/auth-interop-types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", - "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", - "dev": true, - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "0.x" - } - }, - "node_modules/firebase/node_modules/@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "dependencies": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "bin": { + "flat": "cli.js" } }, - "node_modules/firebase/node_modules/@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "dependencies": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "faye-websocket": "0.11.3", - "tslib": "^1.11.1" - } - }, - "node_modules/firebase/node_modules/@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", - "dev": true - }, - "node_modules/firebase/node_modules/@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/firebase/node_modules/faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -8016,82 +8095,116 @@ "dev": true }, "node_modules/google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", - "dev": true, - "optional": true, + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "dependencies": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", + "google-auth-library": "^8.0.2", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", + "retry-request": "^5.0.0" }, "bin": { - "compileProtos": "build/tools/compileProtos.js" + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/google-p12-pem": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", - "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", + "node_modules/google-gax/node_modules/@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", "dependencies": { - "node-forge": "^1.0.0" + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" }, "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/googleapis": { - "version": "105.0.0", - "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-105.0.0.tgz", - "integrity": "sha512-wH/jU/6QpqwsjTKj4vfKZz97ne7xT7BBbKwzQEwnbsG8iH9Seyw19P+AuLJcxNNrmgblwLqfr3LORg4Okat1BQ==", - "dev": true, + "node_modules/google-gax/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==", "dependencies": { - "google-auth-library": "^8.0.2", - "googleapis-common": "^6.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/googleapis-common": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.3.tgz", - "integrity": "sha512-Xyb4FsQ6PQDu4tAE/M/ev4yzZhFe2Gc7+rKmuCX2ZGk1ajBKbafsGlVYpmzGqQOT93BRDe8DiTmQb6YSkbICrA==", - "dev": true, + "node_modules/google-gax/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "extend": "^3.0.2", - "gaxios": "^5.0.1", - "google-auth-library": "^8.0.2", - "qs": "^6.7.0", - "url-template": "^2.0.8", - "uuid": "^9.0.0" + "balanced-match": "^1.0.0" + } + }, + "node_modules/google-gax/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/googleapis-common/node_modules/gaxios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", - "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", - "dev": true, + "node_modules/google-gax/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/google-gax/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==" + }, + "node_modules/google-gax/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/google-gax/node_modules/gaxios": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", @@ -8102,11 +8215,10 @@ "node": ">=12" } }, - "node_modules/googleapis-common/node_modules/gcp-metadata": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", - "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", - "dev": true, + "node_modules/google-gax/node_modules/gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", "dependencies": { "gaxios": "^5.0.0", "json-bigint": "^1.0.0" @@ -8115,11 +8227,28 @@ "node": ">=12" } }, - "node_modules/googleapis-common/node_modules/google-auth-library": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.5.1.tgz", - "integrity": "sha512-7jNMDRhenfw2HLfL9m0ZP/Jw5hzXygfSprzBdypG3rZ+q2gIUbVC/osrFB7y/Z5dkrUr1mnLoDNlerF+p6VXZA==", - "dev": true, + "node_modules/google-gax/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-gax/node_modules/google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", "dependencies": { "arrify": "^2.0.0", "base64-js": "^1.3.0", @@ -8135,11 +8264,10 @@ "node": ">=12" } }, - "node_modules/googleapis-common/node_modules/google-p12-pem": { + "node_modules/google-gax/node_modules/google-p12-pem": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "dev": true, "dependencies": { "node-forge": "^1.3.1" }, @@ -8150,11 +8278,10 @@ "node": ">=12.0.0" } }, - "node_modules/googleapis-common/node_modules/gtoken": { + "node_modules/google-gax/node_modules/gtoken": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "dev": true, "dependencies": { "gaxios": "^5.0.1", "google-p12-pem": "^4.0.0", @@ -8164,11 +8291,18 @@ "node": ">=12.0.0" } }, - "node_modules/googleapis-common/node_modules/is-stream": { + "node_modules/google-gax/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/google-gax/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==", - "dev": true, "engines": { "node": ">=8" }, @@ -8176,97 +8310,340 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/googleapis-common/node_modules/jwa": { + "node_modules/google-gax/node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dev": true, "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, - "node_modules/googleapis-common/node_modules/jws": { + "node_modules/google-gax/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==", - "dev": true, "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, - "node_modules/googleapis-common/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/googleapis/node_modules/gaxios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", - "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", - "dev": true, + "node_modules/google-gax/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/googleapis/node_modules/gcp-metadata": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", - "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", - "dev": true, + "node_modules/google-gax/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" + "@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" + "node": ">=12.0.0" } }, - "node_modules/googleapis/node_modules/google-auth-library": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.5.1.tgz", - "integrity": "sha512-7jNMDRhenfw2HLfL9m0ZP/Jw5hzXygfSprzBdypG3rZ+q2gIUbVC/osrFB7y/Z5dkrUr1mnLoDNlerF+p6VXZA==", - "dev": true, + "node_modules/google-gax/node_modules/protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" }, "engines": { - "node": ">=12" + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" } }, - "node_modules/googleapis/node_modules/google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "dev": true, + "node_modules/google-gax/node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, + "node_modules/google-gax/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dependencies": { - "node-forge": "^1.3.1" + "lru-cache": "^6.0.0" }, "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" + "semver": "bin/semver.js" }, "engines": { - "node": ">=12.0.0" + "node": ">=10" + } + }, + "node_modules/google-gax/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/google-p12-pem": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", + "dependencies": { + "node-forge": "^1.0.0" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/googleapis": { + "version": "105.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-105.0.0.tgz", + "integrity": "sha512-wH/jU/6QpqwsjTKj4vfKZz97ne7xT7BBbKwzQEwnbsG8iH9Seyw19P+AuLJcxNNrmgblwLqfr3LORg4Okat1BQ==", + "dev": true, + "dependencies": { + "google-auth-library": "^8.0.2", + "googleapis-common": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.3.tgz", + "integrity": "sha512-Xyb4FsQ6PQDu4tAE/M/ev4yzZhFe2Gc7+rKmuCX2ZGk1ajBKbafsGlVYpmzGqQOT93BRDe8DiTmQb6YSkbICrA==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^5.0.1", + "google-auth-library": "^8.0.2", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common/node_modules/gaxios": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", + "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis-common/node_modules/gcp-metadata": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", + "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "dev": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis-common/node_modules/google-auth-library": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.5.1.tgz", + "integrity": "sha512-7jNMDRhenfw2HLfL9m0ZP/Jw5hzXygfSprzBdypG3rZ+q2gIUbVC/osrFB7y/Z5dkrUr1mnLoDNlerF+p6VXZA==", + "dev": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis-common/node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dev": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common/node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dev": true, + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common/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==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/googleapis-common/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis-common/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==", + "dev": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/googleapis/node_modules/gaxios": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", + "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis/node_modules/gcp-metadata": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", + "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "dev": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis/node_modules/google-auth-library": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.5.1.tgz", + "integrity": "sha512-7jNMDRhenfw2HLfL9m0ZP/Jw5hzXygfSprzBdypG3rZ+q2gIUbVC/osrFB7y/Z5dkrUr1mnLoDNlerF+p6VXZA==", + "dev": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis/node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dev": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" } }, "node_modules/googleapis/node_modules/gtoken": { @@ -8437,13 +8814,6 @@ "node": ">=8" } }, - "node_modules/hash-stream-validation": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", - "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", - "dev": true, - "optional": true - }, "node_modules/hasha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", @@ -8640,9 +9010,9 @@ } }, "node_modules/idb": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", - "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", "dev": true }, "node_modules/ieee754": { @@ -9287,16 +9657,10 @@ } }, "node_modules/jose": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", - "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", + "integrity": "sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A==", "dev": true, - "dependencies": { - "@panva/asn1.js": "^1.0.0" - }, - "engines": { - "node": ">=10.13.0 < 13 || >=13.7.0" - }, "funding": { "url": "https://github.com/sponsors/panva" } @@ -9319,20 +9683,91 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.0.1.tgz", - "integrity": "sha512-vqMCdAFVIiFhVgBYE/X8naf3L/7qiJsaYWTfUJZZZ124dR3OUz9HrmaMUGpYIYAN4VSuodf6gIZY0e8ktPw9cg==", + "node_modules/jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "dependencies": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.0.1.tgz", + "integrity": "sha512-vqMCdAFVIiFhVgBYE/X8naf3L/7qiJsaYWTfUJZZZ124dR3OUz9HrmaMUGpYIYAN4VSuodf6gIZY0e8ktPw9cg==", "dev": true, "engines": { "node": ">=12.0.0" } }, + "node_modules/jsdoc/node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/jsdoc/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdoc/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsdoc/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==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -9541,25 +9976,35 @@ } }, "node_modules/jwks-rsa": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", - "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", "dev": true, "dependencies": { - "@types/express-jwt": "0.0.42", - "debug": "^4.3.2", - "jose": "^2.0.5", + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", + "debug": "^4.3.4", + "jose": "^4.10.4", "limiter": "^1.1.5", "lru-memoizer": "^2.1.4" }, "engines": { - "node": ">=10 < 13 || >=14" + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", + "dev": true, + "dependencies": { + "@types/node": "*" } }, "node_modules/jwks-rsa/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -9597,6 +10042,14 @@ "node": ">=0.10.0" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", @@ -9675,6 +10128,14 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", @@ -9713,7 +10174,7 @@ "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "dev": true }, "node_modules/lodash.defaults": { @@ -9743,30 +10204,6 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "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==", - "dev": true - }, - "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==", - "dev": true - }, - "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==", - "dev": true - }, - "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==", - "dev": true - }, "node_modules/lodash.isobject": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", @@ -9780,24 +10217,12 @@ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, - "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==", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, "node_modules/lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -9960,7 +10385,7 @@ "node_modules/lru-memoizer/node_modules/lru-cache": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", "dev": true, "dependencies": { "pseudomap": "^1.0.1", @@ -9970,7 +10395,7 @@ "node_modules/lru-memoizer/node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true }, "node_modules/make-dir": { @@ -10106,6 +10531,35 @@ "node": ">=8" } }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/marked": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", @@ -10147,6 +10601,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -11976,12 +12435,6 @@ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "optional": true }, - "node_modules/promise-polyfill": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", - "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==", - "dev": true - }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -12019,19 +12472,49 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, "node_modules/proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", - "dev": true, - "optional": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", + "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", + "dependencies": { + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proto3-json-serializer/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, + "node_modules/proto3-json-serializer/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, "dependencies": { - "protobufjs": "^6.11.2" + "@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/protobufjs": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "dev": true, "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -12056,7 +12539,8 @@ "node_modules/protobufjs/node_modules/@types/node": { "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", - "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", + "dev": true }, "node_modules/proxy": { "version": "1.0.2", @@ -12167,7 +12651,7 @@ "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", "dev": true }, "node_modules/psl": { @@ -12184,18 +12668,6 @@ "once": "^1.3.1" } }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "dev": true, - "optional": true, - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -12683,6 +13155,14 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -12730,25 +13210,21 @@ } }, "node_modules/retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", - "dev": true, - "optional": true, + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", "dependencies": { "debug": "^4.1.1", "extend": "^3.0.2" }, "engines": { - "node": ">=8.10.0" + "node": ">=12" } }, "node_modules/retry-request/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, "dependencies": { "ms": "2.1.2" }, @@ -12764,9 +13240,7 @@ "node_modules/retry-request/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/reusify": { "version": "1.0.4", @@ -13821,6 +14295,11 @@ "node": ">=12" } }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" + }, "node_modules/tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", @@ -13914,9 +14393,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/teeny-request": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", - "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "dev": true, "optional": true, "dependencies": { @@ -13924,10 +14403,10 @@ "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/teeny-request/node_modules/@tootallnate/once": { @@ -13980,6 +14459,16 @@ "dev": true, "optional": true }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/term-size": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", @@ -14363,6 +14852,22 @@ "node": ">=4.2.0" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -14373,6 +14878,11 @@ "through": "^2.3.8" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "node_modules/unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -14886,12 +15396,6 @@ "node": ">=0.8.0" } }, - "node_modules/whatwg-fetch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", - "dev": true - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -15087,14 +15591,10 @@ "node": ">=8" } }, - "node_modules/xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" }, "node_modules/xregexp": { "version": "2.0.0", @@ -15554,8 +16054,7 @@ "@babel/parser": { "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "dev": true + "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==" }, "@babel/template": { "version": "7.18.10", @@ -15752,155 +16251,83 @@ } }, "@firebase/analytics": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.6.0.tgz", - "integrity": "sha512-6qYEOPUVYrMhqvJ46Z5Uf1S4uULd6d7vGpMP5Qz+u8kIWuOQGcPdJKQap+Hla6Rq164or9gC2HRXuYXKlgWfpw==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.9.1.tgz", + "integrity": "sha512-ARXtNHDrjDhVrs5MqmFDpr5yyCw89r1eHLd+Dw9fotAufxL1WTmo6O9bJqKb7QulIJaA84vsFokA3NaO2DNCnQ==", "dev": true, "requires": { - "@firebase/analytics-types": "0.4.0", - "@firebase/component": "0.1.19", - "@firebase/installations": "0.4.17", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } } } }, - "@firebase/analytics-types": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.4.0.tgz", - "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA==", - "dev": true - }, - "@firebase/app": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.11.tgz", - "integrity": "sha512-FH++PaoyTzfTAVuJ0gITNYEIcjT5G+D0671La27MU8Vvr6MTko+5YUZ4xS9QItyotSeRF4rMJ1KR7G8LSyySiA==", + "@firebase/analytics-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.1.tgz", + "integrity": "sha512-qfFAGS4YFsBbmZwVa7xaDnGh7k9BKF4o/piyjySAv0lxRYd74/tSrm3kMk1YM7GCti7PdbgKvl6oSR70zMFQjw==", "dev": true, "requires": { - "@firebase/app-types": "0.6.1", - "@firebase/component": "0.1.19", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "dom-storage": "2.1.0", - "tslib": "^1.11.1", - "xmlhttprequest": "1.8.0" + "@firebase/analytics": "0.9.1", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.1", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", - "dev": true - }, - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } } } }, - "@firebase/app-types": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.1.tgz", - "integrity": "sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==", + "@firebase/analytics-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==", "dev": true }, - "@firebase/auth": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.15.0.tgz", - "integrity": "sha512-IFuzhxS+HtOQl7+SZ/Mhaghy/zTU7CENsJFWbC16tv2wfLZbayKF5jYGdAU3VFLehgC8KjlcIWd10akc3XivfQ==", + "@firebase/app": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.1.tgz", + "integrity": "sha512-Z8wOSol+pvp4CFyY1mW+aqdZlrwhW/ha2YXQ6/avJ56c5Hnvt4k6GktZE6o5NyzvfJTgNHryhMtnEJMIuLaT4w==", "dev": true, "requires": { - "@firebase/auth-types": "0.10.1" + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "idb": "7.0.1", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/auth-types": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", - "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", - "dev": true, - "requires": {} - }, - "@firebase/util": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.4.1.tgz", - "integrity": "sha512-XhYCOwq4AH+YeQBEnDQvigz50WiiBU4LnJh2+//VMt4J2Ybsk0eTgUHNngUzXsmp80EJrwal3ItODg55q1ajWg==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^2.1.0" - } - }, "tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", - "dev": true, - "peer": true + "dev": true } } }, - "@firebase/auth-interop-types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.7.tgz", - "integrity": "sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==", - "dev": true, - "requires": {} - }, - "@firebase/component": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.21.tgz", - "integrity": "sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==", + "@firebase/app-check": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.6.1.tgz", + "integrity": "sha512-gDG4Gr4n3MnBZAAwLMynU9u/b+f1y87lCezfwlmN1gUxD85mJcvp4hLf87fACTyRkdVfe8hqSXm+MOYn2bMGLg==", "dev": true, "requires": { - "@firebase/util": "1.7.3", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", "tslib": "^2.1.0" }, "dependencies": { @@ -15912,17 +16339,17 @@ } } }, - "@firebase/database": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.13.10.tgz", - "integrity": "sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==", + "@firebase/app-check-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.1.tgz", + "integrity": "sha512-IaSYdmaoQgWUrN6rjAYJs1TGXj38Wl9damtrDEyJBf7+rrvKshPAP/CP6e2bd89XOMZKbvy8rKoe1CqX1K3ZjQ==", "dev": true, "requires": { - "@firebase/auth-interop-types": "0.1.7", - "@firebase/component": "0.5.21", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", - "faye-websocket": "0.11.4", + "@firebase/app-check": "0.6.1", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", "tslib": "^2.1.0" }, "dependencies": { @@ -15934,30 +16361,31 @@ } } }, - "@firebase/database-compat": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.2.10.tgz", - "integrity": "sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==", - "dev": true, + "@firebase/app-check-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.2.0.tgz", + "integrity": "sha512-+3PQIeX6/eiVK+x/yg8r6xTNR97fN7MahFDm+jiQmDjcyvSefoGuTTNQuuMScGyx3vYUBeZn+Cp9kC0yY/9uxQ==", + "dev": true + }, + "@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==", + "dev": true + }, + "@firebase/app-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.1.tgz", + "integrity": "sha512-UgPy2ZO0li0j4hAkaZKY9P1TuJEx5RylhUWPzCb8DZhBm+uHdfsFI9Yr+wMlu6qQH2sWoweFtYU6ljGzxwdctw==", + "dev": true, "requires": { - "@firebase/component": "0.5.21", - "@firebase/database": "0.13.10", - "@firebase/database-types": "0.9.17", - "@firebase/logger": "0.3.4", - "@firebase/util": "1.7.3", + "@firebase/app": "0.9.1", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", "tslib": "^2.1.0" }, "dependencies": { - "@firebase/database-types": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", - "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", - "dev": true, - "requires": { - "@firebase/app-types": "0.8.1", - "@firebase/util": "1.7.3" - } - }, "tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", @@ -15966,181 +16394,294 @@ } } }, - "@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==", + "dev": true + }, + "@firebase/auth": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.21.1.tgz", + "integrity": "sha512-/ap7eT9X7kZTD4Fn2m+nJyC1a9DfFo0H4euoJDN8U+JCMN+GOqkPbkMWCey7wV510WNoPCZQ05+nsAqKkbEVJw==", + "dev": true, + "requires": { + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/auth-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.3.1.tgz", + "integrity": "sha512-Ndcaam+IL1TuJ6hZ0EcQ+v261cK3kPm4mvUtouoTfl3FNinm9XvhccN8ojuaRtIV9TiY18mzGjONKF5ZCXLIZw==", + "dev": true, + "requires": { + "@firebase/auth": "0.21.1", + "@firebase/auth-types": "0.12.0", + "@firebase/component": "0.6.1", + "@firebase/util": "1.9.0", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==", + "dev": true + }, + "@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", + "dev": true, + "requires": {} + }, + "@firebase/component": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.1.tgz", + "integrity": "sha512-yvKthG0InjFx9aOPnh6gk0lVNfNVEtyq3LwXgZr+hOwD0x/CtXq33XCpqv0sQj5CA4FdMy8OO+y9edI+ZUw8LA==", + "dev": true, + "requires": { + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/database": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.1.tgz", + "integrity": "sha512-iX6/p7hoxUMbYAGZD+D97L05xQgpkslF2+uJLZl46EdaEfjVMEwAdy7RS/grF96kcFZFg502LwPYTXoIdrZqOA==", "dev": true, "requires": { - "@firebase/app-types": "0.6.1" + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true } } }, + "@firebase/database-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.1.tgz", + "integrity": "sha512-sI7LNh0C8PCq9uUKjrBKLbZvqHTSjsf2LeZRxin+rHVegomjsOAYk9OzYwxETWh3URhpMkCM8KcTl7RVwAldog==", + "dev": true, + "requires": { + "@firebase/component": "0.6.1", + "@firebase/database": "0.14.1", + "@firebase/database-types": "0.10.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/database-types": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.1.tgz", + "integrity": "sha512-UgUx9VakTHbP2WrVUdYrUT2ofTFVfWjGW2O1fwuvvMyo6WSnuSyO5nB1u0cyoMPvO25dfMIUVerfK7qFfwGL3Q==", + "dev": true, + "requires": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.0" + } + }, "@firebase/firestore": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.18.0.tgz", - "integrity": "sha512-maMq4ltkrwjDRusR2nt0qS4wldHQMp+0IDSfXIjC+SNmjnWY/t/+Skn9U3Po+dB38xpz3i7nsKbs+8utpDnPSw==", - "dev": true, - "requires": { - "@firebase/component": "0.1.19", - "@firebase/firestore-types": "1.14.0", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "@firebase/webchannel-wrapper": "0.4.0", - "@grpc/grpc-js": "^1.0.0", - "@grpc/proto-loader": "^0.5.0", - "node-fetch": "2.6.1", - "tslib": "^1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.8.1.tgz", + "integrity": "sha512-oc2HMkUnq/zF+g9o974tp5RVCdXCnrU8e5S98ajfWG/hGV+8pr4i6vIa4z0yEXKWGi4X0FguxrC69z1dxEJbNg==", + "dev": true, + "requires": { + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "@firebase/webchannel-wrapper": "0.9.0", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "@grpc/proto-loader": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz", - "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", - "dev": true, - "requires": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + } + } + }, + "@firebase/firestore-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.1.tgz", + "integrity": "sha512-7eE4O2ASyy5X2h4a+KCRt0ZpliUAKo2jrKxKl1ZVCnOOjSCkXXeRVRG9eNZRqBwukhdwskJTM9acs0WxmKOYLA==", + "dev": true, + "requires": { + "@firebase/component": "0.6.1", + "@firebase/firestore": "3.8.1", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true } } }, "@firebase/firestore-types": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.14.0.tgz", - "integrity": "sha512-WF8IBwHzZDhwyOgQnmB0pheVrLNP78A8PGxk1nxb/Nrgh1amo4/zYvFMGgSsTeaQK37xMYS/g7eS948te/dJxw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", "dev": true, "requires": {} }, "@firebase/functions": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.5.1.tgz", - "integrity": "sha512-yyjPZXXvzFPjkGRSqFVS5Hc2Y7Y48GyyMH+M3i7hLGe69r/59w6wzgXKqTiSYmyE1pxfjxU4a1YqBDHNkQkrYQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.9.1.tgz", + "integrity": "sha512-xCSSU4aVSqYU+lCqhn9o5jJcE1KLUOOKyJfCTdCSCyTn2J3vl9Vk4TDm3JSb1Eu6XsNWtxeMW188F/GYxuMWcw==", "dev": true, "requires": { - "@firebase/component": "0.1.19", - "@firebase/functions-types": "0.3.17", - "@firebase/messaging-types": "0.5.0", - "node-fetch": "2.6.1", - "tslib": "^1.11.1" + "@firebase/app-check-interop-types": "0.2.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.1", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.0", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/functions-compat": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.1.tgz", + "integrity": "sha512-f2D2XoRN+QCziCrUL7UrLaBEoG3v2iAeyNwbbOQ3vv0rI0mtku2/yeB2OINz5/iI6oIrBPUMNLr5fitofj7FpQ==", + "dev": true, + "requires": { + "@firebase/component": "0.6.1", + "@firebase/functions": "0.9.1", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true } } }, "@firebase/functions-types": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.17.tgz", - "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==", "dev": true }, "@firebase/installations": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.17.tgz", - "integrity": "sha512-AE/TyzIpwkC4UayRJD419xTqZkKzxwk0FLht3Dci8WI2OEKHSwoZG9xv4hOBZebe+fDzoV2EzfatQY8c/6Avig==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.1.tgz", + "integrity": "sha512-gpobP09LLLakBfNCL04fyblfyb3oX1pn+iNmELygrcAkXTO13IAMuOzThI+Xk4NHQZMX1p5GFSAiGbG4yfsSUQ==", "dev": true, "requires": { - "@firebase/component": "0.1.19", - "@firebase/installations-types": "0.3.4", - "@firebase/util": "0.3.2", - "idb": "3.0.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/util": "1.9.0", + "idb": "7.0.1", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/installations-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.1.tgz", + "integrity": "sha512-X4IBVKajEeaE45zWX0Y1q8ey39aPFLa+BsUoYzsduMzCxcMBIPZd5/lV1EVGt8SN3+unnC2J75flYkxXVlhBoQ==", + "dev": true, + "requires": { + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true } } }, "@firebase/installations-types": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", - "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", "dev": true, "requires": {} }, "@firebase/logger": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.4.tgz", - "integrity": "sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", "dev": true, "requires": { "tslib": "^2.1.0" @@ -16155,196 +16696,203 @@ } }, "@firebase/messaging": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.7.1.tgz", - "integrity": "sha512-iev/ST9v0xd/8YpGYrZtDcqdD9J6ZWzSuceRn8EKy5vIgQvW/rk2eTQc8axzvDpQ36ZfphMYuhW6XuNrR3Pd2Q==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.1.tgz", + "integrity": "sha512-/F+2OWarR8TcJJVlQS6zBoHHfXMgfgR0/ukQ3h7Ow3WZ3WZ9+Sj/gvxzothXZm+WtBylfXuhiANFgHEDFL0J0w==", "dev": true, "requires": { - "@firebase/component": "0.1.19", - "@firebase/installations": "0.4.17", - "@firebase/messaging-types": "0.5.0", - "@firebase/util": "0.3.2", - "idb": "3.0.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.0", + "idb": "7.0.1", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true } } }, - "@firebase/messaging-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.5.0.tgz", - "integrity": "sha512-QaaBswrU6umJYb/ZYvjR5JDSslCGOH6D9P136PhabFAHLTR4TWjsaACvbBXuvwrfCXu10DtcjMxqfhdNIB1Xfg==", + "@firebase/messaging-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.1.tgz", + "integrity": "sha512-BykvXtAWOs0W4Ik79lNfMKSxaUCtOJ47PJ9Vw2ySHZ14vFFNuDAtRTOBOlAFhUpsHqRoQFvFCkBGsRIQYq8hzw==", "dev": true, - "requires": {} + "requires": { + "@firebase/component": "0.6.1", + "@firebase/messaging": "0.12.1", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==", + "dev": true }, "@firebase/performance": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.4.2.tgz", - "integrity": "sha512-irHTCVWJ/sxJo0QHg+yQifBeVu8ZJPihiTqYzBUz/0AGc51YSt49FZwqSfknvCN2+OfHaazz/ARVBn87g7Ex8g==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.1.tgz", + "integrity": "sha512-mT/CWz3CLgyn/a3sO/TJgrTt+RA3DfuvWwGXY9zmIiuBZY2bDi1M2uMefJdJKc9sBUPRajNF6RL10nGYq3BAuQ==", "dev": true, "requires": { - "@firebase/component": "0.1.19", - "@firebase/installations": "0.4.17", - "@firebase/logger": "0.2.6", - "@firebase/performance-types": "0.0.13", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/performance-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.1.tgz", + "integrity": "sha512-4mn6eS7r2r+ZAHvU0OHE+3ZO+x6gOVhf2ypBoijuDNaRNjSn9GcvA8udD4IbJ8FNv/k7mbbtA9AdxVb701Lr1g==", + "dev": true, + "requires": { + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.1", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } } } }, "@firebase/performance-types": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", - "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==", "dev": true }, - "@firebase/polyfill": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", - "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", + "@firebase/remote-config": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.1.tgz", + "integrity": "sha512-RCzBH3FjAPRSP3M1T7jdxLYBesIdLtNIQ0fR9ywJpGSSa0kIXEJ9iSZMTP+9pJtaCxz8db07FvjEqg7Y+lgjzg==", "dev": true, "requires": { - "core-js": "3.6.5", - "promise-polyfill": "8.1.3", - "whatwg-fetch": "2.0.4" + "@firebase/component": "0.6.1", + "@firebase/installations": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } } }, - "@firebase/remote-config": { - "version": "0.1.28", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.28.tgz", - "integrity": "sha512-4zSdyxpt94jAnFhO8toNjG8oMKBD+xTuBIcK+Nw8BdQWeJhEamgXlupdBARUk1uf3AvYICngHH32+Si/dMVTbw==", + "@firebase/remote-config-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.1.tgz", + "integrity": "sha512-RPCj7c2Q3QxMgJH3YCt0iD57KppFApghxAGETzlr6Jm6vT7k0vqvk2KgRBgKa4koJBsgwlUtRn2roaCqUEadyg==", "dev": true, "requires": { - "@firebase/component": "0.1.19", - "@firebase/installations": "0.4.17", - "@firebase/logger": "0.2.6", - "@firebase/remote-config-types": "0.1.9", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.1", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } } } }, "@firebase/remote-config-types": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", - "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==", "dev": true }, "@firebase/storage": { - "version": "0.3.43", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.43.tgz", - "integrity": "sha512-Jp54jcuyimLxPhZHFVAhNbQmgTu3Sda7vXjXrNpPEhlvvMSq4yuZBR6RrZxe/OrNVprLHh/6lTCjwjOVSo3bWA==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.10.1.tgz", + "integrity": "sha512-eN4ME+TFCh5KfyG9uo8PhE6cgKjK5Rb9eucQg1XEyLHMiaZiUv2xSuWehJn0FaL+UdteoaWKuRUZ4WXRDskXrA==", "dev": true, "requires": { - "@firebase/component": "0.1.19", - "@firebase/storage-types": "0.3.13", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/component": "0.6.1", + "@firebase/util": "1.9.0", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" }, "dependencies": { - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/storage-types": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.13.tgz", - "integrity": "sha512-pL7b8d5kMNCCL0w9hF7pr16POyKkb3imOW7w0qYrhBnbyJTdVxMWZhb0HxCFyQWC0w3EiIFFmxoz8NTFZDEFog==", - "dev": true, - "requires": {} - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, + "@firebase/storage-compat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.2.1.tgz", + "integrity": "sha512-H0oFdYsMn2Z6tP9tlVERBkJiZsCbFAcl3Li1dnpvDg9g323egdjCnUUgH/tJODRR/Y84iZSNRkg4FvHDVI/o7Q==", + "dev": true, + "requires": { + "@firebase/component": "0.6.1", + "@firebase/storage": "0.10.1", + "@firebase/storage-types": "0.7.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true } } }, + "@firebase/storage-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.7.0.tgz", + "integrity": "sha512-n/8pYd82hc9XItV3Pa2KGpnuJ/2h/n/oTAaBberhe6GeyWQPnsmwwRK94W3GxUwBA/ZsszBAYZd7w7tTE+6XXA==", + "dev": true, + "requires": {} + }, "@firebase/util": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.7.3.tgz", - "integrity": "sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.0.tgz", + "integrity": "sha512-oeoq/6Sr9btbwUQs5HPfeww97bf7qgBbkknbDTXpRaph2LZ23O9XLCE5tJy856SBmGQfO4xBZP8dyryLLM2nSQ==", "dev": true, "requires": { "tslib": "^2.1.0" @@ -16359,9 +16907,9 @@ } }, "@firebase/webchannel-wrapper": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.4.0.tgz", - "integrity": "sha512-8cUA/mg0S+BxIZ72TdZRsXKBP5n5uRcE3k29TZhZw6oIiHBt9JA7CTb/4pE1uKtE/q5NeTY2tBDcagoZ+1zjXQ==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz", + "integrity": "sha512-BpiZLBWdLFw+qFel9p3Zs1jD6QmH7Ii4aTDu6+vx8ShdidChZUXqDhYJly4ZjSgQh54miXbBgBrk0S+jTIh/Qg==", "dev": true }, "@gar/promisify": { @@ -16371,16 +16919,46 @@ "optional": true }, "@google-cloud/firestore": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", - "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.2.tgz", + "integrity": "sha512-f7xFwINJveaqTFcgy0G4o2CBPm0Gv9lTGQ4dQt+7skwaHs3ytdue9ma8oQZYXKNoWcAoDIMQ929Dk0KOIocxFg==", "dev": true, "optional": true, "requires": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" + "google-gax": "^3.5.2", + "protobufjs": "^7.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "dev": true, + "optional": true + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "dev": true, + "optional": true, + "requires": { + "@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" + } + } } }, "@google-cloud/paginator": { @@ -16440,14 +17018,6 @@ "extend": "^3.0.2" } }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, "gaxios": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.0.tgz", @@ -16485,26 +17055,6 @@ "lru-cache": "^6.0.0" } }, - "google-gax": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.0.3.tgz", - "integrity": "sha512-oS1x9DWOmC2xX6pl2W92KtyR50x1zh1aEh0dxUCpu7GCv62aQ/MMkax2kJb/T6OKj/Yn8MYT8bIGyU+trk/p2Q==", - "requires": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "6.11.3", - "retry-request": "^5.0.0" - } - }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -16528,63 +17078,143 @@ "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } + } + } + }, + "@google-cloud/storage": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", + "dev": true, + "optional": true, + "requires": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" + }, + "dependencies": { + "@google-cloud/projectify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "dev": true, + "optional": true }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "@google-cloud/promisify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "dev": true, + "optional": true }, - "proto3-json-serializer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.0.0.tgz", - "integrity": "sha512-uEylKn4a7I6ZtLZ0fwCJCdQqr2vMsGtxxwKZIoqy4VwYeK9HKpCiG8WMBdtodV+1UO5YHHvHvb39b5CyRWT+9g==", + "gaxios": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", + "dev": true, + "optional": true, "requires": { - "protobufjs": "^6.11.2" + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" } }, - "retry-request": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.0.tgz", - "integrity": "sha512-vBZdBxUordje9253imlmGtppC5gdcwZmNz7JnU2ui+KKFPk25keR+0c020AVV20oesYxIFOI0Kh3HE88/59ieg==", + "gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "dev": true, + "optional": true, + "requires": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", + "dev": true, + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dev": true, + "optional": true, + "requires": { + "node-forge": "^1.3.1" + } + }, + "gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dev": true, + "optional": true, + "requires": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "optional": true + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "optional": true, "requires": { - "debug": "^4.1.1", - "extend": "^3.0.2" + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" } - } - } - }, - "@google-cloud/storage": { - "version": "5.20.5", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", - "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", - "dev": true, - "optional": true, - "requires": { - "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", - "abort-controller": "^3.0.0", - "arrify": "^2.0.0", - "async-retry": "^1.3.3", - "compressible": "^2.0.12", - "configstore": "^5.0.0", - "duplexify": "^4.0.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "gaxios": "^4.0.0", - "google-auth-library": "^7.14.1", - "hash-stream-validation": "^0.2.2", - "mime": "^3.0.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "pumpify": "^2.0.0", - "retry-request": "^4.2.2", - "stream-events": "^1.0.4", - "teeny-request": "^7.1.3", - "uuid": "^8.0.0", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { + }, "mime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", @@ -16611,18 +17241,59 @@ "dev": true }, "@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "requires": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + } + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "requires": { + "@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" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + } + } + } } }, "@grpc/proto-loader": { "version": "0.6.13", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dev": true, "requires": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", @@ -17012,12 +17683,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.2.0.tgz", "integrity": "sha512-BNKB9fiYVghALJzCuWO3eNYfdTExPVK4ykrtmfNfy0A6UWYhOYjGMXifUmkunDJNL8ju9tBobo8jF0WR9zGy1Q==" }, - "@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", - "dev": true - }, "@pnpm/network.ca-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", @@ -17316,30 +17981,21 @@ "dev": true }, "@types/express": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.0.tgz", - "integrity": "sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==", + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.16.tgz", + "integrity": "sha512-LkKpqRZ7zqXJuvoELakaFYuETHjZkSol8EV6cNnyishutDBCCdv6+dsKPbKkCcIk57qRphOLY5sEgClw1bO3gA==", "dev": true, "requires": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.31", + "@types/qs": "*", "@types/serve-static": "*" } }, - "@types/express-jwt": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", - "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", - "dev": true, - "requires": { - "@types/express": "*", - "@types/express-unless": "*" - } - }, "@types/express-serve-static-core": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", - "integrity": "sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==", + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", "dev": true, "requires": { "@types/node": "*", @@ -17347,15 +18003,6 @@ "@types/range-parser": "*" } }, - "@types/express-unless": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.3.tgz", - "integrity": "sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, "@types/firebase": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/firebase/-/firebase-3.2.1.tgz", @@ -17422,6 +18069,11 @@ "integrity": "sha512-LisgKLlYQk19baQwjkBZZXdJL0KbeTpdEnrAfz5hQACbklCY0gVFnsKUyjfNWF1UQsCSjw93Sj5jSbiO8RPfdw==", "dev": true }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" + }, "@types/lodash": { "version": "4.14.149", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", @@ -17433,6 +18085,15 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, "@types/marked": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz", @@ -17457,6 +18118,11 @@ } } }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -18033,7 +18699,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "requires": {} }, "acorn-walk": { @@ -18841,6 +19506,14 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "requires": { + "lodash": "^4.17.15" + } + }, "chai": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", @@ -19234,15 +19907,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", - "dev": true - }, - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true }, "core-util-is": { @@ -19542,12 +20209,6 @@ "esutils": "^2.0.2" } }, - "dom-storage": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", - "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==", - "dev": true - }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -19673,6 +20334,11 @@ "dev": true, "optional": true }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, "env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -20256,14 +20922,12 @@ "eslint-visitor-keys": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", - "dev": true + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==" }, "espree": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", - "dev": true, "requires": { "acorn": "^8.7.0", "acorn-jsx": "^5.3.1", @@ -20699,141 +21363,61 @@ } }, "firebase": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.24.0.tgz", - "integrity": "sha512-j6jIyGFFBlwWAmrlUg9HyQ/x+YpsPkc/TTkbTyeLwwAJrpAmmEHNPT6O9xtAnMV4g7d3RqLL/u9//aZlbY4rQA==", - "dev": true, - "requires": { - "@firebase/analytics": "0.6.0", - "@firebase/app": "0.6.11", - "@firebase/app-types": "0.6.1", - "@firebase/auth": "0.15.0", - "@firebase/database": "0.6.13", - "@firebase/firestore": "1.18.0", - "@firebase/functions": "0.5.1", - "@firebase/installations": "0.4.17", - "@firebase/messaging": "0.7.1", - "@firebase/performance": "0.4.2", - "@firebase/polyfill": "0.3.36", - "@firebase/remote-config": "0.1.28", - "@firebase/storage": "0.3.43", - "@firebase/util": "0.3.2" - }, - "dependencies": { - "@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", - "dev": true - }, - "@firebase/auth-interop-types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", - "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", - "dev": true, - "requires": {} - }, - "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", - "dev": true, - "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" - } - }, - "@firebase/database": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.13.tgz", - "integrity": "sha512-NommVkAPzU7CKd1gyehmi3lz0K78q0KOfiex7Nfy7MBMwknLm7oNqKovXSgQV1PCLvKXvvAplDSFhDhzIf9obA==", - "dev": true, - "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "faye-websocket": "0.11.3", - "tslib": "^1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", - "dev": true - }, - "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - } + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.16.0.tgz", + "integrity": "sha512-nNLpDwJvfP3crRc6AjnHH46TAkFzk8zimNVMJfYRCwAf5amOSGyU8duuc3IsJF6dQGiYLSfzfr2tMCsQa+rhKQ==", + "dev": true, + "requires": { + "@firebase/analytics": "0.9.1", + "@firebase/analytics-compat": "0.2.1", + "@firebase/app": "0.9.1", + "@firebase/app-check": "0.6.1", + "@firebase/app-check-compat": "0.3.1", + "@firebase/app-compat": "0.2.1", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "0.21.1", + "@firebase/auth-compat": "0.3.1", + "@firebase/database": "0.14.1", + "@firebase/database-compat": "0.3.1", + "@firebase/firestore": "3.8.1", + "@firebase/firestore-compat": "0.3.1", + "@firebase/functions": "0.9.1", + "@firebase/functions-compat": "0.3.1", + "@firebase/installations": "0.6.1", + "@firebase/installations-compat": "0.2.1", + "@firebase/messaging": "0.12.1", + "@firebase/messaging-compat": "0.2.1", + "@firebase/performance": "0.6.1", + "@firebase/performance-compat": "0.2.1", + "@firebase/remote-config": "0.4.1", + "@firebase/remote-config-compat": "0.2.1", + "@firebase/storage": "0.10.1", + "@firebase/storage-compat": "0.2.1", + "@firebase/util": "1.9.0" } }, "firebase-admin": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-10.3.0.tgz", - "integrity": "sha512-A0wgMLEjyVyUE+heyMJYqHRkPVjpebhOYsa47RHdrTM4ltApcx8Tn86sUmjqxlfh09gNnILAm7a8q5+FmgBYpg==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.5.0.tgz", + "integrity": "sha512-bBdlYtNvXx8yZGdCd00NrfZl1o1A0aXOw5h8q5PwC8RXikOLNXq8vYtSKW44dj8zIaafVP6jFdcUXZem/LMsHA==", "dev": true, "requires": { "@fastify/busboy": "^1.1.0", - "@firebase/database-compat": "^0.2.0", - "@firebase/database-types": "^0.9.7", - "@google-cloud/firestore": "^4.15.1", - "@google-cloud/storage": "^5.18.3", + "@firebase/database-compat": "^0.3.0", + "@firebase/database-types": "^0.10.0", + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2", "@types/node": ">=12.12.47", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", "node-forge": "^1.3.1", - "uuid": "^8.3.2" + "uuid": "^9.0.0" }, "dependencies": { - "@firebase/database-types": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.17.tgz", - "integrity": "sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==", - "dev": true, - "requires": { - "@firebase/app-types": "0.8.1", - "@firebase/util": "1.7.3" - } - }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "dev": true, - "requires": { - "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" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "dev": true } } @@ -21303,51 +21887,269 @@ } } }, - "google-discovery-to-swagger": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/google-discovery-to-swagger/-/google-discovery-to-swagger-2.1.0.tgz", - "integrity": "sha512-MI1gfmWPkuXCp6yH+9rfd8ZG8R1R5OIyY4WlKDTqr2+ere1gt2Ne4DSEu8HM7NkwKpuVCE5TrTRAPfm3ownMUQ==", - "dev": true, - "requires": { - "json-schema-compatibility": "^1.1.0", - "jsonpath": "^1.0.2", - "lodash": "^4.17.15", - "mime-db": "^1.21.0", - "mime-lookup": "^0.0.2", - "traverse": "~0.6.6", - "urijs": "^1.17.0" - }, - "dependencies": { - "traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", - "dev": true - } - } - }, - "google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", - "dev": true, - "optional": true, - "requires": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" - } - }, + "google-discovery-to-swagger": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/google-discovery-to-swagger/-/google-discovery-to-swagger-2.1.0.tgz", + "integrity": "sha512-MI1gfmWPkuXCp6yH+9rfd8ZG8R1R5OIyY4WlKDTqr2+ere1gt2Ne4DSEu8HM7NkwKpuVCE5TrTRAPfm3ownMUQ==", + "dev": true, + "requires": { + "json-schema-compatibility": "^1.1.0", + "jsonpath": "^1.0.2", + "lodash": "^4.17.15", + "mime-db": "^1.21.0", + "mime-lookup": "^0.0.2", + "traverse": "~0.6.6", + "urijs": "^1.17.0" + }, + "dependencies": { + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + } + } + }, + "google-gax": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "requires": { + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^8.0.2", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", + "retry-request": "^5.0.0" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "gaxios": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + } + }, + "gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "requires": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "requires": { + "node-forge": "^1.3.1" + } + }, + "gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "requires": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "requires": { + "@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" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + } + } + }, + "protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "requires": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "google-p12-pem": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", @@ -21654,13 +22456,6 @@ "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" }, - "hash-stream-validation": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", - "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", - "dev": true, - "optional": true - }, "hasha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", @@ -21822,9 +22617,9 @@ } }, "idb": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", - "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", "dev": true }, "ieee754": { @@ -22305,13 +23100,10 @@ } }, "jose": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", - "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", - "dev": true, - "requires": { - "@panva/asn1.js": "^1.0.0" - } + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", + "integrity": "sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A==", + "dev": true }, "js-tokens": { "version": "4.0.0", @@ -22328,11 +23120,63 @@ "esprima": "^4.0.0" } }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "requires": { + "xmlcreate": "^2.0.4" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "requires": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + }, + "dependencies": { + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "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==" + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "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==" + } + } + }, "jsdoc-type-pratt-parser": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.0.1.tgz", @@ -22513,22 +23357,32 @@ } }, "jwks-rsa": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", - "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", "dev": true, "requires": { - "@types/express-jwt": "0.0.42", - "debug": "^4.3.2", - "jose": "^2.0.5", + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", + "debug": "^4.3.4", + "jose": "^4.10.4", "limiter": "^1.1.5", "lru-memoizer": "^2.1.4" }, "dependencies": { + "@types/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -22557,6 +23411,14 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "requires": { + "graceful-fs": "^4.1.9" + } + }, "kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", @@ -22628,6 +23490,14 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", @@ -22660,7 +23530,7 @@ "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "dev": true }, "lodash.defaults": { @@ -22690,30 +23560,6 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true - }, - "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==", - "dev": true - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true - }, - "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==", - "dev": true - }, "lodash.isobject": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", @@ -22727,24 +23573,12 @@ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, - "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==", - "dev": true - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -22875,7 +23709,7 @@ "lru-cache": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", "dev": true, "requires": { "pseudomap": "^1.0.1", @@ -22885,7 +23719,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true } } @@ -22992,6 +23826,31 @@ "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", "dev": true }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + } + } + }, + "markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "requires": {} + }, "marked": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", @@ -23017,6 +23876,11 @@ } } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -24382,12 +25246,6 @@ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "optional": true }, - "promise-polyfill": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", - "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==", - "dev": true - }, "promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -24418,19 +25276,44 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, "proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", - "dev": true, - "optional": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", + "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", "requires": { - "protobufjs": "^6.11.2" + "protobufjs": "^7.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, + "protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "requires": { + "@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" + } + } } }, "protobufjs": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "dev": true, "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -24450,7 +25333,8 @@ "@types/node": { "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", - "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", + "dev": true } } }, @@ -24542,7 +25426,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", "dev": true }, "psl": { @@ -24559,18 +25443,6 @@ "once": "^1.3.1" } }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "dev": true, - "optional": true, - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -24927,6 +25799,14 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "requires": { + "lodash": "^4.17.21" + } + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -24959,11 +25839,9 @@ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" }, "retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", - "dev": true, - "optional": true, + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", "requires": { "debug": "^4.1.1", "extend": "^3.0.2" @@ -24973,8 +25851,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, "requires": { "ms": "2.1.2" } @@ -24982,9 +25858,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -25820,6 +26694,11 @@ } } }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" + }, "tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", @@ -25894,9 +26773,9 @@ } }, "teeny-request": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", - "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "dev": true, "optional": true, "requires": { @@ -25904,7 +26783,7 @@ "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" }, "dependencies": { "@tootallnate/once": { @@ -25942,6 +26821,13 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, "optional": true + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "optional": true } } }, @@ -26221,6 +27107,16 @@ } } }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==" + }, "unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -26231,6 +27127,11 @@ "through": "^2.3.8" } }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -26589,12 +27490,6 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, - "whatwg-fetch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", - "dev": true - }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -26747,11 +27642,10 @@ "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", - "dev": true + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" }, "xregexp": { "version": "2.0.0", diff --git a/package.json b/package.json index 9653e259f2a..c24694256e9 100644 --- a/package.json +++ b/package.json @@ -216,8 +216,8 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-jsdoc": "^39.2.9", "eslint-plugin-prettier": "^4.0.0", - "firebase": "^7.24.0", - "firebase-admin": "^10.0.0", + "firebase": "^9.16.0", + "firebase-admin": "^11.5.0", "firebase-functions": "^4.1.0", "google-discovery-to-swagger": "^2.1.0", "googleapis": "^105.0.0", diff --git a/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts b/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts index 3f3e51f56ec..d39f87b2241 100644 --- a/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts +++ b/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts @@ -1,7 +1,8 @@ import { Bucket } from "@google-cloud/storage"; import { expect } from "chai"; -import * as firebase from "firebase"; -import * as admin from "firebase-admin"; +import firebasePkg from "firebase/compat/app"; +import { applicationDefault, cert, deleteApp, getApp, initializeApp } from "firebase-admin/app"; +import { getStorage } from "firebase-admin/storage"; import * as fs from "fs"; import * as puppeteer from "puppeteer"; import { TEST_ENV } from "./env"; @@ -22,6 +23,11 @@ const TEST_FILE_NAME = "testing/storage_ref/testFile"; // Example use: emulatorOnly.it("Local only test case", () => {...}); const emulatorOnly = { it: TEST_ENV.useProductionServers ? it.skip : it }; +// This is a 'workaround' to prevent typescript from renaming the import. That +// causes issues when page.evaluate is run with the rename, since the renamed +// values don't exist in the created page. +const firebase = firebasePkg; + describe("Firebase Storage JavaScript SDK conformance tests", () => { const storageBucket = TEST_ENV.appConfig.storageBucket; const expectedFirebaseHost = TEST_ENV.firebaseHost; @@ -42,16 +48,14 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { filename: string, text: string, format?: string, - metadata?: firebase.storage.UploadMetadata + metadata?: firebasePkg.storage.UploadMetadata ): Promise { return page.evaluate( async (filename, text, format, metadata) => { try { - const task = await firebase - .storage() - .ref(filename) - .putString(text, format, JSON.parse(metadata)); - return task.state; + const ref = firebase.storage().ref(filename); + const res = await ref.putString(text, format, JSON.parse(metadata)); + return res.state; } catch (err) { if (err instanceof Error) { throw err.message; @@ -90,10 +94,10 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { // Init GCS admin SDK. const credential = TEST_ENV.prodServiceAccountKeyJson - ? admin.credential.cert(TEST_ENV.prodServiceAccountKeyJson) - : admin.credential.applicationDefault(); - admin.initializeApp({ credential }); - testBucket = admin.storage().bucket(storageBucket); + ? cert(TEST_ENV.prodServiceAccountKeyJson) + : applicationDefault(); + initializeApp({ credential }); + testBucket = getStorage().bucket(storageBucket); authHeader = { Authorization: `Bearer ${await TEST_ENV.adminAccessTokenGetter}` }; // Init fake browser page. @@ -104,30 +108,39 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { page = await browser.newPage(); await page.goto("https://example.com", { waitUntil: "networkidle2" }); await page.addScriptTag({ - url: "https://www.gstatic.com/firebasejs/9.9.1/firebase-app-compat.js", + url: "https://www.gstatic.com/firebasejs/9.16.0/firebase-app-compat.js", }); await page.addScriptTag({ - url: "https://www.gstatic.com/firebasejs/9.9.1/firebase-auth-compat.js", + url: "https://www.gstatic.com/firebasejs/9.16.0/firebase-auth-compat.js", }); await page.addScriptTag({ - url: "https://www.gstatic.com/firebasejs/9.9.1/firebase-storage-compat.js", + url: "https://www.gstatic.com/firebasejs/9.16.0/firebase-storage-compat.js", }); // Init Firebase app in browser context and maybe set emulator host overrides. - await page.evaluate( - (appConfig, useProductionServers, authEmulatorHost, storageEmulatorHost) => { - firebase.initializeApp(appConfig); - if (!useProductionServers) { - firebase.auth().useEmulator(authEmulatorHost); - const [storageHost, storagePort] = storageEmulatorHost.split(":") as string[]; - (firebase.storage() as any).useEmulator(storageHost, storagePort); - } - }, - TEST_ENV.appConfig, - TEST_ENV.useProductionServers, - TEST_ENV.authEmulatorHost, - TEST_ENV.storageEmulatorHost.replace(/^(https?:|)\/\//, "") - ); + console.error("we're going to use this config", TEST_ENV.appConfig); + await page + .evaluate( + (appConfig, useProductionServers, authEmulatorHost, storageEmulatorHost) => { + // throw new Error(window.firebase.toString()); + // if (firebase.apps.length <= 0) { + firebase.initializeApp(appConfig); + // } + if (!useProductionServers) { + firebase.app().auth().useEmulator(authEmulatorHost); + const [storageHost, storagePort] = storageEmulatorHost.split(":"); + firebase.app().storage().useEmulator(storageHost, Number(storagePort)); + } + }, + TEST_ENV.appConfig, + TEST_ENV.useProductionServers, + TEST_ENV.authEmulatorHost, + TEST_ENV.storageEmulatorHost.replace(/^(https?:|)\/\//, "") + ) + .catch((reason) => { + console.error("*** ", reason); + throw reason; + }); }); beforeEach(async () => { @@ -142,7 +155,7 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { after(async function (this) { this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); - admin.app().delete(); + await deleteApp(getApp()); fs.rmSync(tmpDir, { recursive: true, force: true }); await page.close(); await browser.close(); @@ -271,7 +284,7 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { throw err; } }, IMAGE_FILE_BASE64); - expect(uploadState!).to.include("User does not have permission"); + expect(uploadState).to.include("User does not have permission"); }); it("should return a 403 on rules deny when overwriting existing file", async () => { @@ -289,7 +302,7 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { await uploadText(page, "upload/allowIfNoExistingFile.txt", "some-content"); const uploadState = await shouldThrowOnUpload(); - expect(uploadState!).to.include("User does not have permission"); + expect(uploadState).to.include("User does not have permission"); }); it("should default to application/octet-stream", async () => { @@ -483,18 +496,16 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { } await signInToFirebaseAuth(page); - const promises: Promise[] = []; + const values: string[] = []; for (const singleFileName of allFileNames) { - promises.push( + values.push( await page.evaluate((filename) => { return firebase.storage().ref(filename).getDownloadURL(); }, singleFileName) ); } - Promise.all(promises).then((values) => { - expect(values.length).to.be.equal(10); - }); + expect(values.length).to.be.equal(10); }); }); @@ -523,7 +534,8 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { "contentEncoding", "contentType", ]); - expect(metadata.type).to.be.eql("file"); + // Unsure why `type` still exists in practice but not the typing. + // expect(metadata.type).to.be.eql("file"); expect(metadata.bucket).to.be.eql(storageBucket); expect(metadata.generation).to.be.a("string"); // Firebase Storage automatically updates metadata with a download token on data or diff --git a/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts b/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts index 18703e6b775..55b41f64024 100644 --- a/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts +++ b/scripts/storage-emulator-integration/conformance/gcs-js-sdk.test.ts @@ -330,10 +330,11 @@ describe("GCS Javascript SDK conformance tests", () => { }); it("should list files in sub-directory (using directory)", async () => { - const [files, , { prefixes }] = await testBucket.getFiles({ + const res = await testBucket.getFiles({ autoPaginate: false, - directory: "testing/", + prefix: "testing/", }); + const [files, , { prefixes }] = res; expect(prefixes).to.be.undefined; expect(files.map((file) => file.name)).to.deep.equal([TESTING_FILE]); diff --git a/scripts/storage-emulator-integration/conformance/persistence.test.ts b/scripts/storage-emulator-integration/conformance/persistence.test.ts index 059ce72e48f..b6067959f71 100644 --- a/scripts/storage-emulator-integration/conformance/persistence.test.ts +++ b/scripts/storage-emulator-integration/conformance/persistence.test.ts @@ -2,7 +2,7 @@ import * as puppeteer from "puppeteer"; import { expect } from "chai"; import * as admin from "firebase-admin"; import { Bucket } from "@google-cloud/storage"; -import * as firebase from "firebase"; +import firebasePkg from "firebase/compat/app"; import { EmulatorEndToEndTest } from "../../integration-helpers/framework"; import * as fs from "fs"; import { TEST_ENV } from "./env"; @@ -17,6 +17,11 @@ import { const TEST_FILE_NAME = "public/testFile"; +// This is a 'workaround' to prevent typescript from renaming the import. That +// causes issues when page.evaluate is run with the rename, since the renamed +// values don't exist in the created page. +const firebase = firebasePkg; + // Tests files uploaded from one SDK are available in others. describe("Storage persistence conformance tests", () => { // Temp directory to store generated files. diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index f2fe4cefceb..078be5c9b9b 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -19,4 +19,3 @@ mocha scripts/storage-emulator-integration/internal/tests.ts mocha scripts/storage-emulator-integration/multiple-targets/tests.ts mocha scripts/storage-emulator-integration/conformance/*.test.ts - diff --git a/src/test/hosting/initMiddleware.spec.ts b/src/test/hosting/initMiddleware.spec.ts index f3798573844..0e55ebd590e 100644 --- a/src/test/hosting/initMiddleware.spec.ts +++ b/src/test/hosting/initMiddleware.spec.ts @@ -144,7 +144,7 @@ describe("initMiddleware", () => { beforeEach(async () => { port = await portfinder.getPortPromise(); - await new Promise((resolve) => (server = app.listen(port, resolve))); + await new Promise((resolve) => (server = app.listen(port, resolve))); }); afterEach(async () => { From 2ec0d5e2c3765063c6fc8d5d7a615114d4b14363 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 24 Jan 2023 14:55:54 -0800 Subject: [PATCH 0768/1699] update firebase-admin in emulator-tests (#5447) * update firebase-admin in emulator-tests * skip test that isn't working correctly * try out a fix --- .../functions/package-lock.json | 2523 ++++++++++------- scripts/emulator-tests/functions/package.json | 2 +- .../functionsEmulatorRuntime.spec.ts | 2 +- 3 files changed, 1532 insertions(+), 995 deletions(-) diff --git a/scripts/emulator-tests/functions/package-lock.json b/scripts/emulator-tests/functions/package-lock.json index 006a71765d8..1fef8ed3335 100644 --- a/scripts/emulator-tests/functions/package-lock.json +++ b/scripts/emulator-tests/functions/package-lock.json @@ -9,198 +9,119 @@ "version": "0.0.1", "dependencies": { "express": "^4.18.1", - "firebase-admin": "^9.12.0", + "firebase-admin": "^11.5.0", "firebase-functions": "^3.22.0" }, "engines": { "node": "16" } }, - "node_modules/@firebase/app": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.33.tgz", - "integrity": "sha512-7K7ljuFhbT9uF0gTvuA7ZrpFFnS1eJLplfjJdjDQFWyjD6Cwk0FXNdu75WvoWgywoQCGiVBX8u5Jb437UQIhWQ==", - "peer": true, - "dependencies": { - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat": { - "version": "0.1.34", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.34.tgz", - "integrity": "sha512-3XSrHDgtASIH8j6sDngiKykDcqlEM0mYplJTYdyN69ruZ1o0M+bUhIvX9mUoRelWZGT1BcMpFmh/62vz/wN72Q==", - "peer": true, - "dependencies": { - "@firebase/app": "0.7.33", - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/component": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", - "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", - "peer": true, - "dependencies": { - "@firebase/util": "1.6.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-compat/node_modules/@firebase/logger": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", - "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", - "peer": true, - "dependencies": { - "tslib": "^2.1.0" + "node_modules/@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "optional": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@firebase/app-compat/node_modules/@firebase/util": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", - "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", - "peer": true, + "node_modules/@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", "dependencies": { - "tslib": "^2.1.0" + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=14" } }, "node_modules/@firebase/app-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", - "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" - }, - "node_modules/@firebase/app/node_modules/@firebase/component": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", - "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", - "peer": true, - "dependencies": { - "@firebase/util": "1.6.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app/node_modules/@firebase/logger": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", - "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app/node_modules/@firebase/util": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", - "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" }, "node_modules/@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" }, "node_modules/@firebase/component": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", - "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.1.tgz", + "integrity": "sha512-yvKthG0InjFx9aOPnh6gk0lVNfNVEtyq3LwXgZr+hOwD0x/CtXq33XCpqv0sQj5CA4FdMy8OO+y9edI+ZUw8LA==", "dependencies": { - "@firebase/util": "1.5.2", + "@firebase/util": "1.9.0", "tslib": "^2.1.0" } }, "node_modules/@firebase/database": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.8.tgz", - "integrity": "sha512-JBQVfFLzfhxlQbl4OU6ov9fdsddkytBQdtSSR49cz48homj38ccltAhK6seum+BI7f28cV2LFHF9672lcN+qxA==", - "dependencies": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.13", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.5.2", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.1.tgz", + "integrity": "sha512-iX6/p7hoxUMbYAGZD+D97L05xQgpkslF2+uJLZl46EdaEfjVMEwAdy7RS/grF96kcFZFg502LwPYTXoIdrZqOA==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "node_modules/@firebase/database-compat": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.8.tgz", - "integrity": "sha512-dhXr5CSieBuKNdU96HgeewMQCT9EgOIkfF1GNy+iRrdl7BWLxmlKuvLfK319rmIytSs/vnCzcD9uqyxTeU/A3A==", - "dependencies": { - "@firebase/component": "0.5.13", - "@firebase/database": "0.12.8", - "@firebase/database-types": "0.9.7", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.5.2", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/database-compat/node_modules/@firebase/database-types": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.7.tgz", - "integrity": "sha512-EFhgL89Fz6DY3kkB8TzdHvdu8XaqqvzcF2DLVOXEnQ3Ms7L755p5EO42LfxXoJqb9jKFvgLpFmKicyJG25WFWw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.1.tgz", + "integrity": "sha512-sI7LNh0C8PCq9uUKjrBKLbZvqHTSjsf2LeZRxin+rHVegomjsOAYk9OzYwxETWh3URhpMkCM8KcTl7RVwAldog==", "dependencies": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.5.2" + "@firebase/component": "0.6.1", + "@firebase/database": "0.14.1", + "@firebase/database-types": "0.10.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", + "tslib": "^2.1.0" } }, "node_modules/@firebase/database-types": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", - "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.1.tgz", + "integrity": "sha512-UgUx9VakTHbP2WrVUdYrUT2ofTFVfWjGW2O1fwuvvMyo6WSnuSyO5nB1u0cyoMPvO25dfMIUVerfK7qFfwGL3Q==", "dependencies": { - "@firebase/app-types": "0.6.3" + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.0" } }, - "node_modules/@firebase/database-types/node_modules/@firebase/app-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", - "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==" - }, "node_modules/@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@firebase/util": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", - "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.0.tgz", + "integrity": "sha512-oeoq/6Sr9btbwUQs5HPfeww97bf7qgBbkknbDTXpRaph2LZ23O9XLCE5tJy856SBmGQfO4xBZP8dyryLLM2nSQ==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@google-cloud/firestore": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", - "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.2.tgz", + "integrity": "sha512-f7xFwINJveaqTFcgy0G4o2CBPm0Gv9lTGQ4dQt+7skwaHs3ytdue9ma8oQZYXKNoWcAoDIMQ929Dk0KOIocxFg==", "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" + "google-gax": "^3.5.2", + "protobufjs": "^7.0.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=12.0.0" } }, "node_modules/@google-cloud/paginator": { @@ -217,55 +138,49 @@ } }, "node_modules/@google-cloud/projectify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", - "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", "optional": true, "engines": { - "node": ">=10" + "node": ">=12.0.0" } }, "node_modules/@google-cloud/promisify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", - "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", "optional": true, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/@google-cloud/storage": { - "version": "5.20.5", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", - "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "optional": true, "dependencies": { "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", - "configstore": "^5.0.0", "duplexify": "^4.0.0", "ent": "^2.2.0", "extend": "^3.0.2", - "gaxios": "^4.0.0", - "google-auth-library": "^7.14.1", - "hash-stream-validation": "^0.2.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", - "retry-request": "^4.2.2", - "stream-events": "^1.0.4", - "teeny-request": "^7.1.3", - "uuid": "^8.0.0", - "xdg-basedir": "^4.0.0" + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/@google-cloud/storage/node_modules/mime": { @@ -280,13 +195,22 @@ "node": ">=10.0.0" } }, + "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==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "optional": true, "dependencies": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" }, "engines": { @@ -294,15 +218,15 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", "optional": true, "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.11.3", + "protobufjs": "^7.0.0", "yargs": "^16.2.0" }, "bin": { @@ -312,14 +236,6 @@ "node": ">=6" } }, - "node_modules/@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -426,9 +342,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.29", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz", - "integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==", + "version": "4.17.32", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz", + "integrity": "sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -436,19 +352,41 @@ } }, "node_modules/@types/jsonwebtoken": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz", - "integrity": "sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, "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==", "optional": true }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -502,6 +440,27 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "optional": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -561,6 +520,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "optional": true + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -584,6 +549,12 @@ "retry": "0.13.1" } }, + "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==", + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -605,14 +576,20 @@ "optional": true }, "node_modules/bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "optional": true, "engines": { "node": "*" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "optional": true + }, "node_modules/body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -636,6 +613,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.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", @@ -661,6 +647,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "optional": 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/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -702,22 +716,11 @@ "node": ">= 0.6" } }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "optional": true, - "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" - }, - "engines": { - "node": ">=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==", + "optional": true }, "node_modules/content-disposition": { "version": "0.5.4", @@ -763,15 +766,6 @@ "node": ">= 0.10" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -780,6 +774,12 @@ "ms": "2.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -797,29 +797,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/dicer": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz", - "integrity": "sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "optional": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/duplexify": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", @@ -874,6 +851,15 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "optional": true }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -888,6 +874,103 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "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==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "optional": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "optional": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "optional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -958,10 +1041,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "optional": true + }, "node_modules/fast-text-encoding": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", - "integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", "optional": true }, "node_modules/faye-websocket": { @@ -993,24 +1082,25 @@ } }, "node_modules/firebase-admin": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", - "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.5.0.tgz", + "integrity": "sha512-bBdlYtNvXx8yZGdCd00NrfZl1o1A0aXOw5h8q5PwC8RXikOLNXq8vYtSKW44dj8zIaafVP6jFdcUXZem/LMsHA==", "dependencies": { - "@firebase/database-compat": "^0.1.1", - "@firebase/database-types": "^0.7.2", + "@fastify/busboy": "^1.1.0", + "@firebase/database-compat": "^0.3.0", + "@firebase/database-types": "^0.10.0", "@types/node": ">=12.12.47", - "dicer": "^0.3.0", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", - "node-forge": "^0.10.0" + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", + "node-forge": "^1.3.1", + "uuid": "^9.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "optionalDependencies": { - "@google-cloud/firestore": "^4.5.0", - "@google-cloud/storage": "^5.3.0" + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2" } }, "node_modules/firebase-functions": { @@ -1051,6 +1141,12 @@ "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==", + "optional": true + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1063,32 +1159,31 @@ "optional": true }, "node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", "optional": true, "dependencies": { - "abort-controller": "^3.0.0", "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", "node-fetch": "^2.6.7" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", "optional": true, "dependencies": { - "gaxios": "^4.0.0", + "gaxios": "^5.0.0", "json-bigint": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/get-caller-file": { @@ -1113,57 +1208,78 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/google-auth-library": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", - "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", "optional": true, "dependencies": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", "jws": "^4.0.0", "lru-cache": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "optional": true, "dependencies": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", + "google-auth-library": "^8.0.2", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", + "retry-request": "^5.0.0" }, "bin": { - "compileProtos": "build/tools/compileProtos.js" + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/google-p12-pem": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", - "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", "optional": true, "dependencies": { "node-forge": "^1.3.1" @@ -1172,16 +1288,7 @@ "gp12-pem": "build/src/bin/gp12-pem.js" }, "engines": { - "node": ">=10" - } - }, - "node_modules/google-p12-pem/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==", - "optional": true, - "engines": { - "node": ">= 6.13.0" + "node": ">=12.0.0" } }, "node_modules/graceful-fs": { @@ -1191,17 +1298,17 @@ "optional": true }, "node_modules/gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", "optional": true, "dependencies": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", "jws": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" } }, "node_modules/has": { @@ -1215,6 +1322,15 @@ "node": ">= 0.4.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==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -1226,12 +1342,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash-stream-validation": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", - "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", - "optional": true - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1336,19 +1446,14 @@ "node": ">=0.10.0" } }, - "node_modules/idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", - "peer": true - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "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==", "optional": true, - "engines": { - "node": ">=0.8.19" + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, "node_modules/inherits": { @@ -1373,15 +1478,6 @@ "node": ">=8" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -1400,24 +1496,50 @@ "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", "optional": true }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "optional": true - }, "node_modules/jose": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", - "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", + "integrity": "sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "optional": true, "dependencies": { - "@panva/asn1.js": "^1.0.0" + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" }, - "engines": { - "node": ">=10.13.0 < 13 || >=13.7.0" + "bin": { + "jsdoc": "jsdoc.js" }, - "funding": { - "url": "https://github.com/sponsors/panva" + "engines": { + "node": ">=12.0.0" } }, "node_modules/json-bigint": { @@ -1430,24 +1552,18 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/jwa": { @@ -1474,14 +1590,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -1494,28 +1602,28 @@ } }, "node_modules/jwks-rsa": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.4.tgz", - "integrity": "sha512-mpArfgPkUpX11lNtGxsF/szkasUcbWHGplZl/uFvFO2NuMHmt0dQXIihh0rkPU2yQd5niQtuUHbXnG/WKiXF6Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", "dependencies": { - "@types/express": "^4.17.13", - "@types/jsonwebtoken": "^8.5.8", + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", "debug": "^4.3.4", - "jose": "^2.0.5", + "jose": "^4.10.4", "limiter": "^1.1.5", "lru-memoizer": "^2.1.4" }, "engines": { - "node": ">=10 < 13 || >=14" + "node": ">=14" } }, "node_modules/jwks-rsa/node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz", + "integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", + "@types/express-serve-static-core": "^4.17.31", "@types/qs": "*", "@types/serve-static": "*" } @@ -1551,11 +1659,42 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "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/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1572,41 +1711,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -1617,7 +1721,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -1648,21 +1751,50 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "optional": true, "dependencies": { - "semver": "^6.0.0" + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" }, - "engines": { - "node": ">=8" + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "optional": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "optional": true, + "bin": { + "marked": "bin/marked.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 12" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1714,6 +1846,39 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1747,11 +1912,11 @@ } }, "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "engines": { - "node": ">= 6.0.0" + "node": ">= 6.13.0" } }, "node_modules/object-assign": { @@ -1799,6 +1964,23 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1822,24 +2004,45 @@ "node": ">= 0.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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", + "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", "optional": true, "dependencies": { - "protobufjs": "^6.11.2" + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" } }, "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "hasInstallScript": true, "optional": true, "dependencies": { @@ -1853,15 +2056,47 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "optional": true, + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" }, "bin": { "pbjs": "bin/pbjs", "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" } }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "optional": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1879,27 +2114,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "node_modules/qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -1959,6 +2173,15 @@ "node": ">=0.10.0" } }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -1969,16 +2192,16 @@ } }, "node_modules/retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", "optional": true, "dependencies": { "debug": "^4.1.1", "extend": "^3.0.2" }, "engines": { - "node": ">=8.10.0" + "node": ">=12" } }, "node_modules/retry-request/node_modules/debug": { @@ -2004,6 +2227,63 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "optional": true }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": 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/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2029,12 +2309,17 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "optional": true, + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -2097,11 +2382,14 @@ "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==", - "optional": true + "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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/statuses": { "version": "2.0.1", @@ -2126,14 +2414,6 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "optional": true }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2169,26 +2449,73 @@ "node": ">=8" } }, + "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==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "optional": true }, - "node_modules/teeny-request": { + "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", - "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "optional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "optional": true + }, + "node_modules/teeny-request": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "optional": true, "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" + } + }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" } }, "node_modules/toidentifier": { @@ -2205,9 +2532,21 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } }, "node_modules/type-is": { "version": "1.6.18", @@ -2221,27 +2560,30 @@ "node": ">= 0.6" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "optional": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "optional": true, - "dependencies": { - "crypto-random-string": "^2.0.0" + "bin": { + "uglifyjs": "bin/uglifyjs" }, "engines": { - "node": ">=8" + "node": ">=0.8.0" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2265,10 +2607,9 @@ } }, "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==", - "optional": true, + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { "uuid": "dist/bin/uuid" } @@ -2316,6 +2657,15 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -2339,26 +2689,11 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "optional": true }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "optional": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "optional": true, - "engines": { - "node": ">=8" - } + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "optional": true }, "node_modules/y18n": { "version": "5.0.8", @@ -2372,8 +2707,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { "version": "16.2.0", @@ -2416,190 +2750,100 @@ } }, "dependencies": { - "@firebase/app": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.33.tgz", - "integrity": "sha512-7K7ljuFhbT9uF0gTvuA7ZrpFFnS1eJLplfjJdjDQFWyjD6Cwk0FXNdu75WvoWgywoQCGiVBX8u5Jb437UQIhWQ==", - "peer": true, - "requires": { - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", - "idb": "7.0.1", - "tslib": "^2.1.0" - }, - "dependencies": { - "@firebase/component": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", - "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", - "peer": true, - "requires": { - "@firebase/util": "1.6.3", - "tslib": "^2.1.0" - } - }, - "@firebase/logger": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", - "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", - "peer": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/util": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", - "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", - "peer": true, - "requires": { - "tslib": "^2.1.0" - } - } - } + "@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "optional": true }, - "@firebase/app-compat": { - "version": "0.1.34", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.34.tgz", - "integrity": "sha512-3XSrHDgtASIH8j6sDngiKykDcqlEM0mYplJTYdyN69ruZ1o0M+bUhIvX9mUoRelWZGT1BcMpFmh/62vz/wN72Q==", - "peer": true, + "@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", "requires": { - "@firebase/app": "0.7.33", - "@firebase/component": "0.5.17", - "@firebase/logger": "0.3.3", - "@firebase/util": "1.6.3", - "tslib": "^2.1.0" - }, - "dependencies": { - "@firebase/component": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.17.tgz", - "integrity": "sha512-mTM5CBSIlmI+i76qU4+DhuExnWtzcPS3cVgObA3VAjliPPr3GrUlTaaa8KBGfxsD27juQxMsYA0TvCR5X+GQ3Q==", - "peer": true, - "requires": { - "@firebase/util": "1.6.3", - "tslib": "^2.1.0" - } - }, - "@firebase/logger": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.3.tgz", - "integrity": "sha512-POTJl07jOKTOevLXrTvJD/VZ0M6PnJXflbAh5J9VGkmtXPXNG6MdZ9fmRgqYhXKTaDId6AQenQ262uwgpdtO0Q==", - "peer": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/util": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.6.3.tgz", - "integrity": "sha512-FujteO6Zjv6v8A4HS+t7c+PjU0Kaxj+rOnka0BsI/twUaCC9t8EQPmXpWZdk7XfszfahJn2pqsflUWUhtUkRlg==", - "peer": true, - "requires": { - "tslib": "^2.1.0" - } - } + "text-decoding": "^1.0.0" } }, "@firebase/app-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", - "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" }, "@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", - "requires": {} + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" }, "@firebase/component": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.13.tgz", - "integrity": "sha512-hxhJtpD8Ppf/VU2Rlos6KFCEV77TGIGD5bJlkPK1+B/WUe0mC6dTjW7KhZtXTc+qRBp9nFHWcsIORnT8liHP9w==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.1.tgz", + "integrity": "sha512-yvKthG0InjFx9aOPnh6gk0lVNfNVEtyq3LwXgZr+hOwD0x/CtXq33XCpqv0sQj5CA4FdMy8OO+y9edI+ZUw8LA==", "requires": { - "@firebase/util": "1.5.2", + "@firebase/util": "1.9.0", "tslib": "^2.1.0" } }, "@firebase/database": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.8.tgz", - "integrity": "sha512-JBQVfFLzfhxlQbl4OU6ov9fdsddkytBQdtSSR49cz48homj38ccltAhK6seum+BI7f28cV2LFHF9672lcN+qxA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.1.tgz", + "integrity": "sha512-iX6/p7hoxUMbYAGZD+D97L05xQgpkslF2+uJLZl46EdaEfjVMEwAdy7RS/grF96kcFZFg502LwPYTXoIdrZqOA==", "requires": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.13", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.5.2", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "@firebase/database-compat": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.8.tgz", - "integrity": "sha512-dhXr5CSieBuKNdU96HgeewMQCT9EgOIkfF1GNy+iRrdl7BWLxmlKuvLfK319rmIytSs/vnCzcD9uqyxTeU/A3A==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.1.tgz", + "integrity": "sha512-sI7LNh0C8PCq9uUKjrBKLbZvqHTSjsf2LeZRxin+rHVegomjsOAYk9OzYwxETWh3URhpMkCM8KcTl7RVwAldog==", "requires": { - "@firebase/component": "0.5.13", - "@firebase/database": "0.12.8", - "@firebase/database-types": "0.9.7", - "@firebase/logger": "0.3.2", - "@firebase/util": "1.5.2", + "@firebase/component": "0.6.1", + "@firebase/database": "0.14.1", + "@firebase/database-types": "0.10.1", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.0", "tslib": "^2.1.0" - }, - "dependencies": { - "@firebase/database-types": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.7.tgz", - "integrity": "sha512-EFhgL89Fz6DY3kkB8TzdHvdu8XaqqvzcF2DLVOXEnQ3Ms7L755p5EO42LfxXoJqb9jKFvgLpFmKicyJG25WFWw==", - "requires": { - "@firebase/app-types": "0.7.0", - "@firebase/util": "1.5.2" - } - } } }, "@firebase/database-types": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", - "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.1.tgz", + "integrity": "sha512-UgUx9VakTHbP2WrVUdYrUT2ofTFVfWjGW2O1fwuvvMyo6WSnuSyO5nB1u0cyoMPvO25dfMIUVerfK7qFfwGL3Q==", "requires": { - "@firebase/app-types": "0.6.3" - }, - "dependencies": { - "@firebase/app-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", - "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==" - } + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.0" } }, "@firebase/logger": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.2.tgz", - "integrity": "sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", "requires": { "tslib": "^2.1.0" } }, "@firebase/util": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.5.2.tgz", - "integrity": "sha512-YvBH2UxFcdWG2HdFnhxZptPl2eVFlpOyTH66iDo13JPEYraWzWToZ5AMTtkyRHVmu7sssUpQlU9igy1KET7TOw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.0.tgz", + "integrity": "sha512-oeoq/6Sr9btbwUQs5HPfeww97bf7qgBbkknbDTXpRaph2LZ23O9XLCE5tJy856SBmGQfO4xBZP8dyryLLM2nSQ==", "requires": { "tslib": "^2.1.0" } }, "@google-cloud/firestore": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", - "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.4.2.tgz", + "integrity": "sha512-f7xFwINJveaqTFcgy0G4o2CBPm0Gv9lTGQ4dQt+7skwaHs3ytdue9ma8oQZYXKNoWcAoDIMQ929Dk0KOIocxFg==", "optional": true, "requires": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" + "google-gax": "^3.5.2", + "protobufjs": "^7.0.0" } }, "@google-cloud/paginator": { @@ -2613,46 +2857,40 @@ } }, "@google-cloud/projectify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", - "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", "optional": true }, "@google-cloud/promisify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", - "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", "optional": true }, "@google-cloud/storage": { - "version": "5.20.5", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz", - "integrity": "sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.9.0.tgz", + "integrity": "sha512-0mn9DUe3dtyTWLsWLplQP3gzPolJ5kD4PwHuzeD3ye0SAQ+oFfDbT8d+vNZxqyvddL2c6uNP72TKETN2PQxDKg==", "optional": true, "requires": { "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", "abort-controller": "^3.0.0", - "arrify": "^2.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", - "configstore": "^5.0.0", "duplexify": "^4.0.0", "ent": "^2.2.0", "extend": "^3.0.2", - "gaxios": "^4.0.0", - "google-auth-library": "^7.14.1", - "hash-stream-validation": "^0.2.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "pumpify": "^2.0.0", - "retry-request": "^4.2.2", - "stream-events": "^1.0.4", - "teeny-request": "^7.1.3", - "uuid": "^8.0.0", - "xdg-basedir": "^4.0.0" + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" }, "dependencies": { "mime": { @@ -2660,37 +2898,38 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "optional": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true } } }, "@grpc/grpc-js": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz", - "integrity": "sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", "optional": true, "requires": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", - "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", "optional": true, "requires": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.11.3", + "protobufjs": "^7.0.0", "yargs": "^16.2.0" } }, - "@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" - }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2794,9 +3033,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.29", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz", - "integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==", + "version": "4.17.32", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz", + "integrity": "sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -2804,19 +3043,41 @@ } }, "@types/jsonwebtoken": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz", - "integrity": "sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", "requires": { "@types/node": "*" } }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, "@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", "optional": true }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -2864,6 +3125,19 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "optional": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "optional": true, + "requires": {} + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -2905,6 +3179,12 @@ "color-convert": "^2.0.1" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "optional": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -2925,6 +3205,12 @@ "retry": "0.13.1" } }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "optional": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2932,9 +3218,15 @@ "optional": true }, "bignumber.js": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", - "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "optional": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "optional": true }, "body-parser": { @@ -2956,6 +3248,15 @@ "unpipe": "1.0.0" } }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "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", @@ -2975,6 +3276,25 @@ "get-intrinsic": "^1.0.2" } }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -3010,20 +3330,12 @@ "mime-db": ">= 1.43.0 < 2" } }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "optional": true, - "requires": { - "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" - } - }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "optional": true + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3056,12 +3368,6 @@ "vary": "^1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "optional": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3070,6 +3376,12 @@ "ms": "2.0.0" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "optional": true + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3080,23 +3392,6 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, - "dicer": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz", - "integrity": "sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==", - "requires": { - "streamsearch": "^1.1.0" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "optional": true, - "requires": { - "is-obj": "^2.0.0" - } - }, "duplexify": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", @@ -3148,6 +3443,12 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "optional": true }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3159,6 +3460,68 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "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==", + "optional": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "optional": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "optional": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "optional": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -3220,10 +3583,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "optional": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "optional": true + }, "fast-text-encoding": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", - "integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", "optional": true }, "faye-websocket": { @@ -3249,19 +3618,20 @@ } }, "firebase-admin": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.12.0.tgz", - "integrity": "sha512-AtA7OH5RbIFGoc0gZOQgaYC6cdjdhZv4w3XgWoupkPKO1HY+0GzixOuXDa75kFeoVyhIyo4PkLg/GAC1dC1P6w==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.5.0.tgz", + "integrity": "sha512-bBdlYtNvXx8yZGdCd00NrfZl1o1A0aXOw5h8q5PwC8RXikOLNXq8vYtSKW44dj8zIaafVP6jFdcUXZem/LMsHA==", "requires": { - "@firebase/database-compat": "^0.1.1", - "@firebase/database-types": "^0.7.2", - "@google-cloud/firestore": "^4.5.0", - "@google-cloud/storage": "^5.3.0", + "@fastify/busboy": "^1.1.0", + "@firebase/database-compat": "^0.3.0", + "@firebase/database-types": "^0.10.0", + "@google-cloud/firestore": "^6.4.0", + "@google-cloud/storage": "^6.5.2", "@types/node": ">=12.12.47", - "dicer": "^0.3.0", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", - "node-forge": "^0.10.0" + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", + "node-forge": "^1.3.1", + "uuid": "^9.0.0" } }, "firebase-functions": { @@ -3287,6 +3657,12 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3299,12 +3675,11 @@ "optional": true }, "gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", "optional": true, "requires": { - "abort-controller": "^3.0.0", "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", @@ -3312,12 +3687,12 @@ } }, "gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", "optional": true, "requires": { - "gaxios": "^4.0.0", + "gaxios": "^5.0.0", "json-bigint": "^1.0.0" } }, @@ -3337,59 +3712,65 @@ "has-symbols": "^1.0.3" } }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, "google-auth-library": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", - "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", "optional": true, "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", "jws": "^4.0.0", "lru-cache": "^6.0.0" } }, "google-gax": { - "version": "2.30.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz", - "integrity": "sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", + "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", "optional": true, "requires": { - "@grpc/grpc-js": "~1.6.0", - "@grpc/proto-loader": "^0.6.12", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.14.0", + "google-auth-library": "^8.0.2", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", - "proto3-json-serializer": "^0.1.8", - "protobufjs": "6.11.3", - "retry-request": "^4.0.0" + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.1.2", + "protobufjs-cli": "1.0.2", + "retry-request": "^5.0.0" } }, "google-p12-pem": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", - "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", "optional": true, "requires": { "node-forge": "^1.3.1" - }, - "dependencies": { - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "optional": true - } } }, "graceful-fs": { @@ -3399,13 +3780,13 @@ "optional": true }, "gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", "optional": true, "requires": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", "jws": "^4.0.0" } }, @@ -3417,17 +3798,17 @@ "function-bind": "^1.1.1" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "optional": true + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, - "hash-stream-validation": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", - "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", - "optional": true - }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -3508,17 +3889,15 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", - "peer": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "optional": true + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } }, "inherits": { "version": "2.0.4", @@ -3536,12 +3915,6 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "optional": true }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "optional": true - }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3554,18 +3927,41 @@ "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", "optional": true }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "optional": true - }, "jose": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", - "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", + "integrity": "sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A==" + }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, "requires": { - "@panva/asn1.js": "^1.0.0" + "xmlcreate": "^2.0.4" + } + }, + "jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "optional": true, + "requires": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" } }, "json-bigint": { @@ -3578,20 +3974,14 @@ } }, "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "requires": { "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "jwa": { @@ -3617,11 +4007,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -3637,25 +4022,25 @@ } }, "jwks-rsa": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.4.tgz", - "integrity": "sha512-mpArfgPkUpX11lNtGxsF/szkasUcbWHGplZl/uFvFO2NuMHmt0dQXIihh0rkPU2yQd5niQtuUHbXnG/WKiXF6Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", "requires": { - "@types/express": "^4.17.13", - "@types/jsonwebtoken": "^8.5.8", + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", "debug": "^4.3.4", - "jose": "^2.0.5", + "jose": "^4.10.4", "limiter": "^1.1.5", "lru-memoizer": "^2.1.4" }, "dependencies": { "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz", + "integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==", "requires": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", + "@types/express-serve-static-core": "^4.17.31", "@types/qs": "*", "@types/serve-static": "*" } @@ -3685,11 +4070,39 @@ "safe-buffer": "^5.0.1" } }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, "limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3706,41 +4119,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "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==" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -3751,7 +4129,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, "requires": { "yallist": "^4.0.0" } @@ -3781,15 +4158,38 @@ } } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "optional": true, "requires": { - "semver": "^6.0.0" + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" } }, + "markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "optional": true, + "requires": {} + }, + "marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "optional": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3823,6 +4223,27 @@ "mime-db": "1.52.0" } }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "optional": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "optional": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3842,9 +4263,9 @@ } }, "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, "object-assign": { "version": "4.1.1", @@ -3879,6 +4300,20 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3893,24 +4328,36 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "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==", + "optional": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true + }, "proto3-json-serializer": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz", - "integrity": "sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.0.tgz", + "integrity": "sha512-SjXwUWe/vANGs/mJJTbw5++7U67nwsymg7qsoPtw6GiXqw3kUy8ByojrlEdVE2efxAdKreX8WkDafxvYW95ZQg==", "optional": true, "requires": { - "protobufjs": "^6.11.2" + "protobufjs": "^7.0.0" } }, "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -3923,9 +4370,34 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==", + "optional": true + } + } + }, + "protobufjs-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", + "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "optional": true, + "requires": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^3.6.3", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" } }, "proxy-addr": { @@ -3942,27 +4414,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -4004,6 +4455,15 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "optional": true }, + "requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "requires": { + "lodash": "^4.17.21" + } + }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -4011,9 +4471,9 @@ "optional": true }, "retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", "optional": true, "requires": { "debug": "^4.1.1", @@ -4037,6 +4497,50 @@ } } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "optional": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4048,10 +4552,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "optional": true + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } }, "send": { "version": "0.18.0", @@ -4106,10 +4612,10 @@ "object-inspect": "^1.9.0" } }, - "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==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "optional": true }, "statuses": { @@ -4132,11 +4638,6 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "optional": true }, - "streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4166,23 +4667,58 @@ "ansi-regex": "^5.0.1" } }, + "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==", + "optional": true + }, "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "optional": true }, - "teeny-request": { + "supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.2.0.tgz", - "integrity": "sha512-SyY0pek1zWsi0LRVAALem+avzMLc33MKW/JLLakdP4s9+D7+jHcy5x6P+h94g2QNZsAqQNfX5lsbd3WSeJXrrw==", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "optional": true + }, + "teeny-request": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", + "integrity": "sha512-34pe0a4zASseXZCKdeTiIZqSKA8ETHb1EwItZr01PAR3CLPojeAKgSjzeNS4373gi59hNulyDrPKEbh2zO9sCg==", "optional": true, "requires": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "uuid": "^9.0.0" + } + }, + "text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "requires": { + "rimraf": "^3.0.0" } }, "toidentifier": { @@ -4196,9 +4732,18 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2" + } }, "type-is": { "version": "1.6.18", @@ -4209,23 +4754,23 @@ "mime-types": "~2.1.24" } }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "optional": true, - "requires": { - "is-typedarray": "^1.0.0" - } + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "optional": true, - "requires": { - "crypto-random-string": "^2.0.0" - } + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true + }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true }, "unpipe": { "version": "1.0.0", @@ -4244,10 +4789,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" }, "vary": { "version": "1.1.2", @@ -4283,6 +4827,12 @@ "webidl-conversions": "^3.0.0" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4300,22 +4850,10 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "optional": true }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "optional": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "optional": true }, "y18n": { @@ -4327,8 +4865,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "16.2.0", diff --git a/scripts/emulator-tests/functions/package.json b/scripts/emulator-tests/functions/package.json index 0da3d2c98a4..954cce0f233 100644 --- a/scripts/emulator-tests/functions/package.json +++ b/scripts/emulator-tests/functions/package.json @@ -5,7 +5,7 @@ "main": "index.js", "dependencies": { "express": "^4.18.1", - "firebase-admin": "^9.12.0", + "firebase-admin": "^11.5.0", "firebase-functions": "^3.22.0" }, "engines": { diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index 7c473bf4efc..4bbd63ce45f 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -305,7 +305,7 @@ describe("FunctionsEmulator-Runtime", function () { .firestore.document("test/test") .onCreate(() => { console.log( - JSON.stringify(require("firebase-admin").firestore.FieldValue.increment(4)) + JSON.stringify(require("firebase-admin/firestore").FieldValue.increment(4)) ); return Promise.resolve(); }), From 225c1d77066d49c29901fc4da9ff7c3483131bd6 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 24 Jan 2023 15:32:59 -0800 Subject: [PATCH 0769/1699] yet another boring audit-based fixup PR (#5463) * fix json5 issue * update lock file and minimatch --- scripts/firepit-builder/package-lock.json | 433 +++++++++++++++++- .../hosting/package-lock.json | 12 +- 2 files changed, 435 insertions(+), 10 deletions(-) diff --git a/scripts/firepit-builder/package-lock.json b/scripts/firepit-builder/package-lock.json index a58efd9942f..53a99c3bdf8 100644 --- a/scripts/firepit-builder/package-lock.json +++ b/scripts/firepit-builder/package-lock.json @@ -1,8 +1,433 @@ { "name": "cloud_build", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "cloud_build", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "shelljs": "^0.8.5", + "yargs": "^13.3.0" + } + }, + "node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "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==" + }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "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==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "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==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "engines": { + "node": ">=4" + } + }, + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "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==" + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/resolve": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", + "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", + "dependencies": { + "is-core-module": "^2.8.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/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "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.1" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + }, "dependencies": { "ansi-regex": { "version": "4.1.1", @@ -160,9 +585,9 @@ } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } diff --git a/scripts/webframeworks-deploy-tests/hosting/package-lock.json b/scripts/webframeworks-deploy-tests/hosting/package-lock.json index 69330694bbc..8a08d3f70ce 100644 --- a/scripts/webframeworks-deploy-tests/hosting/package-lock.json +++ b/scripts/webframeworks-deploy-tests/hosting/package-lock.json @@ -2040,9 +2040,9 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -4526,9 +4526,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" From 06b8bad39e8f92f9f348a751076fc016b6583a79 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 25 Jan 2023 13:13:30 -0800 Subject: [PATCH 0770/1699] Add python support for Functions Emulator. (#5423) Add support for loading and serving functions written using the Firebase Functions Python SDK (WIP: https://github.com/firebase/firebase-functions-python) This PR is a fork of https://github.com/firebase/firebase-tools/pull/4653 extended with support for emulating Python functions. Python Runtime Delegate implementation is unsurprising but does include some additional wrapper code to make sure all commands (e.g. spinning up the admin server) runs within the virtualenv environment. For now, we hardcode virtual environment `venv` directory exists on the developer's laptop, but we'll later add support for specifying arbitrary directory for specifying virtualenv directory via firebase.json configuration. Another note is that each emulated Python function will bind to a Port instead of Unix Domain Socket (UDS) as done when emulating Node.js function. This is because there is no straightfoward, platform-neutral way to bind python webserver to UDS. Finding large number of open port might have a bit more performance penalty and cause bugs due to race condition (similar to https://github.com/firebase/firebase-tools/issues/5418) but it seems that we have no other choice atm. --- .../emulator-tests/functionsEmulator.spec.ts | 1 + .../functions/runtimes/discovery/index.ts | 2 +- src/deploy/functions/runtimes/index.ts | 7 +- src/deploy/functions/runtimes/python/index.ts | 152 ++++++++++++++++++ src/emulator/functionsEmulator.ts | 124 +++++++++++--- src/emulator/functionsRuntimeWorker.ts | 32 ++-- src/functions/python.ts | 32 ++++ .../runtimes/discovery/index.spec.ts | 4 +- .../functions/runtimes/python/index.spec.ts | 45 ++++++ .../emulators/functionsRuntimeWorker.spec.ts | 4 +- 10 files changed, 354 insertions(+), 49 deletions(-) create mode 100644 src/deploy/functions/runtimes/python/index.ts create mode 100644 src/functions/python.ts create mode 100644 src/test/deploy/functions/runtimes/python/index.spec.ts diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index 30d336bc0b4..f6591d0e8e3 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -38,6 +38,7 @@ const TEST_BACKEND = { secretEnv: [], codebase: "default", bin: process.execPath, + runtime: "nodejs14", // NOTE: Use the following node bin path if you want to run test cases directly from your IDE. // bin: path.join(MODULE_ROOT, "node_modules/.bin/ts-node"), }; diff --git a/src/deploy/functions/runtimes/discovery/index.ts b/src/deploy/functions/runtimes/discovery/index.ts index 809671fa93a..e5e0e8ff71e 100644 --- a/src/deploy/functions/runtimes/discovery/index.ts +++ b/src/deploy/functions/runtimes/discovery/index.ts @@ -72,7 +72,7 @@ export async function detectFromPort( while (true) { try { - res = await Promise.race([fetch(`http://localhost:${port}/__/functions.yaml`), timedOut]); + res = await Promise.race([fetch(`http://127.0.0.1:${port}/__/functions.yaml`), timedOut]); break; } catch (err: any) { // Allow us to wait until the server is listening. diff --git a/src/deploy/functions/runtimes/index.ts b/src/deploy/functions/runtimes/index.ts index ce327dd4f8c..8864b127f44 100644 --- a/src/deploy/functions/runtimes/index.ts +++ b/src/deploy/functions/runtimes/index.ts @@ -1,6 +1,7 @@ import * as backend from "../backend"; import * as build from "../build"; import * as node from "./node"; +import * as python from "./python"; import * as validate from "../validate"; import { FirebaseError } from "../../../error"; @@ -9,7 +10,7 @@ const RUNTIMES: string[] = ["nodejs10", "nodejs12", "nodejs14", "nodejs16", "nod // Experimental runtimes are part of the Runtime type, but are in a // different list to help guard against some day accidentally iterating over // and printing a hidden runtime to the user. -const EXPERIMENTAL_RUNTIMES: string[] = []; +const EXPERIMENTAL_RUNTIMES: string[] = ["python310", "python311"]; export type Runtime = typeof RUNTIMES[number] | typeof EXPERIMENTAL_RUNTIMES[number]; /** Runtimes that can be found in existing backends but not used for new functions. */ @@ -34,6 +35,8 @@ const MESSAGE_FRIENDLY_RUNTIMES: Record = { nodejs14: "Node.js 14", nodejs16: "Node.js 16", nodejs18: "Node.js 18", + python310: "Python 3.10", + python311: "Python 3.11 (Preview)", }; /** @@ -113,7 +116,7 @@ export interface DelegateContext { } type Factory = (context: DelegateContext) => Promise; -const factories: Factory[] = [node.tryCreateDelegate]; +const factories: Factory[] = [node.tryCreateDelegate, python.tryCreateDelegate]; /** * diff --git a/src/deploy/functions/runtimes/python/index.ts b/src/deploy/functions/runtimes/python/index.ts new file mode 100644 index 00000000000..4e4989f9df5 --- /dev/null +++ b/src/deploy/functions/runtimes/python/index.ts @@ -0,0 +1,152 @@ +import * as fs from "fs"; +import * as path from "path"; +import fetch from "node-fetch"; +import { promisify } from "util"; + +import * as portfinder from "portfinder"; + +import * as runtimes from ".."; +import * as backend from "../../backend"; +import * as discovery from "../discovery"; +import { logger } from "../../../../logger"; +import { runWithVirtualEnv } from "../../../../functions/python"; +import { FirebaseError } from "../../../../error"; +import { Build } from "../../build"; + +const LATEST_VERSION: runtimes.Runtime = "python310"; + +/** + * Create a runtime delegate for the Python runtime, if applicable. + * + * @param context runtimes.DelegateContext + * @return Delegate Python runtime delegate + */ +export async function tryCreateDelegate( + context: runtimes.DelegateContext +): Promise { + const requirementsTextPath = path.join(context.sourceDir, "requirements.txt"); + + if (!(await promisify(fs.exists)(requirementsTextPath))) { + logger.debug("Customer code is not Python code."); + return; + } + const runtime = context.runtime ? context.runtime : LATEST_VERSION; + if (!runtimes.isValidRuntime(runtime)) { + throw new FirebaseError(`Runtime ${runtime} is not a valid Python runtime`); + } + return Promise.resolve(new Delegate(context.projectId, context.sourceDir, runtime)); +} + +export class Delegate implements runtimes.RuntimeDelegate { + public readonly name = "python"; + constructor( + private readonly projectId: string, + private readonly sourceDir: string, + public readonly runtime: runtimes.Runtime + ) {} + + private _bin = ""; + private _modulesDir = ""; + + get bin(): string { + if (this._bin === "") { + this._bin = this.getPythonBinary(); + } + return this._bin; + } + + async modulesDir(): Promise { + if (!this._modulesDir) { + const child = runWithVirtualEnv( + [ + this.bin, + "-c", + '"import firebase_functions; import os; print(os.path.dirname(firebase_functions.__file__))"', + ], + this.sourceDir, + {} + ); + let out = ""; + child.stdout?.on("data", (chunk: Buffer) => { + const chunkString = chunk.toString(); + out = out + chunkString; + logger.debug(`stdout: ${chunkString}`); + }); + await new Promise((resolve, reject) => { + child.on("exit", resolve); + child.on("error", reject); + }); + this._modulesDir = out.trim(); + } + return this._modulesDir; + } + + getPythonBinary(): string { + if (process.platform === "win32") { + // There is no easy way to get specific version of python executable in Windows. + return "python.exe"; + } + if (this.runtime === "python310") { + return "python3.10"; + } else if (this.runtime === "python311") { + return "python3.11"; + } + return "python"; + } + + validate(): Promise { + // TODO: make sure firebase-functions is included as a dep + return Promise.resolve(); + } + + watch(): Promise<() => Promise> { + return Promise.resolve(() => Promise.resolve()); + } + + async build(): Promise { + return Promise.resolve(); + } + + async serveAdmin(port: number, envs: backend.EnvironmentVariables): Promise<() => Promise> { + const modulesDir = await this.modulesDir(); + const envWithAdminPort = { + ...envs, + ADMIN_PORT: port.toString(), + }; + const args = [this.bin, path.join(modulesDir, "private", "serving.py")]; + logger.debug( + `Running admin server with args: ${JSON.stringify(args)} and env: ${JSON.stringify( + envWithAdminPort + )} in ${this.sourceDir}` + ); + const childProcess = runWithVirtualEnv(args, this.sourceDir, envWithAdminPort); + return Promise.resolve(async () => { + await fetch(`http://127.0.0.1:${port}/__/quitquitquit`); + const quitTimeout = setTimeout(() => { + if (!childProcess.killed) { + childProcess.kill("SIGKILL"); + } + }, 10_000); + clearTimeout(quitTimeout); + }); + } + + async discoverBuild( + _configValues: backend.RuntimeConfigValues, + envs: backend.EnvironmentVariables + ): Promise { + let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime); + if (!discovered) { + const adminPort = await portfinder.getPortPromise({ + port: 8081, + }); + const killProcess = await this.serveAdmin(adminPort, envs); + try { + discovered = await discovery.detectFromPort(adminPort, this.projectId, this.runtime); + } finally { + await killProcess(); + } + } + return discovered; + } +} diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index d8124797e19..34722120d5e 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -15,6 +15,7 @@ import { track, trackEmulator } from "../track"; import { Constants } from "./constants"; import { EmulatorInfo, EmulatorInstance, Emulators, FunctionsExecutionMode } from "./types"; import * as chokidar from "chokidar"; +import * as portfinder from "portfinder"; import * as spawn from "cross-spawn"; import { ChildProcess } from "child_process"; @@ -43,7 +44,7 @@ import { RuntimeWorker, RuntimeWorkerPool } from "./functionsRuntimeWorker"; import { PubsubEmulator } from "./pubsubEmulator"; import { FirebaseError } from "../error"; import { WorkQueue, Work } from "./workQueue"; -import { allSettled, connectableHostname, createDestroyer, debounce } from "../utils"; +import { allSettled, connectableHostname, createDestroyer, debounce, randomInt } from "../utils"; import { getCredentialPathAsync } from "../defaultCredentials"; import { AdminSdkConfig, @@ -60,6 +61,7 @@ import { AUTH_BLOCKING_EVENTS, BEFORE_CREATE_EVENT } from "../functions/events/v import { BlockingFunctionsConfig } from "../gcp/identityPlatform"; import { resolveBackend } from "../deploy/functions/build"; import { setEnvVarsForEmulators } from "./env"; +import { runWithVirtualEnv } from "../functions/python"; const EVENT_INVOKE = "functions:invoke"; // event name for UA const EVENT_INVOKE_GA4 = "functions_invoke"; // event name GA4 (alphanumertic) @@ -122,15 +124,41 @@ export interface FunctionsEmulatorArgs { projectAlias?: string; } -// FunctionsRuntimeInstance is the handler for a running function invocation +/** + * IPC connection info of a Function Runtime. + */ +export class IPCConn { + constructor(readonly socketPath: string) {} + + httpReqOpts(): http.RequestOptions { + return { + socketPath: this.socketPath, + }; + } +} + +/** + * TCP/IP connection info of a Function Runtime. + */ +export class TCPConn { + constructor(readonly host: string, readonly port: number) {} + + httpReqOpts(): http.RequestOptions { + return { + host: this.host, + port: this.port, + }; + } +} + export interface FunctionsRuntimeInstance { process: ChildProcess; // An emitter which sends our EmulatorLog events from the runtime. events: EventEmitter; // A cwd of the process cwd: string; - // Path to socket file used for HTTP-over-IPC comms. - socketPath: string; + // Communication info for the runtime + conn: IPCConn | TCPConn; } export interface InvokeRuntimeOpts { @@ -353,8 +381,8 @@ export class FunctionsEmulator implements EmulatorInstance { return new Promise((resolve, reject) => { const req = http.request( { + ...worker.runtime.conn.httpReqOpts(), path: `/`, - socketPath: worker.runtime.socketPath, headers: headers, }, resolve @@ -405,6 +433,7 @@ export class FunctionsEmulator implements EmulatorInstance { /.+?[\\\/]node_modules[\\\/].+?/, // Ignore node_modules /(^|[\/\\])\../, // Ignore files which begin the a period /.+\.log/, // Ignore files which have a .log extension + /.+?[\\\/]venv[\\\/].+?/, // Ignore site-packages in venv ], persistent: true, }); @@ -660,26 +689,30 @@ export class FunctionsEmulator implements EmulatorInstance { // In debug mode, we eagerly start the runtime processes to allow debuggers to attach // before invoking a function. if (this.args.debugPort) { - // Since we're about to start a runtime to be shared by all the functions in this codebase, - // we need to make sure it has all the secrets used by any function in the codebase. - emulatableBackend.secretEnv = Object.values( - triggerDefinitions.reduce( - (acc: Record, curr: EmulatedTriggerDefinition) => { - for (const secret of curr.secretEnvironmentVariables || []) { - acc[secret.key] = secret; - } - return acc; - }, - {} - ) - ); - try { - await this.startRuntime(emulatableBackend); - } catch (e: any) { - this.logger.logLabeled( - "ERROR", - `Failed to start functions in ${emulatableBackend.functionsDir}: ${e}` + if (!emulatableBackend.bin?.startsWith("node")) { + this.logger.log("WARN", "--inspect-functions only supported for Node.js runtimes."); + } else { + // Since we're about to start a runtime to be shared by all the functions in this codebase, + // we need to make sure it has all the secrets used by any function in the codebase. + emulatableBackend.secretEnv = Object.values( + triggerDefinitions.reduce( + (acc: Record, curr: EmulatedTriggerDefinition) => { + for (const secret of curr.secretEnvironmentVariables || []) { + acc[secret.key] = secret; + } + return acc; + }, + {} + ) ); + try { + await this.startRuntime(emulatableBackend); + } catch (e: any) { + this.logger.logLabeled( + "ERROR", + `Failed to start functions in ${emulatableBackend.functionsDir}: ${e}` + ); + } } } } @@ -1266,10 +1299,44 @@ export class FunctionsEmulator implements EmulatorInstance { process: childProcess, events: new EventEmitter(), cwd: backend.functionsDir, - socketPath, + conn: new IPCConn(socketPath), }); } + async startPython( + backend: EmulatableBackend, + envs: Record + ): Promise { + const args = ["functions-framework"]; + + if (this.args.debugPort) { + this.logger.log("WARN", "--inspect-functions not supported for Python functions. Ignored."); + } + + // No support generic socket interface for Unix Domain Socket/Named Pipe in the python. + // Use TCP/IP stack instead. + const port = await portfinder.getPortPromise({ + port: 8081 + randomInt(0, 1000), // Add a small jitter to avoid race condition. + }); + const childProcess = runWithVirtualEnv(args, backend.functionsDir, { + ...process.env, + ...envs, + // Required to flush stdout/stderr immediately to the piped channels. + PYTHONUNBUFFERED: "1", + // Required to prevent flask development server to reload on code changes. + DEBUG: "False", + HOST: "127.0.0.1", + PORT: port.toString(), + }); + + return { + process: childProcess, + events: new EventEmitter(), + cwd: backend.functionsDir, + conn: new TCPConn("127.0.0.1", port), + }; + } + async startRuntime( backend: EmulatableBackend, trigger?: EmulatedTriggerDefinition @@ -1277,7 +1344,12 @@ export class FunctionsEmulator implements EmulatorInstance { const runtimeEnv = this.getRuntimeEnvs(backend, trigger); const secretEnvs = await this.resolveSecretEnvs(backend, trigger); - const runtime = await this.startNode(backend, { ...runtimeEnv, ...secretEnvs }); + let runtime; + if (backend.runtime!.startsWith("python")) { + runtime = await this.startPython(backend, { ...runtimeEnv, ...secretEnvs }); + } else { + runtime = await this.startNode(backend, { ...runtimeEnv, ...secretEnvs }); + } const extensionLogInfo = { instanceId: backend.extensionInstanceId, ref: backend.extensionVersion?.ref, diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index 3358ac68311..5365b7989d2 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -8,6 +8,7 @@ import { EventEmitter } from "events"; import { EmulatorLogger, ExtensionLogInfo } from "./emulatorLogger"; import { FirebaseError } from "../error"; import { Serializable } from "child_process"; +import { IncomingMessage } from "http"; type LogListener = (el: EmulatorLog) => any; @@ -118,12 +119,12 @@ export class RuntimeWorker { return new Promise((resolve) => { const proxy = http.request( { + ...this.runtime.conn.httpReqOpts(), method: req.method, path: req.path, headers: req.headers, - socketPath: this.runtime.socketPath, }, - (_resp) => { + (_resp: IncomingMessage) => { resp.writeHead(_resp.statusCode || 200, _resp.headers); const piped = _resp.pipe(resp); piped.on("finish", () => { @@ -178,20 +179,19 @@ export class RuntimeWorker { isSocketReady(): Promise { return new Promise((resolve, reject) => { - const req = http - .request( - { - method: "GET", - path: "/__/health", - socketPath: this.runtime.socketPath, - }, - () => { - // Set the worker state to IDLE for new work - this.readyForWork(); - resolve(); - } - ) - .end(); + const req = http.request( + { + ...this.runtime.conn.httpReqOpts(), + method: "GET", + path: "/__/health", + }, + () => { + // Set the worker state to IDLE for new work + this.readyForWork(); + resolve(); + } + ); + req.end(); req.on("error", (error) => { reject(error); }); diff --git a/src/functions/python.ts b/src/functions/python.ts new file mode 100644 index 00000000000..f37b821e1d8 --- /dev/null +++ b/src/functions/python.ts @@ -0,0 +1,32 @@ +import * as path from "path"; +import * as spawn from "cross-spawn"; +import * as cp from "child_process"; +import { logger } from "../logger"; + +const DEFAULT_VENV_DIR = "venv"; + +/** + * Spawn a process inside the Python virtual environment if found. + */ +export function runWithVirtualEnv( + commandAndArgs: string[], + cwd: string, + envs: Record, + venvDir = DEFAULT_VENV_DIR +): cp.ChildProcess { + const activateScriptPath = + process.platform === "win32" ? ["Scripts", "activate.bat"] : ["bin", "activate"]; + const venvActivate = path.join(cwd, venvDir, ...activateScriptPath); + const command = process.platform === "win32" ? venvActivate : "source"; + const args = [process.platform === "win32" ? "" : venvActivate, "&&", ...commandAndArgs]; + logger.debug(`Running command with virtualenv: command=${command}, args=${JSON.stringify(args)}`); + + return spawn(command, args, { + shell: true, + cwd, + stdio: [/* stdin= */ "pipe", /* stdout= */ "pipe", /* stderr= */ "pipe", "pipe"], + // Linting disabled since internal types expect NODE_ENV which does not apply to Python runtimes. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + env: envs as any, + }); +} diff --git a/src/test/deploy/functions/runtimes/discovery/index.spec.ts b/src/test/deploy/functions/runtimes/discovery/index.spec.ts index 266cce9997a..0a6f07b3f62 100644 --- a/src/test/deploy/functions/runtimes/discovery/index.spec.ts +++ b/src/test/deploy/functions/runtimes/discovery/index.spec.ts @@ -96,12 +96,12 @@ describe("detectFromPort", () => { }); it("passes as smoke test", async () => { - nock("http://localhost:8080").get("/__/functions.yaml").times(5).replyWithError({ + nock("http://127.0.0.1:8080").get("/__/functions.yaml").times(5).replyWithError({ message: "Still booting", code: "ECONNREFUSED", }); - nock("http://localhost:8080").get("/__/functions.yaml").reply(200, YAML_TEXT); + nock("http://127.0.0.1:8080").get("/__/functions.yaml").reply(200, YAML_TEXT); const parsed = await discovery.detectFromPort(8080, "project", "nodejs16"); expect(parsed).to.deep.equal(BUILD); diff --git a/src/test/deploy/functions/runtimes/python/index.spec.ts b/src/test/deploy/functions/runtimes/python/index.spec.ts new file mode 100644 index 00000000000..c16dae73830 --- /dev/null +++ b/src/test/deploy/functions/runtimes/python/index.spec.ts @@ -0,0 +1,45 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; + +import * as python from "../../../../../deploy/functions/runtimes/python"; + +const PROJECT_ID = "test-project"; +const SOURCE_DIR = "/some/path/fns"; + +describe("PythonDelegate", () => { + describe("getPythonBinary", () => { + let platformMock: sinon.SinonStub; + + beforeEach(() => { + platformMock = sinon.stub(process, "platform"); + }); + + afterEach(() => { + platformMock.restore(); + }); + + it("returns specific version of the python binary corresponding to the runtime", () => { + platformMock.value("darwin"); + const requestedRuntime = "python310"; + const delegate = new python.Delegate(PROJECT_ID, SOURCE_DIR, requestedRuntime); + + expect(delegate.getPythonBinary()).to.equal("python3.10"); + }); + + it("returns generic python binary given non-recognized python runtime", () => { + platformMock.value("darwin"); + const requestedRuntime = "python312"; + const delegate = new python.Delegate(PROJECT_ID, SOURCE_DIR, requestedRuntime); + + expect(delegate.getPythonBinary()).to.equal("python"); + }); + + it("always returns version-neutral, python.exe on windows", () => { + platformMock.value("win32"); + const requestedRuntime = "python310"; + const delegate = new python.Delegate(PROJECT_ID, SOURCE_DIR, requestedRuntime); + + expect(delegate.getPythonBinary()).to.equal("python.exe"); + }); + }); +}); diff --git a/src/test/emulators/functionsRuntimeWorker.spec.ts b/src/test/emulators/functionsRuntimeWorker.spec.ts index d0cb0ca557d..00bc983930c 100644 --- a/src/test/emulators/functionsRuntimeWorker.spec.ts +++ b/src/test/emulators/functionsRuntimeWorker.spec.ts @@ -1,7 +1,7 @@ import * as httpMocks from "node-mocks-http"; import * as nock from "nock"; import { expect } from "chai"; -import { FunctionsRuntimeInstance } from "../../emulator/functionsEmulator"; +import { FunctionsRuntimeInstance, IPCConn } from "../../emulator/functionsEmulator"; import { EventEmitter } from "events"; import { RuntimeWorker, @@ -21,7 +21,7 @@ class MockRuntimeInstance implements FunctionsRuntimeInstance { events: EventEmitter = new EventEmitter(); exit: Promise; cwd = "/home/users/dir"; - socketPath = "/path/to/socket/foo.sock"; + conn = new IPCConn("/path/to/socket/foo.sock"); constructor() { this.exit = new Promise((resolve) => { From d59b78b4debeb231cea65d01afdcaf8e4d90b7b8 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 26 Jan 2023 10:26:41 -0800 Subject: [PATCH 0771/1699] Stop using AppEngine to check database mode (#5261) * Stop using AppEngine to check database mode * fixing tests * Removing unnecessary check for cloud resource location * remove outdated test * add changelog --- CHANGELOG.md | 5 +++-- src/firestore/checkDatabaseType.ts | 19 +++++++++++-------- src/init/features/firestore/index.ts | 4 +--- src/test/init/features/firestore.spec.ts | 11 +---------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 527a409d2f6..fcec825e1c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ -- Refactor Functions Emulator. (#5422) -- Fix race condition when discovering functions. (#5444) +- Refactors Functions Emulator. (#5422) +- Fixes race condition when discovering functions. (#5444) +- Fixes issue where `init firestore` was unecessarilly checking for default resource location. (#5230 and #5452) diff --git a/src/firestore/checkDatabaseType.ts b/src/firestore/checkDatabaseType.ts index 2d7c489e23c..897feaf484a 100644 --- a/src/firestore/checkDatabaseType.ts +++ b/src/firestore/checkDatabaseType.ts @@ -1,21 +1,24 @@ -import { appengineOrigin } from "../api"; +import { firestoreOrigin } from "../api"; import { Client } from "../apiv2"; import { logger } from "../logger"; /** * Determine the Firestore database type for a given project. One of: * - DATABASE_TYPE_UNSPECIFIED (unspecified) - * - CLOUD_DATASTORE (Datastore legacy) - * - CLOUD_FIRESTORE (Firestore native mode) - * - CLOUD_DATASTORE_COMPATIBILITY (Firestore datastore mode) + * - DATASTORE_MODE(Datastore legacy) + * - FIRESTORE_NATIVE (Firestore native mode) * * @param projectId the Firebase project ID. */ -export async function checkDatabaseType(projectId: string): Promise { +export async function checkDatabaseType( + projectId: string +): Promise<"DATASTORE_MODE" | "FIRESTORE_NATIVE" | "DATABASE_TYPE_UNSPECIFIED" | undefined> { try { - const client = new Client({ urlPrefix: appengineOrigin, apiVersion: "v1" }); - const resp = await client.get<{ databaseType?: string }>(`/apps/${projectId}`); - return resp.body.databaseType; + const client = new Client({ urlPrefix: firestoreOrigin, apiVersion: "v1" }); + const resp = await client.get<{ + type?: "DATASTORE_MODE" | "FIRESTORE_NATIVE" | "DATABASE_TYPE_UNSPECIFIED"; + }>(`/projects/${projectId}/databases/(default)`); + return resp.body.type; } catch (err: any) { logger.debug("error getting database type", err); return undefined; diff --git a/src/init/features/firestore/index.ts b/src/init/features/firestore/index.ts index 050d387b2dc..988644a2891 100644 --- a/src/init/features/firestore/index.ts +++ b/src/init/features/firestore/index.ts @@ -1,6 +1,5 @@ import { logger } from "../../../logger"; import * as apiEnabled from "../../../ensureApiEnabled"; -import { ensureLocationSet } from "../../../ensureCloudResourceLocation"; import { requirePermissions } from "../../../requirePermissions"; import { checkDatabaseType } from "../../../firestore/checkDatabaseType"; import * as rules from "./rules"; @@ -36,14 +35,13 @@ async function checkProjectSetup(setup: any, config: any, options: any) { if (!dbType) { throw firestoreUnusedError; - } else if (dbType !== "CLOUD_FIRESTORE") { + } else if (dbType !== "FIRESTORE_NATIVE") { throw new FirebaseError( `It looks like this project is using Cloud Datastore or Cloud Firestore in Datastore mode. The Firebase CLI can only manage projects using Cloud Firestore in Native mode. For more information, visit https://cloud.google.com/datastore/docs/firestore-or-datastore`, { exit: 1 } ); } - ensureLocationSet(setup.projectLocation, "Cloud Firestore"); await requirePermissions({ ...options, project: setup.projectId }); } diff --git a/src/test/init/features/firestore.spec.ts b/src/test/init/features/firestore.spec.ts index 012178165f3..38009e5299f 100644 --- a/src/test/init/features/firestore.spec.ts +++ b/src/test/init/features/firestore.spec.ts @@ -21,7 +21,7 @@ describe("firestore", () => { // By default, mock Firestore enabled in Native mode checkApiStub.returns(true); - checkDbTypeStub.returns("CLOUD_FIRESTORE"); + checkDbTypeStub.returns("FIRESTORE_NATIVE"); }); afterEach(() => { @@ -46,15 +46,6 @@ describe("firestore", () => { expect(_.get(setup, "config.firestore")).to.deep.equal({}); }); - it("should error when cloud resource location is not set", async () => { - const setup = { config: {}, projectId: "my-project-123" }; - - await expect(firestore.doSetup(setup, {}, {})).to.eventually.be.rejectedWith( - FirebaseError, - "Cloud resource location is not set" - ); - }); - it("should error when the firestore API is not enabled", async () => { checkApiStub.returns(false); From 5811aea8ca0c9628cfdc3ef24ba57a9eb363e8b5 Mon Sep 17 00:00:00 2001 From: Leonardo Ortiz Date: Thu, 26 Jan 2023 17:30:31 -0300 Subject: [PATCH 0772/1699] Pass `trailingSlash` from Next.js config to firebase.json (#5445) * Don't use internal redirects for the backend test * pass trailingSlash from next config to firebase.json Co-authored-by: James Daniels --- CHANGELOG.md | 2 ++ src/frameworks/index.ts | 3 +++ src/frameworks/next/index.ts | 15 +++++++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcec825e1c3..5349e595ed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ - Refactors Functions Emulator. (#5422) - Fixes race condition when discovering functions. (#5444) - Fixes issue where `init firestore` was unecessarilly checking for default resource location. (#5230 and #5452) +- Pass `trailingSlash` from Next.js config to `firebase.json` (#5445) +- Don't use Next.js internal redirects for the backend test (#5445) diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 2a6222b9b11..eec612381a4 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -41,6 +41,7 @@ export interface BuildResult { redirects?: any[]; headers?: any[]; wantsBackend?: boolean; + trailingSlash?: boolean; } export interface Framework { @@ -405,10 +406,12 @@ export async function prepareFrameworks( rewrites = [], redirects = [], headers = [], + trailingSlash, } = (await build(getProjectPath())) || {}; config.rewrites.push(...rewrites); config.redirects.push(...redirects); config.headers.push(...headers); + config.trailingSlash ??= trailingSlash; if (await pathExists(hostingDist)) await rm(hostingDist, { recursive: true }); await mkdirp(hostingDist); await ɵcodegenPublicDirectory(getProjectPath(), hostingDist); diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index 9e1b8690e37..0a4db4994ff 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -95,7 +95,7 @@ export async function build(dir: string): Promise { }); const reasonsForBackend = []; - const { distDir } = await getConfig(dir); + const { distDir, trailingSlash } = await getConfig(dir); if (await isUsingMiddleware(join(dir, distDir), false)) { reasonsForBackend.push("middleware"); @@ -171,7 +171,9 @@ export async function build(dir: string): Promise { headers, })); - const isEveryRedirectSupported = nextJsRedirects.every(isRedirectSupportedByHosting); + const isEveryRedirectSupported = nextJsRedirects + .filter((it) => !it.internal) + .every(isRedirectSupportedByHosting); if (!isEveryRedirectSupported) { reasonsForBackend.push("advanced redirects"); } @@ -227,7 +229,7 @@ export async function build(dir: string): Promise { console.log(""); } - return { wantsBackend, headers, redirects, rewrites }; + return { wantsBackend, headers, redirects, rewrites, trailingSlash }; } /** @@ -442,5 +444,10 @@ async function getConfig(dir: string): Promise } } } - return { distDir: ".next", ...config }; + return { + distDir: ".next", + // trailingSlash defaults to false in Next.js: https://nextjs.org/docs/api-reference/next.config.js/trailing-slash + trailingSlash: false, + ...config, + }; } From accea7abda3cc9fa6bb91368e4895faf95281c60 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 30 Jan 2023 11:05:12 -0800 Subject: [PATCH 0773/1699] Fix broken pnpm support for cf3 (#5467) Due to the way pnpm resolves packages with peer dependencies, location of the firebase functions sdk package isn't where the CLI expects it to be. We make the logic for finding the binary associated Firebase Functions SDK more robust by checking list of possible paths where it might exist. We also add integration test for pnpm in the functions discovery test. Fixes https://github.com/firebase/firebase-tools/issues/5448 --- CHANGELOG.md | 1 + .../fixtures/pnpm/firebase.json | 1 + .../fixtures/pnpm/functions/index.js | 10 +++ .../fixtures/pnpm/functions/package.json | 9 +++ .../fixtures/pnpm/install.sh | 5 ++ scripts/functions-discover-tests/run.sh | 3 + scripts/functions-discover-tests/tests.ts | 10 +++ src/deploy/functions/runtimes/node/index.ts | 71 ++++++++++++------- 8 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 scripts/functions-discover-tests/fixtures/pnpm/firebase.json create mode 100644 scripts/functions-discover-tests/fixtures/pnpm/functions/index.js create mode 100644 scripts/functions-discover-tests/fixtures/pnpm/functions/package.json create mode 100755 scripts/functions-discover-tests/fixtures/pnpm/install.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 5349e595ed6..3563c5bc59a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Fixes issue where `init firestore` was unecessarilly checking for default resource location. (#5230 and #5452) - Pass `trailingSlash` from Next.js config to `firebase.json` (#5445) - Don't use Next.js internal redirects for the backend test (#5445) +- Fix issue where pnpm support broke for function emulation and deployment. (#5467) diff --git a/scripts/functions-discover-tests/fixtures/pnpm/firebase.json b/scripts/functions-discover-tests/fixtures/pnpm/firebase.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/pnpm/firebase.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/scripts/functions-discover-tests/fixtures/pnpm/functions/index.js b/scripts/functions-discover-tests/fixtures/pnpm/functions/index.js new file mode 100644 index 00000000000..cf0342ca53a --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/pnpm/functions/index.js @@ -0,0 +1,10 @@ +const functions = require("firebase-functions"); +const { onRequest } = require("firebase-functions/v2/https"); + +exports.hellov1 = functions.https.onRequest((request, response) => { + response.send("Hello from Firebase!"); +}); + +exports.hellov2 = onRequest((request, response) => { + response.send("Hello from Firebase!"); +}); diff --git a/scripts/functions-discover-tests/fixtures/pnpm/functions/package.json b/scripts/functions-discover-tests/fixtures/pnpm/functions/package.json new file mode 100644 index 00000000000..b71df5af785 --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/pnpm/functions/package.json @@ -0,0 +1,9 @@ +{ + "name": "pnpm", + "dependencies": { + "firebase-functions": "^4.0.0" + }, + "engines": { + "node": "16" + } +} diff --git a/scripts/functions-discover-tests/fixtures/pnpm/install.sh b/scripts/functions-discover-tests/fixtures/pnpm/install.sh new file mode 100755 index 00000000000..f9e13353e1e --- /dev/null +++ b/scripts/functions-discover-tests/fixtures/pnpm/install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -euxo pipefail # bash strict mode +IFS=$'\n\t' + +cd functions && pnpm install diff --git a/scripts/functions-discover-tests/run.sh b/scripts/functions-discover-tests/run.sh index 0cd21daaec0..a67e2dcc8a8 100755 --- a/scripts/functions-discover-tests/run.sh +++ b/scripts/functions-discover-tests/run.sh @@ -11,6 +11,9 @@ firebase experiments:enable internaltesting # Install yarn npm i -g yarn +# Install pnpm +npm install -g pnpm --force # it's okay to reinstall pnpm + for dir in ./scripts/functions-discover-tests/fixtures/*; do (cd $dir && ./install.sh) done diff --git a/scripts/functions-discover-tests/tests.ts b/scripts/functions-discover-tests/tests.ts index 4c3cf859d82..c44de3b829c 100644 --- a/scripts/functions-discover-tests/tests.ts +++ b/scripts/functions-discover-tests/tests.ts @@ -77,6 +77,16 @@ describe("Function discovery test", function (this) { }, ], }, + { + name: "pnpm", + projectDir: "pnpm", + expects: [ + { + codebase: "default", + endpoints: ["hellov1", "hellov2"], + }, + ], + }, ]; for (const tc of testCases) { diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index cd387503d9b..b4f22bcefcd 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -17,6 +17,7 @@ import * as runtimes from ".."; import * as validate from "./validate"; import * as versioning from "./versioning"; import * as parseTriggers from "./parseTriggers"; +import { fileExistsSync } from "../../../../fsutils"; const MIN_FUNCTIONS_SDK_VERSION = "3.20.0"; @@ -169,33 +170,51 @@ export class Delegate { if (Object.keys(config || {}).length) { env.CLOUD_RUNTIME_CONFIG = JSON.stringify(config); } - // At this point, we've already confirmed that we found supported firebase functions sdk. + // Location of the binary included in the Firebase Functions SDK + // differs depending on the developer's setup and choice of package manager. + // + // We'll try few routes in the following order: + // + // 1. $SOURCE_DIR/node_modules/.bin/firebase-functions + // 2. node_modules closest to the resolved path ${require.resolve("firebase-functions")} + // + // (1) works for most package managers (npm, yarn[no-hoist],pnpm). + // (2) handles cases where developer prefers monorepo setup or bundled function code. + const sourceNodeModulesPath = path.join(this.sourceDir, "node_modules"); const sdkPath = require.resolve("firebase-functions", { paths: [this.sourceDir] }); - // Find location of the closest node_modules/ directory where we found the sdk. - const binPath = sdkPath.substring(0, sdkPath.lastIndexOf("node_modules") + 12); - // And execute the binary included in the sdk. - const childProcess = spawn(path.join(binPath, ".bin", "firebase-functions"), [this.sourceDir], { - env, - cwd: this.sourceDir, - stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "inherit"], - }); - childProcess.stdout?.on("data", (chunk) => { - logger.debug(chunk.toString()); - }); - return Promise.resolve(async () => { - const p = new Promise((resolve, reject) => { - childProcess.once("exit", resolve); - childProcess.once("error", reject); - }); - - await fetch(`http://localhost:${port}/__/quitquitquit`); - setTimeout(() => { - if (!childProcess.killed) { - childProcess.kill("SIGKILL"); - } - }, 10_000); - return p; - }); + const sdkNodeModulesPath = sdkPath.substring(0, sdkPath.lastIndexOf("node_modules") + 12); + for (const nodeModulesPath of [sourceNodeModulesPath, sdkNodeModulesPath]) { + const binPath = path.join(nodeModulesPath, ".bin", "firebase-functions"); + if (fileExistsSync(binPath)) { + logger.debug(`Found firebase-functions binary at '${binPath}'`); + const childProcess = spawn(binPath, [this.sourceDir], { + env, + cwd: this.sourceDir, + stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "inherit"], + }); + childProcess.stdout?.on("data", (chunk) => { + logger.debug(chunk.toString()); + }); + return Promise.resolve(async () => { + const p = new Promise((resolve, reject) => { + childProcess.once("exit", resolve); + childProcess.once("error", reject); + }); + + await fetch(`http://localhost:${port}/__/quitquitquit`); + setTimeout(() => { + if (!childProcess.killed) { + childProcess.kill("SIGKILL"); + } + }, 10_000); + return p; + }); + } + } + throw new FirebaseError( + "Failed to find location of Firebase Functions SDK. " + + "Please file a bug on Github (https://github.com/firebase/firebase-tools/)." + ); } // eslint-disable-next-line require-await From 978a10f9682f440f0dda9c52d0865b5855fcff8f Mon Sep 17 00:00:00 2001 From: Claudio F Date: Tue, 31 Jan 2023 10:37:02 -0500 Subject: [PATCH 0774/1699] Nuxt support (#5321) Refactor the Nuxt build workflow to accommodate all the ssr/target combinations Co-authored-by: James Daniels Co-authored-by: Leonardo Ortiz Co-authored-by: Austin Crim --- CHANGELOG.md | 1 + src/frameworks/index.ts | 5 + src/frameworks/nuxt/index.ts | 69 ++++++------ src/frameworks/nuxt/interfaces.ts | 5 + src/frameworks/nuxt/utils.ts | 16 +++ src/frameworks/nuxt2/index.ts | 147 +++++++++++++++++++++++++ src/test/frameworks/nuxt/utils.spec.ts | 44 ++++++++ 7 files changed, 253 insertions(+), 34 deletions(-) create mode 100644 src/frameworks/nuxt/interfaces.ts create mode 100644 src/frameworks/nuxt/utils.ts create mode 100644 src/frameworks/nuxt2/index.ts create mode 100644 src/test/frameworks/nuxt/utils.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3563c5bc59a..4674c105774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ - Refactors Functions Emulator. (#5422) - Fixes race condition when discovering functions. (#5444) +- Added support for Nuxt 2 and Nuxt 3. (#5321) - Fixes issue where `init firestore` was unecessarilly checking for default resource location. (#5230 and #5452) - Pass `trailingSlash` from Next.js config to `firebase.json` (#5445) - Don't use Next.js internal redirects for the backend test (#5445) diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index eec612381a4..8b5bb7c8e08 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -157,8 +157,13 @@ export function relativeRequire( export function relativeRequire(dir: string, mod: "next"): typeof import("next"); export function relativeRequire(dir: string, mod: "vite"): typeof import("vite"); export function relativeRequire(dir: string, mod: "jsonc-parser"): typeof import("jsonc-parser"); + // TODO the types for @nuxt/kit are causing a lot of troubles, need to do something other than any +// Nuxt 2 +export function relativeRequire(dir: string, mod: "nuxt/dist/nuxt.js"): Promise; +// Nuxt 3 export function relativeRequire(dir: string, mod: "@nuxt/kit"): Promise; + /** * */ diff --git a/src/frameworks/nuxt/index.ts b/src/frameworks/nuxt/index.ts index 3fce4387193..c92fcb03a32 100644 --- a/src/frameworks/nuxt/index.ts +++ b/src/frameworks/nuxt/index.ts @@ -1,31 +1,44 @@ import { copy, pathExists } from "fs-extra"; import { readFile } from "fs/promises"; -import { basename, join } from "path"; +import { join } from "path"; import { gte } from "semver"; -import { BuildResult, findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; +import { findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; import { warnIfCustomBuildScript } from "../utils"; export const name = "Nuxt"; export const support = SupportLevel.Experimental; export const type = FrameworkType.Toolchain; +import { NuxtDependency } from "./interfaces"; +import { nuxtConfigFilesExist } from "./utils"; + const DEFAULT_BUILD_SCRIPT = ["nuxt build"]; -export async function discover(dir: string) { +/** + * + * @param dir current directory + * @return undefined if project is not Nuxt 2, {mayWantBackend: true } otherwise + */ +export async function discover(dir: string): Promise<{ mayWantBackend: true } | undefined> { if (!(await pathExists(join(dir, "package.json")))) return; - const nuxtDependency = findDependency("nuxt", { cwd: dir, depth: 0, omitDev: false }); - const configFilesExist = await Promise.all([ - pathExists(join(dir, "nuxt.config.js")), - pathExists(join(dir, "nuxt.config.ts")), - ]); - const anyConfigFileExists = configFilesExist.some((it) => it); + const nuxtDependency = findDependency("nuxt", { + cwd: dir, + depth: 0, + omitDev: false, + }) as NuxtDependency; + + const version = nuxtDependency?.version; + const anyConfigFileExists = await nuxtConfigFilesExist(dir); + if (!anyConfigFileExists && !nuxtDependency) return; - return { mayWantBackend: true }; + if (version && gte(version, "3.0.0-0")) return { mayWantBackend: true }; + + return; } -export async function build(root: string): Promise { +export async function build(root: string) { const { buildNuxt } = await relativeRequire(root, "@nuxt/kit"); - const nuxtApp = await getNuxtApp(root); + const nuxtApp = await getNuxt3App(root); await warnIfCustomBuildScript(root, name, DEFAULT_BUILD_SCRIPT); @@ -33,7 +46,8 @@ export async function build(root: string): Promise { return { wantsBackend: true }; } -async function getNuxtApp(cwd: string) { +// Nuxt 3 +async function getNuxt3App(cwd: string) { const { loadNuxt } = await relativeRequire(cwd, "@nuxt/kit"); return await loadNuxt({ cwd, @@ -45,32 +59,19 @@ async function getNuxtApp(cwd: string) { }); } -function isNuxt3(cwd: string) { - const { version } = findDependency("nuxt", { cwd, depth: 0, omitDev: false }); - return gte(version, "3.0.0-0"); -} - export async function ɵcodegenPublicDirectory(root: string, dest: string) { - const app = await getNuxtApp(root); - const distPath = isNuxt3(root) ? join(root, ".output", "public") : app.options.generate.dir; + const distPath = join(root, ".output", "public"); await copy(distPath, dest); } export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) { const packageJsonBuffer = await readFile(join(sourceDir, "package.json")); const packageJson = JSON.parse(packageJsonBuffer.toString()); - if (isNuxt3(sourceDir)) { - const outputPackageJsonBuffer = await readFile( - join(sourceDir, ".output", "server", "package.json") - ); - const outputPackageJson = JSON.parse(outputPackageJsonBuffer.toString()); - await copy(join(sourceDir, ".output", "server"), destDir); - return { packageJson: { ...packageJson, ...outputPackageJson }, frameworksEntry: "nuxt3" }; - } else { - const { - options: { buildDir }, - } = await getNuxtApp(sourceDir); - await copy(buildDir, join(destDir, basename(buildDir))); - return { packageJson }; - } + + const outputPackageJsonBuffer = await readFile( + join(sourceDir, ".output", "server", "package.json") + ); + const outputPackageJson = JSON.parse(outputPackageJsonBuffer.toString()); + await copy(join(sourceDir, ".output", "server"), destDir); + return { packageJson: { ...packageJson, ...outputPackageJson }, frameworksEntry: "nuxt3" }; } diff --git a/src/frameworks/nuxt/interfaces.ts b/src/frameworks/nuxt/interfaces.ts new file mode 100644 index 00000000000..6dcd0ea6844 --- /dev/null +++ b/src/frameworks/nuxt/interfaces.ts @@ -0,0 +1,5 @@ +export interface NuxtDependency { + version?: string; + resolved?: string; + overridden?: boolean; +} diff --git a/src/frameworks/nuxt/utils.ts b/src/frameworks/nuxt/utils.ts new file mode 100644 index 00000000000..b9ce20da85c --- /dev/null +++ b/src/frameworks/nuxt/utils.ts @@ -0,0 +1,16 @@ +import { pathExists } from "fs-extra"; +import { join } from "path"; + +/** + * + * @param dir current app directory + * @return true or false if Nuxt config file was found in the directory + */ +export async function nuxtConfigFilesExist(dir: string): Promise { + const configFilesExist = await Promise.all([ + pathExists(join(dir, "nuxt.config.js")), + pathExists(join(dir, "nuxt.config.ts")), + ]); + + return configFilesExist.some((it) => it); +} diff --git a/src/frameworks/nuxt2/index.ts b/src/frameworks/nuxt2/index.ts new file mode 100644 index 00000000000..9392a3e17b4 --- /dev/null +++ b/src/frameworks/nuxt2/index.ts @@ -0,0 +1,147 @@ +import { copy, pathExists } from "fs-extra"; +import { readFile } from "fs/promises"; +import { join } from "path"; +import { lt } from "semver"; +import { findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; + +import { NuxtDependency } from "../nuxt/interfaces"; +import { nuxtConfigFilesExist } from "../nuxt/utils"; + +export const name = "Nuxt"; +export const support = SupportLevel.Experimental; +export const type = FrameworkType.MetaFramework; + +/** + * + * @param dir current directory + * @return undefined if project is not Nuxt 2, {mayWantBackend: true } otherwise + */ +export async function discover(dir: string): Promise<{ mayWantBackend: true } | undefined> { + if (!(await pathExists(join(dir, "package.json")))) return; + const nuxtDependency = findDependency("nuxt", { + cwd: dir, + depth: 0, + omitDev: false, + }); + + const version = nuxtDependency?.version; + const anyConfigFileExists = await nuxtConfigFilesExist(dir); + + if (!anyConfigFileExists && !nuxtDependency) return; + if (version && lt(version, "3.0.0-0")) return { mayWantBackend: true }; + + return; +} + +/** + * Get the Nuxt app + * @param cwd + * @return Nuxt app object + */ +async function getNuxtApp(cwd: string): Promise { + return await relativeRequire(cwd, "nuxt/dist/nuxt.js"); +} + +/** + * + * @param root nuxt project root + * @return whether backend is needed or not + */ +export async function build(root: string): Promise<{ wantsBackend: boolean }> { + const nuxt = await getNuxtApp(root); + + const nuxtApp = await nuxt.loadNuxt({ + for: "build", + rootDir: root, + }); + + const { + options: { ssr, target }, + } = await nuxt.build(nuxtApp); + + if (ssr === true && target === "server") { + return { wantsBackend: true }; + } else { + // Inform the user that static target is not supported with `ssr: false`, + // and continue with building for client side as per current Nuxt 2. + if (ssr === false && target === "static") { + console.log( + "Firebase: Nuxt 2: Static target is not supported with `ssr: false`. Please use `target: 'server'` in your `nuxt.config.js` file." + ); + console.log("Firebase: Nuxt 2: Bundling only for client side.\n"); + } + + await buildAndGenerate(nuxt, root); + return { wantsBackend: false }; + } +} +/** + * Build and generate the Nuxt app + * + * @param nuxt nuxt object + * @param root root directory + * @return void + */ +async function buildAndGenerate(nuxt: any, root: string): Promise { + const nuxtApp = await nuxt.loadNuxt({ + for: "start", + rootDir: root, + }); + + const builder = await nuxt.getBuilder(nuxtApp); + const generator = new nuxt.Generator(nuxtApp, builder); + await generator.generate({ build: false, init: true }); +} + +/** + * Copy the static files to the destination directory whether it's a static build or server build. + * @param root + * @param dest + */ +export async function ɵcodegenPublicDirectory(root: string, dest: string) { + const nuxt = await getNuxtApp(root); + const nuxtConfig = await nuxt.loadNuxtConfig(); + const { ssr, target } = nuxtConfig; + + // If `target` is set to `static`, copy the generated files + // to the destination directory (i.e. `/hosting`). + if (!(ssr === true && target === "server")) { + const source = + nuxtConfig?.generate?.dir !== undefined + ? join(root, nuxtConfig?.generate?.dir) + : join(root, "dist"); + + await copy(source, dest); + } + + // Copy static assets if they exist. + const staticPath = join(root, "static"); + if (await pathExists(staticPath)) { + await copy(staticPath, dest); + } +} + +export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) { + const packageJsonBuffer = await readFile(join(sourceDir, "package.json")); + const packageJson = JSON.parse(packageJsonBuffer.toString()); + + // Get the nuxt config into an object so we can check the `target` and `ssr` properties. + const nuxt = await getNuxtApp(sourceDir); + const nuxtConfig = await nuxt.loadNuxtConfig(); + + // When starting the Nuxt 2 server, we need to copy the `.nuxt` to the destination directory (`functions`) + // with the same folder name (.firebase//functions/.nuxt). + // This is because `loadNuxt` (called from `firebase-frameworks`) will only look + // for the `.nuxt` directory in the destination directory. + await copy(join(sourceDir, ".nuxt"), join(destDir, ".nuxt")); + + // When using `SSR: false`, we need to copy the `nuxt.config.js` to the destination directory (`functions`) + // This is because `loadNuxt` (called from `firebase-frameworks`) will look + // for the `nuxt.config.js` file in the destination directory. + if (!nuxtConfig.ssr) { + const nuxtConfigFile = nuxtConfig._nuxtConfigFile.split("/").pop(); + await copy(nuxtConfig._nuxtConfigFile, join(destDir, nuxtConfigFile)); + } + + return { packageJson: { ...packageJson }, frameworksEntry: "nuxt" }; +} diff --git a/src/test/frameworks/nuxt/utils.spec.ts b/src/test/frameworks/nuxt/utils.spec.ts new file mode 100644 index 00000000000..8339fdd0620 --- /dev/null +++ b/src/test/frameworks/nuxt/utils.spec.ts @@ -0,0 +1,44 @@ +import { expect } from "chai"; +// import * as fs from "fs"; +import * as fsExtra from "fs-extra"; +import * as sinon from "sinon"; +import * as frameworksFunctions from "../../../frameworks"; + +import { discover as discoverNuxt2 } from "../../../frameworks/nuxt2"; +import { discover as discoverNuxt3 } from "../../../frameworks/nuxt"; + +describe("Nuxt 2 utils", () => { + describe("nuxtAppDiscovery", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should find a Nuxt 2 app", async () => { + sandbox.stub(fsExtra, "pathExists").resolves(true); + sandbox.stub(frameworksFunctions, "findDependency").returns({ + version: "2.15.8", + resolved: "https://registry.npmjs.org/nuxt/-/nuxt-2.15.8.tgz", + overridden: false, + }); + + expect(await discoverNuxt2(".")).to.deep.equal({ mayWantBackend: true }); + }); + + it("should find a Nuxt 3 app", async () => { + sandbox.stub(fsExtra, "pathExists").resolves(true); + sandbox.stub(frameworksFunctions, "findDependency").returns({ + version: "3.0.0", + resolved: "https://registry.npmjs.org/nuxt/-/nuxt-3.0.0.tgz", + overridden: false, + }); + + expect(await discoverNuxt3(".")).to.deep.equal({ mayWantBackend: true }); + }); + }); +}); From 020c96ba366cd6936e03c3800d97cc96b1ab707c Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 31 Jan 2023 12:22:02 -0800 Subject: [PATCH 0775/1699] Update issue templates (#5475) * Update issue templates Switch Bug report template to use label 'Type: Bug' and adds 'Type: Feature Request' to feature request template. * format --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- .github/ISSUE_TEMPLATE/feature_request.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f6cccad084a..a17f7d1811b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,8 +1,8 @@ --- -name: âš ï¸ Bug report +name: "âš ï¸ Bug report" about: Create a report to help us improve title: "" -labels: bug +labels: "type: bug" assignees: "" --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 3b3f1dd9c1f..c58daaf219e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,8 +1,8 @@ --- -name: 💡 Feature request +name: "\U0001F4A1 Feature request" about: Suggest an idea for this project title: "" -labels: feature request +labels: "type: feature request" assignees: "" --- From 4408aebaf7c3d08974c62141317eae1706b02177 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 31 Jan 2023 13:42:07 -0800 Subject: [PATCH 0776/1699] Fix bug where emulator didn't consider .env.local when discovering triggers. (#5477) Fixes https://github.com/firebase/firebase-tools/issues/5219. Note that the actual fix is a one-liner. I'm adding basic integration test for dotenv and params support for the emulator. --- CHANGELOG.md | 1 + .../functions/package-lock.json | 20 +- scripts/emulator-tests/functions/package.json | 2 +- .../emulator-tests/functionsEmulator.spec.ts | 1394 ++++++++++------- src/emulator/functionsEmulator.ts | 1 + 5 files changed, 824 insertions(+), 594 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4674c105774..409e9d87012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,3 +5,4 @@ - Pass `trailingSlash` from Next.js config to `firebase.json` (#5445) - Don't use Next.js internal redirects for the backend test (#5445) - Fix issue where pnpm support broke for function emulation and deployment. (#5467) +- Fix bug where .env.local files were not picked up during function emulation. (#5477) diff --git a/scripts/emulator-tests/functions/package-lock.json b/scripts/emulator-tests/functions/package-lock.json index 1fef8ed3335..679b999d8c8 100644 --- a/scripts/emulator-tests/functions/package-lock.json +++ b/scripts/emulator-tests/functions/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "express": "^4.18.1", "firebase-admin": "^11.5.0", - "firebase-functions": "^3.22.0" + "firebase-functions": "^4.0.0" }, "engines": { "node": "16" @@ -1104,25 +1104,24 @@ } }, "node_modules/firebase-functions": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.22.0.tgz", - "integrity": "sha512-d1BxBpT95MhvVqXkpLWDvWbyuX7e2l69cFAiqG3U1XQDaMV88bM9S+Zg7H8i9pitEGFr+76ErjKgrY0n+g3ZDA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.2.0.tgz", + "integrity": "sha512-WvC+yeqez769dcgJ8YqGYOHRsB+tzVN6CYV7AARmulhKUOvIP+EqUXK5LQFR1nB01/2LGpeK39uBXh42CPSTpg==", "dependencies": { "@types/cors": "^2.8.5", "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", - "lodash": "^4.17.14", "node-fetch": "^2.6.7" }, "bin": { "firebase-functions": "lib/bin/firebase-functions.js" }, "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=14.10.0" }, "peerDependencies": { - "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + "firebase-admin": "^10.0.0 || ^11.0.0" } }, "node_modules/forwarded": { @@ -3635,15 +3634,14 @@ } }, "firebase-functions": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.22.0.tgz", - "integrity": "sha512-d1BxBpT95MhvVqXkpLWDvWbyuX7e2l69cFAiqG3U1XQDaMV88bM9S+Zg7H8i9pitEGFr+76ErjKgrY0n+g3ZDA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.2.0.tgz", + "integrity": "sha512-WvC+yeqez769dcgJ8YqGYOHRsB+tzVN6CYV7AARmulhKUOvIP+EqUXK5LQFR1nB01/2LGpeK39uBXh42CPSTpg==", "requires": { "@types/cors": "^2.8.5", "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", - "lodash": "^4.17.14", "node-fetch": "^2.6.7" } }, diff --git a/scripts/emulator-tests/functions/package.json b/scripts/emulator-tests/functions/package.json index 954cce0f233..09efcb47ffb 100644 --- a/scripts/emulator-tests/functions/package.json +++ b/scripts/emulator-tests/functions/package.json @@ -6,7 +6,7 @@ "dependencies": { "express": "^4.18.1", "firebase-admin": "^11.5.0", - "firebase-functions": "^3.22.0" + "firebase-functions": "^4.0.0" }, "engines": { "node": "16" diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index f6591d0e8e3..ef6d7c128d8 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -1,5 +1,6 @@ import * as fs from "fs"; import * as fsp from "fs/promises"; +import * as path from "path"; import { expect } from "chai"; import * as express from "express"; @@ -9,7 +10,7 @@ import * as winston from "winston"; import * as logform from "logform"; import { EmulatedTriggerDefinition } from "../../src/emulator/functionsEmulatorShared"; -import { FunctionsEmulator } from "../../src/emulator/functionsEmulator"; +import { EmulatableBackend, FunctionsEmulator } from "../../src/emulator/functionsEmulator"; import { EmulatorInfo, Emulators } from "../../src/emulator/types"; import { FakeEmulator } from "../../src/test/emulators/fakeEmulator"; import { TIMEOUT_LONG, TIMEOUT_MED, MODULE_ROOT } from "./fixtures"; @@ -30,19 +31,56 @@ if ((process.env.DEBUG || "").toLowerCase().includes("spec")) { ); } -const FUNCTIONS_DIR = `./scripts/emulator-tests/functions`; +const FUNCTIONS_DIR = path.resolve( + // MODULE_ROOT points to firebase-tools/dev since that's where this test file is compiled to. + // Function source directory is located on firebase-tools/ hence the "..". See run.sh + path.join(MODULE_ROOT, "..", "scripts/emulator-tests/functions") + // path.join(MODULE_ROOT, "scripts/emulator-tests/functions") +); -const TEST_BACKEND = { +const TEST_BACKEND: EmulatableBackend = { functionsDir: FUNCTIONS_DIR, env: {}, secretEnv: [], codebase: "default", - bin: process.execPath, runtime: "nodejs14", + bin: process.execPath, // NOTE: Use the following node bin path if you want to run test cases directly from your IDE. // bin: path.join(MODULE_ROOT, "node_modules/.bin/ts-node"), }; +async function setupEnvFiles(envs: Record) { + const envFiles: string[] = []; + for (const [filename, data] of Object.entries(envs)) { + const envPath = path.join(FUNCTIONS_DIR, filename); + await fsp.writeFile(path.join(FUNCTIONS_DIR, filename), data); + envFiles.push(envPath); + } + return async () => { + await Promise.all(envFiles.map((f) => fsp.rm(f))); + }; +} + +async function writeSource( + triggerSource: () => void, + params?: Record void> +): Promise<() => Promise> { + let sourceCode = `module.exports = (${triggerSource.toString()})();\n`; + const sourcePath = path.join(FUNCTIONS_DIR, "index.js"); + if (params) { + for (const [paramName, valFn] of Object.entries(params)) { + sourceCode = `const ${paramName} = (${valFn.toString()})();\n${sourceCode}`; + // Since parameter cannot be references before it's defined, employ this hack to + // replace all "string-escaped" param references to real instances. + sourceCode = sourceCode.replaceAll(`"__$${paramName}__"`, paramName); + } + } + await fsp.writeFile(sourcePath, sourceCode); + return async () => { + await fsp.rm(sourcePath); + }; +} + async function useFunction( emu: FunctionsEmulator, triggerName: string, @@ -50,9 +88,7 @@ async function useFunction( regions: string[] = ["us-central1"], triggerOverrides?: Partial ): Promise { - const sourceCode = `module.exports = (${triggerSource.toString()})();\n`; - await fsp.writeFile(`${FUNCTIONS_DIR}/index.js`, sourceCode); - + await writeSource(triggerSource); const triggers: EmulatedTriggerDefinition[] = []; for (const region of regions) { triggers.push({ @@ -69,7 +105,9 @@ async function useFunction( emu.setTriggersForTesting(triggers, TEST_BACKEND); } -describe("FunctionsEmulator-Hub", function () { +const TEST_PROJECT_ID = "fake-project-id"; + +describe("FunctionsEmulator", function () { // eslint-disable-next-line @typescript-eslint/no-invalid-this this.timeout(TIMEOUT_LONG); @@ -77,14 +115,14 @@ describe("FunctionsEmulator-Hub", function () { beforeEach(() => { emu = new FunctionsEmulator({ - projectId: "fake-project-id", + projectId: TEST_PROJECT_ID, projectDir: MODULE_ROOT, emulatableBackends: [TEST_BACKEND], quiet: true, adminSdkConfig: { - projectId: "fake-project-id", - databaseURL: "https://fake-project-id-default-rtdb.firebaseio.com", - storageBucket: "fake-project-id.appspot.com", + projectId: TEST_PROJECT_ID, + databaseURL: `https://${TEST_PROJECT_ID}-default-rtdb.firebaseio.com`, + storageBucket: `${TEST_PROJECT_ID}.appspot.com`, }, }); }); @@ -93,718 +131,910 @@ describe("FunctionsEmulator-Hub", function () { await emu.stop(); }); - it("should route requests to /:project_id/us-central1/:trigger_id to default region HTTPS Function", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ path: req.path }); - } - ), - }; - }); - - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId") - .expect(200) - .then((res) => { - expect(res.body.path).to.deep.equal("/"); - }); - }); - - it("should route requests to /:project_id/:other-region/:trigger_id to the region's HTTPS Function", async () => { - await useFunction( - emu, - "functionId", - () => { - require("firebase-admin").initializeApp(); + describe("Hub", () => { + it("should route requests to /:project_id/us-central1/:trigger_id to default region HTTPS Function", async () => { + await useFunction(emu, "functionId", () => { return { - functionId: require("firebase-functions") - .region("us-central1", "europe-west2") - .https.onRequest((req: express.Request, res: express.Response) => { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { res.json({ path: req.path }); - }), + } + ), }; - }, - ["us-central1", "europe-west2"] - ); - - await supertest(emu.createHubServer()) - .get("/fake-project-id/europe-west2/functionId") - .expect(200) - .then((res) => { - expect(res.body.path).to.deep.equal("/"); }); - }); - it("should 404 when a function doesn't exist in the region", async () => { - await useFunction( - emu, - "functionId", - () => { - require("firebase-admin").initializeApp(); - return { - functionId: require("firebase-functions") - .region("us-central1", "europe-west2") - .https.onRequest((req: express.Request, res: express.Response) => { - res.json({ path: req.path }); - }), - }; - }, - ["us-central1", "europe-west2"] - ); + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId`) + .expect(200) + .then((res) => { + expect(res.body.path).to.deep.equal("/"); + }); + }); - await supertest(emu.createHubServer()).get("/fake-project-id/us-east1/functionId").expect(404); - }); + it("should route requests to /:project_id/:other-region/:trigger_id to the region's HTTPS Function", async () => { + await useFunction( + emu, + "functionId", + () => { + require("firebase-admin").initializeApp(); + return { + functionId: require("firebase-functions") + .region("us-central1", "europe-west2") + .https.onRequest((req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + }), + }; + }, + ["us-central1", "europe-west2"] + ); - it("should route requests to /:project_id/:region/:trigger_id/ to HTTPS Function", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ path: req.path }); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/europe-west2/functionId`) + .expect(200) + .then((res) => { + expect(res.body.path).to.deep.equal("/"); + }); }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId/") - .expect(200) - .then((res) => { - expect(res.body.path).to.deep.equal("/"); - }); - }); + it("should 404 when a function doesn't exist in the region", async () => { + await useFunction( + emu, + "functionId", + () => { + require("firebase-admin").initializeApp(); + return { + functionId: require("firebase-functions") + .region("us-central1", "europe-west2") + .https.onRequest((req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + }), + }; + }, + ["us-central1", "europe-west2"] + ); - it("should 404 when a function does not exist", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ path: req.path }); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-east1/functionId`) + .expect(404); }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionDNE") - .expect(404); - }); - - it("should properly route to a namespaced/grouped HTTPs function", async () => { - await useFunction(emu, "nested-functionId", () => { - return { - nested: { + it("should route requests to /:project_id/:region/:trigger_id/ to HTTPS Function", async () => { + await useFunction(emu, "functionId", () => { + return { functionId: require("firebase-functions").https.onRequest( (req: express.Request, res: express.Response) => { res.json({ path: req.path }); } ), - }, - }; - }); - - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/nested-functionId") - .expect(200) - .then((res) => { - expect(res.body.path).to.deep.equal("/"); + }; }); - }); - it("should route requests to /:project_id/:region/:trigger_id/a/b to HTTPS Function", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ path: req.path }); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId/`) + .expect(200) + .then((res) => { + expect(res.body.path).to.deep.equal("/"); + }); }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId/a/b") - .expect(200) - .then((res) => { - expect(res.body.path).to.deep.equal("/a/b"); + it("should 404 when a function does not exist", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + } + ), + }; }); - }); - it("should reject requests to a non-emulator path", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ path: req.path }); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionDNE`) + .expect(404); }); - await supertest(emu.createHubServer()).get("/foo/bar/baz").expect(404); - }); + it("should properly route to a namespaced/grouped HTTPs function", async () => { + await useFunction(emu, "nested-functionId", () => { + return { + nested: { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + } + ), + }, + }; + }); - it("should rewrite req.path to hide /:project_id/:region/:trigger_id", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ path: req.path }); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/nested-functionId`) + .expect(200) + .then((res) => { + expect(res.body.path).to.deep.equal("/"); + }); }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId/sub/route/a") - .expect(200) - .then((res) => { - expect(res.body.path).to.eq("/sub/route/a"); + it("should route requests to /:project_id/:region/:trigger_id/a/b to HTTPS Function", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + } + ), + }; }); - }); - it("should return the correct url, baseUrl, originalUrl for the root route", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ - url: req.url, - baseUrl: req.baseUrl, - originalUrl: req.originalUrl, - }); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId/a/b`) + .expect(200) + .then((res) => { + expect(res.body.path).to.deep.equal("/a/b"); + }); }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId") - .expect(200) - .then((res) => { - expect(res.body.url).to.eq("/"); - expect(res.body.baseUrl).to.eq(""); - expect(res.body.originalUrl).to.eq("/"); + it("should reject requests to a non-emulator path", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + } + ), + }; }); - }); - it("should return the correct url, baseUrl, originalUrl with query params", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ - url: req.url, - baseUrl: req.baseUrl, - originalUrl: req.originalUrl, - query: req.query, - }); - } - ), - }; + await supertest(emu.createHubServer()).get("/foo/bar/baz").expect(404); }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId?a=1&b=2") - .expect(200) - .then((res) => { - expect(res.body.url).to.eq("/?a=1&b=2"); - expect(res.body.baseUrl).to.eq(""); - expect(res.body.originalUrl).to.eq("/?a=1&b=2"); - expect(res.body.query).to.deep.eq({ a: "1", b: "2" }); + it("should rewrite req.path to hide /:project_id/:region/:trigger_id", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + } + ), + }; }); - }); - it("should return the correct url, baseUrl, originalUrl for a subroute", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ - url: req.url, - baseUrl: req.baseUrl, - originalUrl: req.originalUrl, - }); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId/sub/route/a`) + .expect(200) + .then((res) => { + expect(res.body.path).to.eq("/sub/route/a"); + }); }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId/sub/route/a") - .expect(200) - .then((res) => { - expect(res.body.url).to.eq("/sub/route/a"); - expect(res.body.baseUrl).to.eq(""); - expect(res.body.originalUrl).to.eq("/sub/route/a"); - }); - }); - - it("should return the correct url, baseUrl, originalUrl for any region", async () => { - await useFunction( - emu, - "functionId", - () => { + it("should return the correct url, baseUrl, originalUrl for the root route", async () => { + await useFunction(emu, "functionId", () => { return { - functionId: require("firebase-functions") - .region("europe-west3") - .https.onRequest((req: express.Request, res: express.Response) => { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { res.json({ url: req.url, baseUrl: req.baseUrl, originalUrl: req.originalUrl, - query: req.query, }); - }), + } + ), }; - }, - ["europe-west3"] - ); - - await supertest(emu.createHubServer()) - .get("/fake-project-id/europe-west3/functionId?a=1&b=2") - .expect(200) - .then((res) => { - expect(res.body.url).to.eq("/?a=1&b=2"); - expect(res.body.baseUrl).to.eq(""); - expect(res.body.originalUrl).to.eq("/?a=1&b=2"); - expect(res.body.query).to.deep.eq({ a: "1", b: "2" }); }); - }); - it("should route request body", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json(req.body); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId`) + .expect(200) + .then((res) => { + expect(res.body.url).to.eq("/"); + expect(res.body.baseUrl).to.eq(""); + expect(res.body.originalUrl).to.eq("/"); + }); }); - await supertest(emu.createHubServer()) - .post("/fake-project-id/us-central1/functionId/sub/route/a") - .send({ hello: "world" }) - .expect(200) - .then((res) => { - expect(res.body).to.deep.equal({ hello: "world" }); + it("should return the correct url, baseUrl, originalUrl with query params", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ + url: req.url, + baseUrl: req.baseUrl, + originalUrl: req.originalUrl, + query: req.query, + }); + } + ), + }; }); - }); - it("should route query parameters", async () => { - await useFunction(emu, "functionId", () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json(req.query); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId?a=1&b=2`) + .expect(200) + .then((res) => { + expect(res.body.url).to.eq("/?a=1&b=2"); + expect(res.body.baseUrl).to.eq(""); + expect(res.body.originalUrl).to.eq("/?a=1&b=2"); + expect(res.body.query).to.deep.eq({ a: "1", b: "2" }); + }); }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId/sub/route/a?hello=world") - .expect(200) - .then((res) => { - expect(res.body).to.deep.equal({ hello: "world" }); + it("should return the correct url, baseUrl, originalUrl for a subroute", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ + url: req.url, + baseUrl: req.baseUrl, + originalUrl: req.originalUrl, + }); + } + ), + }; }); - }); - - it("should override callable auth", async () => { - await useFunction(emu, "callableFunctionId", () => { - return { - callableFunctionId: require("firebase-functions").https.onCall((data: any, ctx: any) => { - return { - auth: ctx.auth, - }; - }), - }; - }); - // For token info: - // https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsInVzZXJfaWQiOiJTbW56OE8xcmxkZmptZHg4QVJVdE12WG1tdzYyIiwic3ViIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsImlhdCI6MTU4NTA1MzI2NCwiZXhwIjoxNTg1MDU2ODY0LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw - await supertest(emu.createHubServer()) - .post("/fake-project-id/us-central1/callableFunctionId") - .set({ - "Content-Type": "application/json", - Authorization: - "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsInVzZXJfaWQiOiJTbW56OE8xcmxkZmptZHg4QVJVdE12WG1tdzYyIiwic3ViIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsImlhdCI6MTU4NTA1MzI2NCwiZXhwIjoxNTg1MDU2ODY0LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw", - }) - .send({ data: {} }) - .expect(200) - .then((res) => { - expect(res.body).to.deep.equal({ - result: { - auth: { - uid: "Smnz8O1rldfjmdx8ARUtMvXmmw62", - token: { - provider_id: "anonymous", - iss: "https://securetoken.google.com/fir-dumpster", - aud: "fir-dumpster", - auth_time: 1585053264, - user_id: "Smnz8O1rldfjmdx8ARUtMvXmmw62", - sub: "Smnz8O1rldfjmdx8ARUtMvXmmw62", - uid: "Smnz8O1rldfjmdx8ARUtMvXmmw62", - iat: 1585053264, - exp: 1585056864, - firebase: { - identities: {}, - sign_in_provider: "anonymous", - }, - }, - }, - }, + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId/sub/route/a`) + .expect(200) + .then((res) => { + expect(res.body.url).to.eq("/sub/route/a"); + expect(res.body.baseUrl).to.eq(""); + expect(res.body.originalUrl).to.eq("/sub/route/a"); }); - }); - }); + }); - it("should override callable auth with unicode", async () => { - await useFunction(emu, "callableFunctionId", () => { - return { - callableFunctionId: require("firebase-functions").https.onCall((data: any, ctx: any) => { + it("should return the correct url, baseUrl, originalUrl for any region", async () => { + await useFunction( + emu, + "functionId", + () => { return { - auth: ctx.auth, + functionId: require("firebase-functions") + .region("europe-west3") + .https.onRequest((req: express.Request, res: express.Response) => { + res.json({ + url: req.url, + baseUrl: req.baseUrl, + originalUrl: req.originalUrl, + query: req.query, + }); + }), }; - }), - }; - }); + }, + ["europe-west3"] + ); - // For token info: - // https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsIm5hbWUiOiLlsbHnlLDlpKrpg44iLCJ1c2VyX2lkIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsInN1YiI6IlNtbno4TzFybGRmam1keDhBUlV0TXZYbW13NjIiLCJpYXQiOjE1ODUwNTMyNjQsImV4cCI6MTU4NTA1Njg2NCwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6e30sInNpZ25faW5fcHJvdmlkZXIiOiJhbm9ueW1vdXMifX0.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw - await supertest(emu.createHubServer()) - .post("/fake-project-id/us-central1/callableFunctionId") - .set({ - "Content-Type": "application/json", - Authorization: - "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsIm5hbWUiOiLlsbHnlLDlpKrpg44iLCJ1c2VyX2lkIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsInN1YiI6IlNtbno4TzFybGRmam1keDhBUlV0TXZYbW13NjIiLCJpYXQiOjE1ODUwNTMyNjQsImV4cCI6MTU4NTA1Njg2NCwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6e30sInNpZ25faW5fcHJvdmlkZXIiOiJhbm9ueW1vdXMifX0.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw", - }) - .send({ data: {} }) - .expect(200) - .then((res) => { - expect(res.body).to.deep.equal({ - result: { - auth: { - uid: "Smnz8O1rldfjmdx8ARUtMvXmmw62", - token: { - provider_id: "anonymous", - iss: "https://securetoken.google.com/fir-dumpster", - aud: "fir-dumpster", - auth_time: 1585053264, - name: "山田太郎", - user_id: "Smnz8O1rldfjmdx8ARUtMvXmmw62", - sub: "Smnz8O1rldfjmdx8ARUtMvXmmw62", - uid: "Smnz8O1rldfjmdx8ARUtMvXmmw62", - iat: 1585053264, - exp: 1585056864, - firebase: { - identities: {}, - sign_in_provider: "anonymous", - }, - }, - }, - }, + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/europe-west3/functionId?a=1&b=2`) + .expect(200) + .then((res) => { + expect(res.body.url).to.eq("/?a=1&b=2"); + expect(res.body.baseUrl).to.eq(""); + expect(res.body.originalUrl).to.eq("/?a=1&b=2"); + expect(res.body.query).to.deep.eq({ a: "1", b: "2" }); }); - }); - }); - - it("should override callable auth with a poorly padded ID Token", async () => { - await useFunction(emu, "callableFunctionId", () => { - return { - callableFunctionId: require("firebase-functions").https.onCall((data: any, ctx: any) => { - return { - auth: ctx.auth, - }; - }), - }; }); - // For token info: - // https://jwt.io/#debugger-io?token=eyJhbGciOiJub25lIiwia2lkIjoiZmFrZWtpZCJ9.eyJ1aWQiOiJhbGljZSIsImVtYWlsIjoiYWxpY2VAZXhhbXBsZS5jb20iLCJpYXQiOjAsInN1YiI6ImFsaWNlIn0%3D. - await supertest(emu.createHubServer()) - .post("/fake-project-id/us-central1/callableFunctionId") - .set({ - "Content-Type": "application/json", - Authorization: - "Bearer eyJhbGciOiJub25lIiwia2lkIjoiZmFrZWtpZCJ9.eyJ1aWQiOiJhbGljZSIsImVtYWlsIjoiYWxpY2VAZXhhbXBsZS5jb20iLCJpYXQiOjAsInN1YiI6ImFsaWNlIn0=.", - }) - .send({ data: {} }) - .expect(200) - .then((res) => { - expect(res.body).to.deep.equal({ - result: { - auth: { - uid: "alice", - token: { - uid: "alice", - email: "alice@example.com", - iat: 0, - sub: "alice", - }, - }, - }, - }); + it("should route request body", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json(req.body); + } + ), + }; }); - }); - it("should preserve the Authorization header for callable auth", async () => { - await useFunction(emu, "callableFunctionId", () => { - return { - callableFunctionId: require("firebase-functions").https.onCall((data: any, ctx: any) => { - return { - header: ctx.rawRequest.headers["authorization"], - }; - }), - }; + await supertest(emu.createHubServer()) + .post(`/${TEST_PROJECT_ID}/us-central1/functionId/sub/route/a`) + .send({ hello: "world" }) + .expect(200) + .then((res) => { + expect(res.body).to.deep.equal({ hello: "world" }); + }); }); - const authHeader = - "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsInVzZXJfaWQiOiJTbW56OE8xcmxkZmptZHg4QVJVdE12WG1tdzYyIiwic3ViIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsImlhdCI6MTU4NTA1MzI2NCwiZXhwIjoxNTg1MDU2ODY0LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw"; - // For token info: - // https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsInVzZXJfaWQiOiJTbW56OE8xcmxkZmptZHg4QVJVdE12WG1tdzYyIiwic3ViIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsImlhdCI6MTU4NTA1MzI2NCwiZXhwIjoxNTg1MDU2ODY0LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw - await supertest(emu.createHubServer()) - .post("/fake-project-id/us-central1/callableFunctionId") - .set({ - "Content-Type": "application/json", - Authorization: authHeader, - }) - .send({ data: {} }) - .expect(200) - .then((res) => { - expect(res.body).to.deep.equal({ - result: { - header: authHeader, - }, - }); + it("should route query parameters", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json(req.query); + } + ), + }; }); - }); - it("should respond to requests to /backends to with info about the running backends", async () => { - await useFunction(emu, "functionId", () => { - require("firebase-admin").initializeApp(); - return { - functionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ path: req.path }); - } - ), - }; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId/sub/route/a?hello=world`) + .expect(200) + .then((res) => { + expect(res.body).to.deep.equal({ hello: "world" }); + }); }); - await supertest(emu.createHubServer()) - .get("/backends") - .expect(200) - .then((res) => { - // TODO(b/216642962): Add tests for this endpoint that validate behavior when there are Extensions running - expect(res.body.backends.length).to.equal(1); - expect(res.body.backends[0].functionTriggers).to.deep.equal([ - { - entryPoint: "functionId", - httpsTrigger: {}, - id: "us-central1-functionId", - name: "functionId", - platform: "gcfv1", - codebase: "default", - region: "us-central1", - }, - ]); + it("should override callable auth", async () => { + await useFunction(emu, "callableFunctionId", () => { + return { + callableFunctionId: require("firebase-functions").https.onCall((data: any, ctx: any) => { + return { + auth: ctx.auth, + }; + }), + }; }); - }); - describe("environment variables", () => { - const startFakeEmulator = async (emulator: Emulators): Promise => { - const fake = await FakeEmulator.create(emulator); - await registry.EmulatorRegistry.start(fake); - return fake.getInfo(); - }; - - afterEach(() => { - return registry.EmulatorRegistry.stopAll(); + // For token info: + // https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsInVzZXJfaWQiOiJTbW56OE8xcmxkZmptZHg4QVJVdE12WG1tdzYyIiwic3ViIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsImlhdCI6MTU4NTA1MzI2NCwiZXhwIjoxNTg1MDU2ODY0LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw + await supertest(emu.createHubServer()) + .post(`/${TEST_PROJECT_ID}/us-central1/callableFunctionId`) + .set({ + "Content-Type": "application/json", + Authorization: + "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsInVzZXJfaWQiOiJTbW56OE8xcmxkZmptZHg4QVJVdE12WG1tdzYyIiwic3ViIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsImlhdCI6MTU4NTA1MzI2NCwiZXhwIjoxNTg1MDU2ODY0LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw", + }) + .send({ data: {} }) + .expect(200) + .then((res) => { + expect(res.body).to.deep.equal({ + result: { + auth: { + uid: "Smnz8O1rldfjmdx8ARUtMvXmmw62", + token: { + provider_id: "anonymous", + iss: "https://securetoken.google.com/fir-dumpster", + aud: "fir-dumpster", + auth_time: 1585053264, + user_id: "Smnz8O1rldfjmdx8ARUtMvXmmw62", + sub: "Smnz8O1rldfjmdx8ARUtMvXmmw62", + uid: "Smnz8O1rldfjmdx8ARUtMvXmmw62", + iat: 1585053264, + exp: 1585056864, + firebase: { + identities: {}, + sign_in_provider: "anonymous", + }, + }, + }, + }, + }); + }); }); - it("should set env vars when the emulator is running", async () => { - const database = await startFakeEmulator(Emulators.DATABASE); - const firestore = await startFakeEmulator(Emulators.FIRESTORE); - const auth = await startFakeEmulator(Emulators.AUTH); - - await useFunction(emu, "functionId", () => { + it("should override callable auth with unicode", async () => { + await useFunction(emu, "callableFunctionId", () => { return { - functionId: require("firebase-functions").https.onRequest( - (_req: express.Request, res: express.Response) => { - res.json({ - databaseHost: process.env.FIREBASE_DATABASE_EMULATOR_HOST, - firestoreHost: process.env.FIRESTORE_EMULATOR_HOST, - authHost: process.env.FIREBASE_AUTH_EMULATOR_HOST, - }); - } - ), + callableFunctionId: require("firebase-functions").https.onCall((data: any, ctx: any) => { + return { + auth: ctx.auth, + }; + }), }; }); + // For token info: + // https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsIm5hbWUiOiLlsbHnlLDlpKrpg44iLCJ1c2VyX2lkIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsInN1YiI6IlNtbno4TzFybGRmam1keDhBUlV0TXZYbW13NjIiLCJpYXQiOjE1ODUwNTMyNjQsImV4cCI6MTU4NTA1Njg2NCwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6e30sInNpZ25faW5fcHJvdmlkZXIiOiJhbm9ueW1vdXMifX0.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId") + .post(`/${TEST_PROJECT_ID}/us-central1/callableFunctionId`) + .set({ + "Content-Type": "application/json", + Authorization: + "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsIm5hbWUiOiLlsbHnlLDlpKrpg44iLCJ1c2VyX2lkIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsInN1YiI6IlNtbno4TzFybGRmam1keDhBUlV0TXZYbW13NjIiLCJpYXQiOjE1ODUwNTMyNjQsImV4cCI6MTU4NTA1Njg2NCwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6e30sInNpZ25faW5fcHJvdmlkZXIiOiJhbm9ueW1vdXMifX0.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw", + }) + .send({ data: {} }) .expect(200) .then((res) => { - expect(res.body.databaseHost).to.eql(`${database.host}:${database.port}`); - expect(res.body.firestoreHost).to.eql(`${firestore.host}:${firestore.port}`); - expect(res.body.authHost).to.eql(`${auth.host}:${auth.port}`); + expect(res.body).to.deep.equal({ + result: { + auth: { + uid: "Smnz8O1rldfjmdx8ARUtMvXmmw62", + token: { + provider_id: "anonymous", + iss: "https://securetoken.google.com/fir-dumpster", + aud: "fir-dumpster", + auth_time: 1585053264, + name: "山田太郎", + user_id: "Smnz8O1rldfjmdx8ARUtMvXmmw62", + sub: "Smnz8O1rldfjmdx8ARUtMvXmmw62", + uid: "Smnz8O1rldfjmdx8ARUtMvXmmw62", + iat: 1585053264, + exp: 1585056864, + firebase: { + identities: {}, + sign_in_provider: "anonymous", + }, + }, + }, + }, + }); }); - }).timeout(TIMEOUT_MED); - - it("should return an emulated databaseURL when RTDB emulator is running", async () => { - const database = await startFakeEmulator(Emulators.DATABASE); + }); - await useFunction(emu, "functionId", () => { + it("should override callable auth with a poorly padded ID Token", async () => { + await useFunction(emu, "callableFunctionId", () => { return { - functionId: require("firebase-functions").https.onRequest( - (_req: express.Request, res: express.Response) => { - res.json(JSON.parse(process.env.FIREBASE_CONFIG!)); - } - ), + callableFunctionId: require("firebase-functions").https.onCall((data: any, ctx: any) => { + return { + auth: ctx.auth, + }; + }), }; }); + // For token info: + // https://jwt.io/#debugger-io?token=eyJhbGciOiJub25lIiwia2lkIjoiZmFrZWtpZCJ9.eyJ1aWQiOiJhbGljZSIsImVtYWlsIjoiYWxpY2VAZXhhbXBsZS5jb20iLCJpYXQiOjAsInN1YiI6ImFsaWNlIn0%3D. await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId") + .post(`/${TEST_PROJECT_ID}/us-central1/callableFunctionId`) + .set({ + "Content-Type": "application/json", + Authorization: + "Bearer eyJhbGciOiJub25lIiwia2lkIjoiZmFrZWtpZCJ9.eyJ1aWQiOiJhbGljZSIsImVtYWlsIjoiYWxpY2VAZXhhbXBsZS5jb20iLCJpYXQiOjAsInN1YiI6ImFsaWNlIn0=.", + }) + .send({ data: {} }) .expect(200) .then((res) => { - expect(res.body.databaseURL).to.eql( - `http://${database.host}:${database.port}/?ns=fake-project-id-default-rtdb` - ); + expect(res.body).to.deep.equal({ + result: { + auth: { + uid: "alice", + token: { + uid: "alice", + email: "alice@example.com", + iat: 0, + sub: "alice", + }, + }, + }, + }); }); - }).timeout(TIMEOUT_MED); + }); - it("should return a real databaseURL when RTDB emulator is not running", async () => { - await useFunction(emu, "functionId", () => { + it("should preserve the Authorization header for callable auth", async () => { + await useFunction(emu, "callableFunctionId", () => { return { - functionId: require("firebase-functions").https.onRequest( - (_req: express.Request, res: express.Response) => { - res.json(JSON.parse(process.env.FIREBASE_CONFIG!)); - } - ), + callableFunctionId: require("firebase-functions").https.onCall((data: any, ctx: any) => { + return { + header: ctx.rawRequest.headers["authorization"], + }; + }), }; }); + const authHeader = + "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsInVzZXJfaWQiOiJTbW56OE8xcmxkZmptZHg4QVJVdE12WG1tdzYyIiwic3ViIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsImlhdCI6MTU4NTA1MzI2NCwiZXhwIjoxNTg1MDU2ODY0LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw"; + // For token info: + // https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFmODhiODE0MjljYzQ1MWEzMzVjMmY1Y2RiM2RmYjM0ZWIzYmJjN2YiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9maXItZHVtcHN0ZXIiLCJhdWQiOiJmaXItZHVtcHN0ZXIiLCJhdXRoX3RpbWUiOjE1ODUwNTMyNjQsInVzZXJfaWQiOiJTbW56OE8xcmxkZmptZHg4QVJVdE12WG1tdzYyIiwic3ViIjoiU21uejhPMXJsZGZqbWR4OEFSVXRNdlhtbXc2MiIsImlhdCI6MTU4NTA1MzI2NCwiZXhwIjoxNTg1MDU2ODY0LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.ujOthXwov9NJAOmJfumkDzMQgj8P1YRWkhFeq_HqHpPmth1BbtrQ_duwFoFmAPGjnGTuozUi0YUl8eKh4p2CqXi-Wf_OLSumxNnJWhj_tm7OvYWjvUy0ZvjilPBrhQ17_lRnhyOVSLSXfneqehYvE85YkBkFy3GtOpN49fRdmBT7B71Yx8E8SM7fohlia-ah7_uSNpuJXzQ9-0rv6HH9uBYCmjUxb9MiuKwkIjDoYtjTuaqG8-4w8bPrKHmg6V7HeDSNItUcfDbALZiTsM5uob_uuVTwjCCQnwryB5Y3bmdksTqCvp8U7ZTU04HS9CJawTa-zuDXIwlOvsC-J8oQQw await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId") + .post(`/${TEST_PROJECT_ID}/us-central1/callableFunctionId`) + .set({ + "Content-Type": "application/json", + Authorization: authHeader, + }) + .send({ data: {} }) .expect(200) .then((res) => { - expect(res.body.databaseURL).to.eql( - "https://fake-project-id-default-rtdb.firebaseio.com" - ); + expect(res.body).to.deep.equal({ + result: { + header: authHeader, + }, + }); }); - }).timeout(TIMEOUT_MED); + }); - it("should report GMT time zone", async () => { + it("should respond to requests to /backends to with info about the running backends", async () => { await useFunction(emu, "functionId", () => { + require("firebase-admin").initializeApp(); return { functionId: require("firebase-functions").https.onRequest( - (_req: express.Request, res: express.Response) => { - const now = new Date(); - res.json({ offset: now.getTimezoneOffset() }); + (req: express.Request, res: express.Response) => { + res.json({ path: req.path }); } ), }; }); await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/functionId") + .get("/backends") .expect(200) .then((res) => { - expect(res.body.offset).to.eql(0); + // TODO(b/216642962): Add tests for this endpoint that validate behavior when there are Extensions running + expect(res.body.backends.length).to.equal(1); + expect(res.body.backends[0].functionTriggers).to.deep.equal([ + { + entryPoint: "functionId", + httpsTrigger: {}, + id: "us-central1-functionId", + name: "functionId", + platform: "gcfv1", + codebase: "default", + region: "us-central1", + }, + ]); + }); + }); + + describe("system environment variables", () => { + const startFakeEmulator = async (emulator: Emulators): Promise => { + const fake = await FakeEmulator.create(emulator); + await registry.EmulatorRegistry.start(fake); + return fake.getInfo(); + }; + + afterEach(() => { + return registry.EmulatorRegistry.stopAll(); + }); + + it("should set env vars when the emulator is running", async () => { + const database = await startFakeEmulator(Emulators.DATABASE); + const firestore = await startFakeEmulator(Emulators.FIRESTORE); + const auth = await startFakeEmulator(Emulators.AUTH); + + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (_req: express.Request, res: express.Response) => { + res.json({ + databaseHost: process.env.FIREBASE_DATABASE_EMULATOR_HOST, + firestoreHost: process.env.FIRESTORE_EMULATOR_HOST, + authHost: process.env.FIREBASE_AUTH_EMULATOR_HOST, + }); + } + ), + }; + }); + + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId`) + .expect(200) + .then((res) => { + expect(res.body.databaseHost).to.eql(`${database.host}:${database.port}`); + expect(res.body.firestoreHost).to.eql(`${firestore.host}:${firestore.port}`); + expect(res.body.authHost).to.eql(`${auth.host}:${auth.port}`); + }); + }).timeout(TIMEOUT_MED); + + it("should return an emulated databaseURL when RTDB emulator is running", async () => { + const database = await startFakeEmulator(Emulators.DATABASE); + + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (_req: express.Request, res: express.Response) => { + res.json(JSON.parse(process.env.FIREBASE_CONFIG!)); + } + ), + }; + }); + + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId`) + .expect(200) + .then((res) => { + expect(res.body.databaseURL).to.eql( + `http://${database.host}:${database.port}/?ns=${TEST_PROJECT_ID}-default-rtdb` + ); + }); + }).timeout(TIMEOUT_MED); + + it("should return a real databaseURL when RTDB emulator is not running", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (_req: express.Request, res: express.Response) => { + res.json(JSON.parse(process.env.FIREBASE_CONFIG!)); + } + ), + }; }); - }).timeout(TIMEOUT_MED); - }); - describe("secrets", () => { - let readFileSyncStub: sinon.SinonStub; - let accessSecretVersionStub: sinon.SinonStub; + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId`) + .expect(200) + .then((res) => { + expect(res.body.databaseURL).to.eql( + `https://${TEST_PROJECT_ID}-default-rtdb.firebaseio.com` + ); + }); + }).timeout(TIMEOUT_MED); + + it("should report GMT time zone", async () => { + await useFunction(emu, "functionId", () => { + return { + functionId: require("firebase-functions").https.onRequest( + (_req: express.Request, res: express.Response) => { + const now = new Date(); + res.json({ offset: now.getTimezoneOffset() }); + } + ), + }; + }); - beforeEach(() => { - readFileSyncStub = sinon.stub(fs, "readFileSync").throws("Unexpected call"); - accessSecretVersionStub = sinon - .stub(secretManager, "accessSecretVersion") - .rejects("Unexpected call"); + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/functionId`) + .expect(200) + .then((res) => { + expect(res.body.offset).to.eql(0); + }); + }).timeout(TIMEOUT_MED); }); - afterEach(() => { - readFileSyncStub.restore(); - accessSecretVersionStub.restore(); + describe("user-defined environment variables", () => { + let cleanup: (() => Promise) | undefined; + + afterEach(async () => { + await cleanup?.(); + cleanup = undefined; + }); + + it("should load environment variables in .env file", async () => { + cleanup = await setupEnvFiles({ + ".env": "FOO=foo\nBAR=bar", + }); + + await useFunction( + emu, + "dotenv", + () => { + return { + dotenv: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ + FOO: process.env.FOO, + BAR: process.env.BAR, + }); + } + ), + }; + }, + ["us-central1"] + ); + + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/dotenv`) + .expect(200) + .then((res) => { + expect(res.body).to.deep.equal({ FOO: "foo", BAR: "bar" }); + }); + }); + + it("should prefer environment variables in .env.{projectId} file", async () => { + cleanup = await setupEnvFiles({ + ".env": "FOO=foo", + [`.env.${TEST_PROJECT_ID}`]: "FOO=goo", + }); + + await useFunction( + emu, + "dotenv", + () => { + return { + dotenv: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ + FOO: process.env.FOO, + }); + } + ), + }; + }, + ["us-central1"] + ); + + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/dotenv`) + .expect(200) + .then((res) => { + expect(res.body).to.deep.equal({ FOO: "goo" }); + }); + }); + + it("should prefer environment variables in .env.local file", async () => { + cleanup = await setupEnvFiles({ + ".env": "FOO=foo", + [`.env.${TEST_PROJECT_ID}`]: "FOO=goo", + ".env.local": "FOO=hoo", + }); + + await useFunction( + emu, + "dotenv", + () => { + return { + dotenv: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ + FOO: process.env.FOO, + }); + } + ), + }; + }, + ["us-central1"] + ); + + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/dotenv`) + .expect(200) + .then((res) => { + expect(res.body).to.deep.equal({ FOO: "hoo" }); + }); + }); }); - it("should load secret values from local secrets file if one exists", async () => { - readFileSyncStub.returns("MY_SECRET=local"); + describe("secrets", () => { + let readFileSyncStub: sinon.SinonStub; + let accessSecretVersionStub: sinon.SinonStub; - await useFunction( - emu, - "secretsFunctionId", + beforeEach(() => { + readFileSyncStub = sinon.stub(fs, "readFileSync").throws("Unexpected call"); + accessSecretVersionStub = sinon + .stub(secretManager, "accessSecretVersion") + .rejects("Unexpected call"); + }); + + afterEach(() => { + readFileSyncStub.restore(); + accessSecretVersionStub.restore(); + }); + + it("should load secret values from local secrets file if one exists", async () => { + readFileSyncStub.returns("MY_SECRET=local"); + + await useFunction( + emu, + "secretsFunctionId", + () => { + return { + secretsFunctionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ secret: process.env.MY_SECRET }); + } + ), + }; + }, + ["us-central1"], + { + secretEnvironmentVariables: [ + { + projectId: TEST_PROJECT_ID, + secret: "MY_SECRET", + key: "MY_SECRET", + version: "1", + }, + ], + } + ); + + await supertest(emu.createHubServer()) + .get("/" + TEST_PROJECT_ID + "/us-central1/secretsFunctionId") + .expect(200) + .then((res) => { + expect(res.body.secret).to.equal("local"); + }); + }); + + it("should try to access secret values from Secret Manager", async () => { + readFileSyncStub.throws({ code: "ENOENT" }); + accessSecretVersionStub.resolves("secretManager"); + + await useFunction( + emu, + "secretsFunctionId", + () => { + return { + secretsFunctionId: require("firebase-functions").https.onRequest( + (req: express.Request, res: express.Response) => { + res.json({ secret: process.env.MY_SECRET }); + } + ), + }; + }, + ["us-central1"], + { + secretEnvironmentVariables: [ + { + projectId: TEST_PROJECT_ID, + secret: "MY_SECRET", + key: "MY_SECRET", + version: "1", + }, + ], + } + ); + + await supertest(emu.createHubServer()) + .get(`/${TEST_PROJECT_ID}/us-central1/secretsFunctionId`) + .expect(200) + .then((res) => { + expect(res.body.secret).to.equal("secretManager"); + }); + }); + }); + }); + + describe("Discover", () => { + let cleanupSource: (() => Promise) | undefined; + let cleanupEnvs: (() => Promise) | undefined; + + afterEach(async () => { + await cleanupSource?.(); + await cleanupEnvs?.(); + }); + + it("resolves function with parameter value defined in .env correctly", async () => { + cleanupSource = await writeSource( () => { return { - secretsFunctionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ secret: process.env.MY_SECRET }); - } - ), + functionId: require("firebase-functions") + .runWith({ timeoutSeconds: "__$timeout__" }) + .https.onRequest((req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + }), }; }, - ["us-central1"], { - secretEnvironmentVariables: [ - { - projectId: "fake-project-id", - secret: "MY_SECRET", - key: "MY_SECRET", - version: "1", - }, - ], + timeout: () => require("firebase-functions/params").defineInt("TIMEOUT"), } ); + cleanupEnvs = await setupEnvFiles({ + ".env": "TIMEOUT=24", + }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/secretsFunctionId") - .expect(200) - .then((res) => { - expect(res.body.secret).to.equal("local"); - }); + const triggerDefinitions = await emu.discoverTriggers(TEST_BACKEND); + expect(triggerDefinitions).to.have.length(1); + expect(triggerDefinitions[0].timeoutSeconds).to.equal(24); }); - it("should try to access secret values from Secret Manager", async () => { - readFileSyncStub.throws({ code: "ENOENT" }); - accessSecretVersionStub.resolves("secretManager"); + it("resolves function with parameter value defined in .env.projectId correctly", async () => { + cleanupSource = await writeSource( + () => { + return { + functionId: require("firebase-functions") + .runWith({ timeoutSeconds: "__$timeout__" }) + .https.onRequest((req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + }), + }; + }, + { + timeout: () => require("firebase-functions/params").defineInt("TIMEOUT"), + } + ); + cleanupEnvs = await setupEnvFiles({ + ".env": "TIMEOUT=24", + [`.env.${TEST_PROJECT_ID}`]: "TIMEOUT=25", + }); - await useFunction( - emu, - "secretsFunctionId", + const triggerDefinitions = await emu.discoverTriggers(TEST_BACKEND); + expect(triggerDefinitions).to.have.length(1); + expect(triggerDefinitions[0].timeoutSeconds).to.equal(25); + }); + + it("resolves function with parameter value defined in .env.local correctly", async () => { + cleanupSource = await writeSource( () => { return { - secretsFunctionId: require("firebase-functions").https.onRequest( - (req: express.Request, res: express.Response) => { - res.json({ secret: process.env.MY_SECRET }); - } - ), + functionId: require("firebase-functions") + .runWith({ timeoutSeconds: "__$timeout__" }) + .https.onRequest((req: express.Request, res: express.Response) => { + res.json({ path: req.path }); + }), }; }, - ["us-central1"], { - secretEnvironmentVariables: [ - { - projectId: "fake-project-id", - secret: "MY_SECRET", - key: "MY_SECRET", - version: "1", - }, - ], + timeout: () => require("firebase-functions/params").defineInt("TIMEOUT"), } ); + cleanupEnvs = await setupEnvFiles({ + ".env": "TIMEOUT=24", + [`.env.${TEST_PROJECT_ID}`]: "TIMEOUT=25", + ".env.local": "TIMEOUT=26", + }); - await supertest(emu.createHubServer()) - .get("/fake-project-id/us-central1/secretsFunctionId") - .expect(200) - .then((res) => { - expect(res.body.secret).to.equal("secretManager"); - }); + const triggerDefinitions = await emu.discoverTriggers(TEST_BACKEND); + expect(triggerDefinitions).to.have.length(1); + expect(triggerDefinitions[0].timeoutSeconds).to.equal(26); }); }); }); diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 34722120d5e..c4c7f3edd1f 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -508,6 +508,7 @@ export class FunctionsEmulator implements EmulatorInstance { functionsSource: emulatableBackend.functionsDir, projectId: this.args.projectId, projectAlias: this.args.projectAlias, + isEmulator: true, }; const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt); const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment); From 0c64e8ee3b3e7e31092326eb10fc069cf31b4fc6 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Tue, 31 Jan 2023 13:57:40 -0800 Subject: [PATCH 0777/1699] npm audit fix: taffydb (#5476) * npm audit fix: taffydb * upgrade taffydb in top-level package --- npm-shrinkwrap.json | 77 +++++----- .../functions/package-lock.json | 105 ++++++++------ .../triggers/package-lock.json | 137 ++++++++++-------- .../v1/package-lock.json | 117 ++++++++------- .../v2/package-lock.json | 117 ++++++++------- 5 files changed, 299 insertions(+), 254 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7269a446197..8a324bf4af2 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2189,6 +2189,17 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "node_modules/@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@manifoldco/swagger-to-ts": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@manifoldco/swagger-to-ts/-/swagger-to-ts-2.1.0.tgz", @@ -8095,9 +8106,9 @@ "dev": true }, "node_modules/google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "dependencies": { "@grpc/grpc-js": "~1.7.0", "@grpc/proto-loader": "^0.7.0", @@ -8111,7 +8122,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" }, "bin": { @@ -8364,16 +8375,16 @@ } }, "node_modules/google-gax/node_modules/protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "dependencies": { "chalk": "^4.0.0", "escodegen": "^1.13.0", "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -9697,11 +9708,12 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "node_modules/jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "dependencies": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -9714,7 +9726,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" }, "bin": { @@ -14295,11 +14306,6 @@ "node": ">=12" } }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" - }, "node_modules/tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", @@ -17447,6 +17453,14 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "requires": { + "lodash": "^4.17.21" + } + }, "@manifoldco/swagger-to-ts": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@manifoldco/swagger-to-ts/-/swagger-to-ts-2.1.0.tgz", @@ -21911,9 +21925,9 @@ } }, "google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "requires": { "@grpc/grpc-js": "~1.7.0", "@grpc/proto-loader": "^0.7.0", @@ -21927,7 +21941,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" }, "dependencies": { @@ -22116,16 +22130,16 @@ } }, "protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "requires": { "chalk": "^4.0.0", "escodegen": "^1.13.0", "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -23134,11 +23148,12 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "requires": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -23151,7 +23166,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" }, "dependencies": { @@ -26694,11 +26708,6 @@ } } }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" - }, "tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", diff --git a/scripts/emulator-tests/functions/package-lock.json b/scripts/emulator-tests/functions/package-lock.json index 679b999d8c8..d525a285f06 100644 --- a/scripts/emulator-tests/functions/package-lock.json +++ b/scripts/emulator-tests/functions/package-lock.json @@ -17,9 +17,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", + "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", "optional": true, "bin": { "parser": "bin/babel-parser.js" @@ -236,6 +236,18 @@ "node": ">=6" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -441,9 +453,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "optional": true, "bin": { "acorn": "bin/acorn" @@ -1247,9 +1259,9 @@ } }, "node_modules/google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "optional": true, "dependencies": { "@grpc/grpc-js": "~1.7.0", @@ -1264,7 +1276,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" }, "bin": { @@ -1513,12 +1525,13 @@ } }, "node_modules/jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "optional": true, "dependencies": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -1531,7 +1544,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" }, "bin": { @@ -2063,9 +2075,9 @@ } }, "node_modules/protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "optional": true, "dependencies": { "chalk": "^4.0.0", @@ -2073,7 +2085,7 @@ "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -2478,12 +2490,6 @@ "node": ">=8" } }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "optional": true - }, "node_modules/teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", @@ -2750,9 +2756,9 @@ }, "dependencies": { "@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", + "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", "optional": true }, "@fastify/busboy": { @@ -2929,6 +2935,15 @@ "yargs": "^16.2.0" } }, + "@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "optional": true, + "requires": { + "lodash": "^4.17.21" + } + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -3125,9 +3140,9 @@ } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "optional": true }, "acorn-jsx": { @@ -3741,9 +3756,9 @@ } }, "google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "optional": true, "requires": { "@grpc/grpc-js": "~1.7.0", @@ -3758,7 +3773,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" } }, @@ -3940,12 +3955,13 @@ } }, "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "optional": true, "requires": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -3958,7 +3974,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" } }, @@ -4381,9 +4396,9 @@ } }, "protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "optional": true, "requires": { "chalk": "^4.0.0", @@ -4391,7 +4406,7 @@ "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -4686,12 +4701,6 @@ "has-flag": "^4.0.0" } }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "optional": true - }, "teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", diff --git a/scripts/triggers-end-to-end-tests/triggers/package-lock.json b/scripts/triggers-end-to-end-tests/triggers/package-lock.json index 75bcfa5f372..08cf992abd7 100644 --- a/scripts/triggers-end-to-end-tests/triggers/package-lock.json +++ b/scripts/triggers-end-to-end-tests/triggers/package-lock.json @@ -20,9 +20,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", + "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -846,6 +846,17 @@ "node": ">=6" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@opentelemetry/api": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.1.0.tgz", @@ -1074,9 +1085,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "bin": { "acorn": "bin/acorn" }, @@ -2015,9 +2026,9 @@ } }, "node_modules/google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "dependencies": { "@grpc/grpc-js": "~1.7.0", "@grpc/proto-loader": "^0.7.0", @@ -2031,7 +2042,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" }, "bin": { @@ -2069,9 +2080,9 @@ } }, "node_modules/google-gax/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2087,9 +2098,9 @@ } }, "node_modules/google-gax/node_modules/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2157,16 +2168,16 @@ } }, "node_modules/google-gax/node_modules/protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "dependencies": { "chalk": "^4.0.0", "escodegen": "^1.13.0", "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -2450,11 +2461,12 @@ } }, "node_modules/jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "dependencies": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -2467,7 +2479,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" }, "bin": { @@ -2754,9 +2765,9 @@ } }, "node_modules/marked": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz", - "integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", "bin": { "marked": "bin/marked.js" }, @@ -3398,11 +3409,6 @@ "node": ">=8" } }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" - }, "node_modules/teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", @@ -3681,9 +3687,9 @@ }, "dependencies": { "@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==" + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", + "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==" }, "@fastify/busboy": { "version": "1.1.0", @@ -4376,6 +4382,14 @@ "yargs": "^16.2.0" } }, + "@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "requires": { + "lodash": "^4.17.21" + } + }, "@opentelemetry/api": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.1.0.tgz", @@ -4586,9 +4600,9 @@ } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" }, "acorn-jsx": { "version": "5.3.2", @@ -5325,9 +5339,9 @@ } }, "google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "requires": { "@grpc/grpc-js": "~1.7.0", "@grpc/proto-loader": "^0.7.0", @@ -5341,7 +5355,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" }, "dependencies": { @@ -5366,9 +5380,9 @@ } }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5378,9 +5392,9 @@ } }, "minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "requires": { "brace-expansion": "^2.0.1" } @@ -5442,16 +5456,16 @@ } }, "protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "requires": { "chalk": "^4.0.0", "escodegen": "^1.13.0", "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -5658,11 +5672,12 @@ } }, "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "requires": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -5675,7 +5690,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" } }, @@ -5937,9 +5951,9 @@ "requires": {} }, "marked": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz", - "integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==" + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==" }, "mdurl": { "version": "1.0.1", @@ -6394,11 +6408,6 @@ "has-flag": "^4.0.0" } }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" - }, "teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", diff --git a/scripts/triggers-end-to-end-tests/v1/package-lock.json b/scripts/triggers-end-to-end-tests/v1/package-lock.json index 330a6bb2b5e..4b0c0be8c07 100644 --- a/scripts/triggers-end-to-end-tests/v1/package-lock.json +++ b/scripts/triggers-end-to-end-tests/v1/package-lock.json @@ -18,9 +18,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", + "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", "optional": true, "bin": { "parser": "bin/babel-parser.js" @@ -319,6 +319,18 @@ "node": ">=6" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@panva/asn1.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", @@ -538,9 +550,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "optional": true, "bin": { "acorn": "bin/acorn" @@ -1436,9 +1448,9 @@ } }, "node_modules/google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "optional": true, "dependencies": { "@grpc/grpc-js": "~1.7.0", @@ -1453,7 +1465,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" }, "bin": { @@ -1714,12 +1726,13 @@ } }, "node_modules/jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "optional": true, "dependencies": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -1732,7 +1745,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" }, "bin": { @@ -2048,9 +2060,9 @@ } }, "node_modules/minimatch": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.5.tgz", - "integrity": "sha512-CI8wwdrll4ehjPAqs8TL8lBPyNnpZlQI02Wn8C1weNz/QbUbjh3OMxgMKSnvqfKFdLlks3EzHB9tO0BqGc3phQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "optional": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -2265,9 +2277,9 @@ } }, "node_modules/protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "optional": true, "dependencies": { "chalk": "^4.0.0", @@ -2275,7 +2287,7 @@ "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -2691,12 +2703,6 @@ "node": ">=8" } }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "optional": true - }, "node_modules/teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", @@ -2963,9 +2969,9 @@ }, "dependencies": { "@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", + "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", "optional": true }, "@fastify/busboy": { @@ -3230,6 +3236,15 @@ "yargs": "^16.2.0" } }, + "@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "optional": true, + "requires": { + "lodash": "^4.17.21" + } + }, "@panva/asn1.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", @@ -3437,9 +3452,9 @@ } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "optional": true }, "acorn-jsx": { @@ -4137,9 +4152,9 @@ } }, "google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "optional": true, "requires": { "@grpc/grpc-js": "~1.7.0", @@ -4154,7 +4169,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" } }, @@ -4345,12 +4360,13 @@ } }, "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "optional": true, "requires": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -4363,7 +4379,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" } }, @@ -4628,9 +4643,9 @@ } }, "minimatch": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.5.tgz", - "integrity": "sha512-CI8wwdrll4ehjPAqs8TL8lBPyNnpZlQI02Wn8C1weNz/QbUbjh3OMxgMKSnvqfKFdLlks3EzHB9tO0BqGc3phQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "optional": true, "requires": { "brace-expansion": "^2.0.1" @@ -4787,9 +4802,9 @@ } }, "protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "optional": true, "requires": { "chalk": "^4.0.0", @@ -4797,7 +4812,7 @@ "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -5097,12 +5112,6 @@ "has-flag": "^4.0.0" } }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "optional": true - }, "teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", diff --git a/scripts/triggers-end-to-end-tests/v2/package-lock.json b/scripts/triggers-end-to-end-tests/v2/package-lock.json index 2b1898a5655..9e2fbe3c131 100644 --- a/scripts/triggers-end-to-end-tests/v2/package-lock.json +++ b/scripts/triggers-end-to-end-tests/v2/package-lock.json @@ -17,9 +17,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", + "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", "optional": true, "bin": { "parser": "bin/babel-parser.js" @@ -234,6 +234,18 @@ "node": ">=6" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@panva/asn1.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", @@ -453,9 +465,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "optional": true, "bin": { "acorn": "bin/acorn" @@ -1277,9 +1289,9 @@ } }, "node_modules/google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "optional": true, "dependencies": { "@grpc/grpc-js": "~1.7.0", @@ -1294,7 +1306,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" }, "bin": { @@ -1549,12 +1561,13 @@ } }, "node_modules/jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "optional": true, "dependencies": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -1567,7 +1580,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" }, "bin": { @@ -1883,9 +1895,9 @@ } }, "node_modules/minimatch": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.5.tgz", - "integrity": "sha512-CI8wwdrll4ehjPAqs8TL8lBPyNnpZlQI02Wn8C1weNz/QbUbjh3OMxgMKSnvqfKFdLlks3EzHB9tO0BqGc3phQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "optional": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -2100,9 +2112,9 @@ } }, "node_modules/protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "optional": true, "dependencies": { "chalk": "^4.0.0", @@ -2110,7 +2122,7 @@ "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -2526,12 +2538,6 @@ "node": ">=8" } }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "optional": true - }, "node_modules/teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", @@ -2798,9 +2804,9 @@ }, "dependencies": { "@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", + "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", "optional": true }, "@fastify/busboy": { @@ -2980,6 +2986,15 @@ "yargs": "^16.2.0" } }, + "@jsdoc/salty": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", + "integrity": "sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg==", + "optional": true, + "requires": { + "lodash": "^4.17.21" + } + }, "@panva/asn1.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", @@ -3187,9 +3202,9 @@ } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "optional": true }, "acorn-jsx": { @@ -3814,9 +3829,9 @@ } }, "google-gax": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.2.tgz", - "integrity": "sha512-AyP53w0gHcWlzxm+jSgqCR3Xu4Ld7EpSjhtNBnNhzwwWaIUyphH9kBGNIEH+i4UGkTUXOY29K/Re8EiAvkBRGw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.5.3.tgz", + "integrity": "sha512-caItkifbcPDVf5xW2j6xLXmuX9bh2dheJN9AzMuQj6VvWacUVV5d0BkjL+Ia8sgX9VvXtbqjf4NKK3nTgV9UUQ==", "optional": true, "requires": { "@grpc/grpc-js": "~1.7.0", @@ -3831,7 +3846,7 @@ "object-hash": "^3.0.0", "proto3-json-serializer": "^1.0.0", "protobufjs": "7.1.2", - "protobufjs-cli": "1.0.2", + "protobufjs-cli": "1.1.0", "retry-request": "^5.0.0" } }, @@ -4016,12 +4031,13 @@ } }, "jsdoc": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", - "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", "optional": true, "requires": { "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", @@ -4034,7 +4050,6 @@ "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", "underscore": "~1.13.2" } }, @@ -4299,9 +4314,9 @@ } }, "minimatch": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.5.tgz", - "integrity": "sha512-CI8wwdrll4ehjPAqs8TL8lBPyNnpZlQI02Wn8C1weNz/QbUbjh3OMxgMKSnvqfKFdLlks3EzHB9tO0BqGc3phQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "optional": true, "requires": { "brace-expansion": "^2.0.1" @@ -4458,9 +4473,9 @@ } }, "protobufjs-cli": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz", - "integrity": "sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "optional": true, "requires": { "chalk": "^4.0.0", @@ -4468,7 +4483,7 @@ "espree": "^9.0.0", "estraverse": "^5.1.0", "glob": "^8.0.0", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.0", "minimist": "^1.2.0", "semver": "^7.1.2", "tmp": "^0.2.1", @@ -4768,12 +4783,6 @@ "has-flag": "^4.0.0" } }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", - "optional": true - }, "teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", From 06ec3c1d9abef7f1c069183ef92fe5248226f3e0 Mon Sep 17 00:00:00 2001 From: Stefan Pfaffel Date: Tue, 31 Jan 2023 23:12:07 +0100 Subject: [PATCH 0778/1699] fix: slow startup if no network is available (#5055) * fix: add timeouts to npm commands To prevent slow startups if no network is available * chore: update npm command timeout Co-authored-by: Cole Rogers --- src/deploy/functions/runtimes/node/versioning.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/deploy/functions/runtimes/node/versioning.ts b/src/deploy/functions/runtimes/node/versioning.ts index 1ea2fd7de48..d0c400c9bbb 100644 --- a/src/deploy/functions/runtimes/node/versioning.ts +++ b/src/deploy/functions/runtimes/node/versioning.ts @@ -2,12 +2,12 @@ import * as fs from "fs"; import * as path from "path"; import * as clc from "colorette"; -import * as semver from "semver"; import * as spawn from "cross-spawn"; +import * as semver from "semver"; -import * as utils from "../../../../utils"; import { logger } from "../../../../logger"; import { track } from "../../../../track"; +import * as utils from "../../../../utils"; interface NpmShowResult { "dist-tags": { @@ -16,6 +16,7 @@ interface NpmShowResult { } const MIN_SDK_VERSION = "2.0.0"; +const NPM_COMMAND_TIMEOUT_MILLIES = 10000; export const FUNCTIONS_SDK_VERSION_TOO_OLD_WARNING = clc.bold(clc.yellow("functions: ")) + @@ -88,6 +89,7 @@ export function getFunctionsSDKVersion(sourceDir: string): string | undefined { export function getLatestSDKVersion(): string | undefined { const child = spawn.sync("npm", ["show", "firebase-functions", "--json=true"], { encoding: "utf8", + timeout: NPM_COMMAND_TIMEOUT_MILLIES, }); if (child.error) { logger.debug( From 6c5afba3d5f7b737023a1da6f83f28f5d134ee32 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 31 Jan 2023 22:35:55 +0000 Subject: [PATCH 0779/1699] 11.22.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8a324bf4af2..a296c4bfbf8 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.21.0", + "version": "11.22.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.21.0", + "version": "11.22.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index c24694256e9..86433758444 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.21.0", + "version": "11.22.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 0c280de08e50b7d21cebac07b9287fd94f07a5f8 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 31 Jan 2023 22:36:05 +0000 Subject: [PATCH 0780/1699] [firebase-release] Removed change log and reset repo after 11.22.0 release --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 409e9d87012..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +0,0 @@ -- Refactors Functions Emulator. (#5422) -- Fixes race condition when discovering functions. (#5444) -- Added support for Nuxt 2 and Nuxt 3. (#5321) -- Fixes issue where `init firestore` was unecessarilly checking for default resource location. (#5230 and #5452) -- Pass `trailingSlash` from Next.js config to `firebase.json` (#5445) -- Don't use Next.js internal redirects for the backend test (#5445) -- Fix issue where pnpm support broke for function emulation and deployment. (#5467) -- Fix bug where .env.local files were not picked up during function emulation. (#5477) From 5a02f614be6bc9eac3679b8809a4d1e1084d21a8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 31 Jan 2023 15:29:38 -0800 Subject: [PATCH 0781/1699] Enforce function timeout at worker level. (#5464) Function timeout for emulator was enforced by the emulated function process. Here we pull out the timeout enforcement at the worker level (analogous to "Data Plane" in production environment). When timeout is reached, the worker will abort the triggering request and kill the child process. This refactor will remove the need to write code for enforcing timeout for emulated python functions. --- CHANGELOG.md | 1 + .../emulator-tests/functionsEmulator.spec.ts | 29 ++++ .../functionsEmulatorRuntime.spec.ts | 34 ----- src/emulator/functionsEmulator.ts | 8 +- src/emulator/functionsEmulatorRuntime.ts | 36 +---- src/emulator/functionsRuntimeWorker.ts | 144 ++++++++++++------ .../emulators/functionsRuntimeWorker.spec.ts | 69 +++++---- 7 files changed, 166 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..349b92a3ed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Refactor the way timeouts are enforced by the Functions Emulator (#5464) diff --git a/scripts/emulator-tests/functionsEmulator.spec.ts b/scripts/emulator-tests/functionsEmulator.spec.ts index ef6d7c128d8..fc70f1e90ba 100644 --- a/scripts/emulator-tests/functionsEmulator.spec.ts +++ b/scripts/emulator-tests/functionsEmulator.spec.ts @@ -1037,4 +1037,33 @@ describe("FunctionsEmulator", function () { expect(triggerDefinitions[0].timeoutSeconds).to.equal(26); }); }); + + it("should enforce timeout", async () => { + await useFunction( + emu, + "timeoutFn", + () => { + return { + timeoutFn: require("firebase-functions") + .runWith({ timeoutSeconds: 1 }) + .https.onRequest((req: express.Request, res: express.Response): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + res.sendStatus(200); + resolve(); + }, 5_000); + }); + }), + }; + }, + ["us-central1"], + { + timeoutSeconds: 1, + } + ); + + await supertest(emu.createHubServer()) + .get("/fake-project-id/us-central1/timeoutFn") + .expect(500); + }); }); diff --git a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts index 4bbd63ce45f..9d7c270b32e 100644 --- a/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts +++ b/scripts/emulator-tests/functionsEmulatorRuntime.spec.ts @@ -730,40 +730,6 @@ describe("FunctionsEmulator-Runtime", function () { expect(runtime.sysMsg["runtime-error"]?.length).to.eq(1); }); }); - - describe("Timeout", () => { - it("enforces configured timeout", async () => { - const timeoutEnvs = { - FUNCTIONS_EMULATOR_TIMEOUT_SECONDS: "1", - FUNCTIONS_EMULATOR_DISABLE_TIMEOUT: "false", - }; - runtime = await startRuntime( - "functionId", - "http", - () => { - return { - functionId: require("firebase-functions").https.onRequest( - (req: any, resp: any): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - resp.sendStatus(200); - resolve(); - }, 5_000); - }); - } - ), - }; - }, - timeoutEnvs - ); - try { - await sendReq(runtime); - } catch (e: any) { - // Carry on - } - expect(runtime.sysMsg["runtime-error"]?.length).to.eq(1); - }); - }); }); describe("Debug", () => { diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index c4c7f3edd1f..6965429405d 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -1108,12 +1108,6 @@ export class FunctionsEmulator implements EmulatorInstance { envs.K_REVISION = "1"; envs.PORT = "80"; - // TODO(danielylee): Later, we want timeout to be enforce by the data plane. For now, we rely on the runtime to - // enforce timeout. - if (trigger?.timeoutSeconds) { - envs.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS = trigger.timeoutSeconds.toString(); - } - if (trigger) { const target = trigger.entryPoint; envs.FUNCTION_TARGET = target; @@ -1357,7 +1351,7 @@ export class FunctionsEmulator implements EmulatorInstance { }; const pool = this.workerPools[backend.codebase]; - const worker = pool.addWorker(trigger?.id, runtime, extensionLogInfo); + const worker = pool.addWorker(trigger, runtime, extensionLogInfo); await worker.waitForSocketReady(); return worker; } diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index d514c43181e..5e3b20686a3 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -1018,12 +1018,6 @@ async function main(): Promise { }); app.all(`/*`, async (req: express.Request, res: express.Response) => { try { - new EmulatorLog( - "INFO", - "runtime-status", - `Beginning execution of "${FUNCTION_TARGET_NAME}"` - ).log(); - const trigger = FUNCTION_TARGET_NAME.split(".").reduce((mod, functionTargetPart) => { return mod?.[functionTargetPart]; }, functionModule) as CloudFunction; @@ -1031,18 +1025,6 @@ async function main(): Promise { throw new Error(`Failed to find function ${FUNCTION_TARGET_NAME} in the loaded module`); } - const startHrTime = process.hrtime(); - res.on("finish", () => { - const elapsedHrTime = process.hrtime(startHrTime); - new EmulatorLog( - "INFO", - "runtime-status", - `Finished "${FUNCTION_TARGET_NAME}" in ${ - elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1000000 - }ms` - ).log(); - }); - switch (FUNCTION_SIGNATURE) { case "event": case "cloudevent": @@ -1063,25 +1045,9 @@ async function main(): Promise { res.status(500).send(err.message); } }); - const server = app.listen(process.env.PORT, () => { + app.listen(process.env.PORT, () => { logDebug(`Listening to port: ${process.env.PORT}`); }); - if (!FUNCTION_DEBUG_MODE) { - let timeout = process.env.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS || "60"; - if (timeout.endsWith("s")) { - timeout = timeout.slice(0, -1); - } - const timeoutMs = parseInt(timeout, 10) * 1000; - server.setTimeout(timeoutMs, () => { - new EmulatorLog( - "FATAL", - "runtime-error", - `Your function timed out after ~${timeout}s. To configure this timeout, see - https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation.` - ).log(); - return flushAndExit(1); - }); - } // Event emitters do not work well with async functions, so we // construct our own promise chain to make sure each message is diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index 5365b7989d2..241aece94b8 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -3,12 +3,11 @@ import * as uuid from "uuid"; import { FunctionsRuntimeInstance } from "./functionsEmulator"; import { EmulatorLog, Emulators, FunctionsExecutionMode } from "./types"; -import { FunctionsRuntimeBundle } from "./functionsEmulatorShared"; +import { EmulatedTriggerDefinition, FunctionsRuntimeBundle } from "./functionsEmulatorShared"; import { EventEmitter } from "events"; import { EmulatorLogger, ExtensionLogInfo } from "./emulatorLogger"; import { FirebaseError } from "../error"; import { Serializable } from "child_process"; -import { IncomingMessage } from "http"; type LogListener = (el: EmulatorLog) => any; @@ -30,19 +29,32 @@ export enum RuntimeWorkerState { FINISHED = "FINISHED", } +/** + * Given no trigger key, worker is given this special key. + * + * This is useful when running the Functions Emulator in debug mode + * where single process shared amongst all triggers. + */ +const FREE_WORKER_KEY = "~free~"; + export class RuntimeWorker { readonly id: string; - readonly key: string; - readonly runtime: FunctionsRuntimeInstance; + readonly triggerKey: string; stateEvents: EventEmitter = new EventEmitter(); private logListeners: Array = []; + private logger: EmulatorLogger; private _state: RuntimeWorkerState = RuntimeWorkerState.CREATED; - constructor(key: string, runtime: FunctionsRuntimeInstance) { + constructor( + triggerId: string | undefined, + readonly runtime: FunctionsRuntimeInstance, + readonly extensionLogInfo: ExtensionLogInfo, + readonly timeoutSeconds?: number + ) { this.id = uuid.v4(); - this.key = key; + this.triggerKey = triggerId || FREE_WORKER_KEY; this.runtime = runtime; const childProc = this.runtime.process; @@ -64,8 +76,15 @@ export class RuntimeWorker { }); } + this.logger = triggerId + ? EmulatorLogger.forFunction(triggerId, extensionLogInfo) + : EmulatorLogger.forEmulator(Emulators.FUNCTIONS); + this.onLogs((log: EmulatorLog) => { + this.logger.handleRuntimeLog(log); + }, true /* listen forever */); + childProc.on("exit", () => { - this.log("exited"); + this.logDebug("exited"); this.state = RuntimeWorkerState.FINISHED; }); } @@ -107,33 +126,57 @@ export class RuntimeWorker { } request(req: http.RequestOptions, resp: http.ServerResponse, body?: unknown): Promise { + if (this.triggerKey !== FREE_WORKER_KEY) { + this.logInfo(`Beginning execution of "${this.triggerKey}"`); + } + const startHrTime = process.hrtime(); + this.state = RuntimeWorkerState.BUSY; const onFinish = (): void => { + if (this.triggerKey !== FREE_WORKER_KEY) { + const elapsedHrTime = process.hrtime(startHrTime); + this.logInfo( + `Finished "${this.triggerKey}" in ${ + elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1000000 + }ms` + ); + } + if (this.state === RuntimeWorkerState.BUSY) { this.state = RuntimeWorkerState.IDLE; } else if (this.state === RuntimeWorkerState.FINISHING) { - this.log(`IDLE --> FINISHING`); + this.logDebug(`IDLE --> FINISHING`); this.runtime.process.kill(); } }; return new Promise((resolve) => { - const proxy = http.request( - { - ...this.runtime.conn.httpReqOpts(), - method: req.method, - path: req.path, - headers: req.headers, - }, - (_resp: IncomingMessage) => { - resp.writeHead(_resp.statusCode || 200, _resp.headers); - const piped = _resp.pipe(resp); - piped.on("finish", () => { - onFinish(); - resolve(); - }); - } - ); + const reqOpts = { + ...this.runtime.conn.httpReqOpts(), + method: req.method, + path: req.path, + headers: req.headers, + }; + if (this.timeoutSeconds) { + reqOpts.timeout = this.timeoutSeconds * 1000; + } + const proxy = http.request(reqOpts, (_resp: http.IncomingMessage) => { + resp.writeHead(_resp.statusCode || 200, _resp.headers); + const piped = _resp.pipe(resp); + piped.on("finish", () => { + onFinish(); + resolve(); + }); + }); + proxy.on("timeout", () => { + this.logger.log( + "ERROR", + `Your function timed out after ~${this.timeoutSeconds}s. To configure this timeout, see + https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation.` + ); + proxy.destroy(); + }); proxy.on("error", (err) => { + this.logger.log("ERROR", `Request to function failed: ${err}`); resp.writeHead(500); resp.write(JSON.stringify(err)); resp.end(); @@ -164,7 +207,7 @@ export class RuntimeWorker { this.runtime.events.removeAllListeners(); } - this.log(state); + this.logDebug(state); this._state = state; this.stateEvents.emit(this._state); } @@ -220,11 +263,12 @@ export class RuntimeWorker { } } - private log(msg: string): void { - EmulatorLogger.forEmulator(Emulators.FUNCTIONS).log( - "DEBUG", - `[worker-${this.key}-${this.id}]: ${msg}` - ); + private logDebug(msg: string): void { + this.logger.log("DEBUG", `[worker-${this.triggerKey}-${this.id}]: ${msg}`); + } + + private logInfo(msg: string): void { + this.logger.logLabeled("BULLET", "functions", msg); } } @@ -233,7 +277,7 @@ export class RuntimeWorkerPool { constructor(private mode: FunctionsExecutionMode = FunctionsExecutionMode.AUTO) {} - getKey(triggerId: string | undefined) { + getKey(triggerId: string | undefined): string { if (this.mode === FunctionsExecutionMode.SEQUENTIAL) { return "~shared~"; } else { @@ -247,15 +291,15 @@ export class RuntimeWorkerPool { * each BUSY worker we move it to the FINISHING state so that it will * kill itself after it's done with its current task. */ - refresh() { + refresh(): void { for (const arr of this.workers.values()) { arr.forEach((w) => { if (w.state === RuntimeWorkerState.IDLE) { - this.log(`Shutting down IDLE worker (${w.key})`); + this.log(`Shutting down IDLE worker (${w.triggerKey})`); w.state = RuntimeWorkerState.FINISHING; w.runtime.process.kill(); } else if (w.state === RuntimeWorkerState.BUSY) { - this.log(`Marking BUSY worker to finish (${w.key})`); + this.log(`Marking BUSY worker to finish (${w.triggerKey})`); w.state = RuntimeWorkerState.FINISHING; } }); @@ -265,7 +309,7 @@ export class RuntimeWorkerPool { /** * Immediately kill all workers. */ - exit() { + exit(): void { for (const arr of this.workers.values()) { arr.forEach((w) => { if (w.state === RuntimeWorkerState.IDLE) { @@ -340,25 +384,27 @@ export class RuntimeWorkerPool { * `worker.readyForWork()` or `worker.waitForSocketReady()`. */ addWorker( - triggerId: string | undefined, + trigger: EmulatedTriggerDefinition | undefined, runtime: FunctionsRuntimeInstance, - extensionLogInfo?: ExtensionLogInfo + extensionLogInfo: ExtensionLogInfo ): RuntimeWorker { - const worker = new RuntimeWorker(this.getKey(triggerId), runtime); - this.log(`addWorker(${worker.key})`); + this.log(`addWorker(${this.getKey(trigger?.id)})`); + // Disable worker timeout if: + // (1) This is a diagnostic call without trigger id OR + // (2) If in SEQUENTIAL execution mode + const disableTimeout = !trigger?.id || this.mode === FunctionsExecutionMode.SEQUENTIAL; + const worker = new RuntimeWorker( + trigger?.id, + runtime, + extensionLogInfo, + disableTimeout ? undefined : trigger?.timeoutSeconds + ); - const keyWorkers = this.getTriggerWorkers(triggerId); + const keyWorkers = this.getTriggerWorkers(trigger?.id); keyWorkers.push(worker); - this.setTriggerWorkers(triggerId, keyWorkers); - - const logger = triggerId - ? EmulatorLogger.forFunction(triggerId, extensionLogInfo) - : EmulatorLogger.forEmulator(Emulators.FUNCTIONS); - worker.onLogs((log: EmulatorLog) => { - logger.handleRuntimeLog(log); - }, true /* listen forever */); + this.setTriggerWorkers(trigger?.id, keyWorkers); - this.log(`Adding worker with key ${worker.key}, total=${keyWorkers.length}`); + this.log(`Adding worker with key ${worker.triggerKey}, total=${keyWorkers.length}`); return worker; } diff --git a/src/test/emulators/functionsRuntimeWorker.spec.ts b/src/test/emulators/functionsRuntimeWorker.spec.ts index 00bc983930c..28a73687eaf 100644 --- a/src/test/emulators/functionsRuntimeWorker.spec.ts +++ b/src/test/emulators/functionsRuntimeWorker.spec.ts @@ -8,6 +8,7 @@ import { RuntimeWorkerPool, RuntimeWorkerState, } from "../../emulator/functionsRuntimeWorker"; +import { EmulatedTriggerDefinition } from "../../emulator/functionsEmulatorShared"; import { EmulatorLog, FunctionsExecutionMode } from "../../emulator/types"; import { ChildProcess } from "child_process"; @@ -82,14 +83,22 @@ class WorkerStateCounter { } } -describe("FunctionsRuntimeWorker", () => { - const workerPool = new RuntimeWorkerPool(); +function mockTrigger(id: string): EmulatedTriggerDefinition { + return { + id, + name: id, + entryPoint: id, + region: "us-central1", + platform: "gcfv2", + }; +} +describe("FunctionsRuntimeWorker", () => { describe("RuntimeWorker", () => { it("goes from created --> idle --> busy --> idle in normal operation", async () => { const scope = nock("http://localhost").get("/").reply(200); - const worker = new RuntimeWorker(workerPool.getKey("trigger"), new MockRuntimeInstance()); + const worker = new RuntimeWorker("trigger", new MockRuntimeInstance(), {}); const counter = new WorkerStateCounter(worker); worker.readyForWork(); @@ -108,7 +117,7 @@ describe("FunctionsRuntimeWorker", () => { it("goes from created --> idle --> busy --> finished when there's an error", async () => { const scope = nock("http://localhost").get("/").replyWithError("boom"); - const worker = new RuntimeWorker(workerPool.getKey("trigger"), new MockRuntimeInstance()); + const worker = new RuntimeWorker("trigger", new MockRuntimeInstance(), {}); const counter = new WorkerStateCounter(worker); worker.readyForWork(); @@ -128,7 +137,7 @@ describe("FunctionsRuntimeWorker", () => { it("goes from created --> busy --> finishing --> finished when marked", async () => { const scope = nock("http://localhost").get("/").replyWithError("boom"); - const worker = new RuntimeWorker(workerPool.getKey("trigger"), new MockRuntimeInstance()); + const worker = new RuntimeWorker("trigger", new MockRuntimeInstance(), {}); const counter = new WorkerStateCounter(worker); worker.readyForWork(); @@ -153,42 +162,42 @@ describe("FunctionsRuntimeWorker", () => { const scope = nock("http://localhost").get("/").reply(200); const pool = new RuntimeWorkerPool(); - const trigger = "region-trigger1"; + const triggerId = "region-trigger1"; // No idle workers to begin - expect(pool.getIdleWorker(trigger)).to.be.undefined; + expect(pool.getIdleWorker(triggerId)).to.be.undefined; // Add a worker and make sure it's there - const worker = pool.addWorker(trigger, new MockRuntimeInstance()); + const worker = pool.addWorker(mockTrigger(triggerId), new MockRuntimeInstance(), {}); worker.readyForWork(); - const triggerWorkers = pool.getTriggerWorkers(trigger); + const triggerWorkers = pool.getTriggerWorkers(triggerId); expect(triggerWorkers.length).length.to.eq(1); - expect(pool.getIdleWorker(trigger)).to.eql(worker); + expect(pool.getIdleWorker(triggerId)).to.eql(worker); const resp = httpMocks.createResponse({ eventEmitter: EventEmitter }); resp.on("end", () => { // Finished sending response. About to go back to IDLE state. - expect(pool.getIdleWorker(trigger)).to.be.undefined; + expect(pool.getIdleWorker(triggerId)).to.be.undefined; }); await worker.request({ method: "GET", path: "/" }, resp); scope.done(); // Completed handling request. Worker should be IDLE again. - expect(pool.getIdleWorker(trigger)).to.eql(worker); + expect(pool.getIdleWorker(triggerId)).to.eql(worker); }); it("does not consider failed workers idle", async () => { const pool = new RuntimeWorkerPool(); - const trigger = "trigger1"; + const triggerId = "trigger1"; // No idle workers to begin - expect(pool.getIdleWorker(trigger)).to.be.undefined; + expect(pool.getIdleWorker(triggerId)).to.be.undefined; // Add a worker to the pool that's destined to fail. const scope = nock("http://localhost").get("/").replyWithError("boom"); - const worker = pool.addWorker(trigger, new MockRuntimeInstance()); + const worker = pool.addWorker(mockTrigger(triggerId), new MockRuntimeInstance(), {}); worker.readyForWork(); - expect(pool.getIdleWorker(trigger)).to.eql(worker); + expect(pool.getIdleWorker(triggerId)).to.eql(worker); // Send request to the worker. Request should fail, killing the worker. await worker.request( @@ -198,18 +207,18 @@ describe("FunctionsRuntimeWorker", () => { scope.done(); // Confirm there are no idle workers. - expect(pool.getIdleWorker(trigger)).to.be.undefined; + expect(pool.getIdleWorker(triggerId)).to.be.undefined; }); it("exit() kills idle and busy workers", async () => { const pool = new RuntimeWorkerPool(); - const trigger = "trigger1"; + const triggerId = "trigger1"; - const busyWorker = pool.addWorker(trigger, new MockRuntimeInstance()); + const busyWorker = pool.addWorker(mockTrigger(triggerId), new MockRuntimeInstance(), {}); busyWorker.readyForWork(); const busyWorkerCounter = new WorkerStateCounter(busyWorker); - const idleWorker = pool.addWorker(trigger, new MockRuntimeInstance()); + const idleWorker = pool.addWorker(mockTrigger(triggerId), new MockRuntimeInstance(), {}); idleWorker.readyForWork(); const idleWorkerCounter = new WorkerStateCounter(idleWorker); @@ -234,13 +243,13 @@ describe("FunctionsRuntimeWorker", () => { it("refresh() kills idle workers and marks busy ones as finishing", async () => { const pool = new RuntimeWorkerPool(); - const trigger = "trigger1"; + const triggerId = "trigger1"; - const busyWorker = pool.addWorker(trigger, new MockRuntimeInstance()); + const busyWorker = pool.addWorker(mockTrigger(triggerId), new MockRuntimeInstance(), {}); busyWorker.readyForWork(); const busyWorkerCounter = new WorkerStateCounter(busyWorker); - const idleWorker = pool.addWorker(trigger, new MockRuntimeInstance()); + const idleWorker = pool.addWorker(mockTrigger(triggerId), new MockRuntimeInstance(), {}); idleWorker.readyForWork(); const idleWorkerCounter = new WorkerStateCounter(idleWorker); @@ -265,23 +274,23 @@ describe("FunctionsRuntimeWorker", () => { it("gives assigns all triggers to the same worker in sequential mode", async () => { const scope = nock("http://localhost").get("/").reply(200); - const trigger1 = "region-abc"; - const trigger2 = "region-def"; + const triggerId1 = "region-abc"; + const triggerId2 = "region-def"; const pool = new RuntimeWorkerPool(FunctionsExecutionMode.SEQUENTIAL); - const worker = pool.addWorker(trigger1, new MockRuntimeInstance()); + const worker = pool.addWorker(mockTrigger(triggerId1), new MockRuntimeInstance(), {}); worker.readyForWork(); const resp = httpMocks.createResponse({ eventEmitter: EventEmitter }); resp.on("end", () => { - expect(pool.readyForWork(trigger1)).to.be.false; - expect(pool.readyForWork(trigger2)).to.be.false; + expect(pool.readyForWork(triggerId1)).to.be.false; + expect(pool.readyForWork(triggerId2)).to.be.false; }); await worker.request({ method: "GET", path: "/" }, resp); scope.done(); - expect(pool.readyForWork(trigger1)).to.be.true; - expect(pool.readyForWork(trigger2)).to.be.true; + expect(pool.readyForWork(triggerId1)).to.be.true; + expect(pool.readyForWork(triggerId2)).to.be.true; }); }); }); From 7dc6c5b2f7b87b0b058860350c6c411dc3b11b0d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 31 Jan 2023 15:30:22 -0800 Subject: [PATCH 0782/1699] Add option to initialize python functions. (#5465) Add Python as an option for `firebase init functions`. The python choice is guarded behind the `pythonfunctions` experiment. You can enable the experiment with `firebase experiments:enable pythonfunctions`. This PR is a fork of https://github.com/firebase/firebase-tools/pull/4653. With this PR, https://github.com/firebase/firebase-tools/pull/4653 is now obsolete. --- src/deploy/functions/runtimes/python/index.ts | 31 +++++--- src/functions/python.ts | 2 + src/init/features/functions/index.ts | 7 ++ src/init/features/functions/python.ts | 71 +++++++++++++++++++ templates/init/functions/python/_gitignore | 0 templates/init/functions/python/main.py | 13 ++++ .../init/functions/python/requirements.txt | 1 + 7 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 src/init/features/functions/python.ts create mode 100644 templates/init/functions/python/_gitignore create mode 100644 templates/init/functions/python/main.py create mode 100644 templates/init/functions/python/requirements.txt diff --git a/src/deploy/functions/runtimes/python/index.ts b/src/deploy/functions/runtimes/python/index.ts index 4e4989f9df5..8825e383f5a 100644 --- a/src/deploy/functions/runtimes/python/index.ts +++ b/src/deploy/functions/runtimes/python/index.ts @@ -13,7 +13,7 @@ import { runWithVirtualEnv } from "../../../../functions/python"; import { FirebaseError } from "../../../../error"; import { Build } from "../../build"; -const LATEST_VERSION: runtimes.Runtime = "python310"; +export const LATEST_VERSION: runtimes.Runtime = "python310"; /** * Create a runtime delegate for the Python runtime, if applicable. @@ -37,6 +37,24 @@ export async function tryCreateDelegate( return Promise.resolve(new Delegate(context.projectId, context.sourceDir, runtime)); } +/** + * Get corresponding python binary name for a given runtime. + * + * By default, returns "python" + */ +export function getPythonBinary(runtime: runtimes.Runtime): string { + if (process.platform === "win32") { + // There is no easy way to get specific version of python executable in Windows. + return "python.exe"; + } + if (runtime === "python310") { + return "python3.10"; + } else if (runtime === "python311") { + return "python3.11"; + } + return "python"; +} + export class Delegate implements runtimes.RuntimeDelegate { public readonly name = "python"; constructor( @@ -82,16 +100,7 @@ export class Delegate implements runtimes.RuntimeDelegate { } getPythonBinary(): string { - if (process.platform === "win32") { - // There is no easy way to get specific version of python executable in Windows. - return "python.exe"; - } - if (this.runtime === "python310") { - return "python3.10"; - } else if (this.runtime === "python311") { - return "python3.11"; - } - return "python"; + return getPythonBinary(this.runtime); } validate(): Promise { diff --git a/src/functions/python.ts b/src/functions/python.ts index f37b821e1d8..63c1cb9870c 100644 --- a/src/functions/python.ts +++ b/src/functions/python.ts @@ -12,6 +12,7 @@ export function runWithVirtualEnv( commandAndArgs: string[], cwd: string, envs: Record, + spawnOpts: cp.SpawnOptions = {}, venvDir = DEFAULT_VENV_DIR ): cp.ChildProcess { const activateScriptPath = @@ -25,6 +26,7 @@ export function runWithVirtualEnv( shell: true, cwd, stdio: [/* stdin= */ "pipe", /* stdout= */ "pipe", /* stderr= */ "pipe", "pipe"], + ...spawnOpts, // Linting disabled since internal types expect NODE_ENV which does not apply to Python runtimes. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any env: envs as any, diff --git a/src/init/features/functions/index.ts b/src/init/features/functions/index.ts index f80875a5e67..1740104e467 100644 --- a/src/init/features/functions/index.ts +++ b/src/init/features/functions/index.ts @@ -13,6 +13,7 @@ import { assertUnique, } from "../../../functions/projectConfig"; import { FirebaseError } from "../../../error"; +import { isEnabled } from "../../../experiments"; const MAX_ATTEMPTS = 5; @@ -167,6 +168,12 @@ async function languageSetup(setup: any, config: Config): Promise { value: "typescript", }, ]; + if (isEnabled("pythonfunctions")) { + choices.push({ + name: "Python", + value: "python", + }); + } const language = await promptOnce({ type: "list", message: "What language would you like to use to write Cloud Functions?", diff --git a/src/init/features/functions/python.ts b/src/init/features/functions/python.ts new file mode 100644 index 00000000000..729b921fe26 --- /dev/null +++ b/src/init/features/functions/python.ts @@ -0,0 +1,71 @@ +import * as fs from "fs"; +import * as spawn from "cross-spawn"; +import * as path from "path"; + +import { Config } from "../../../config"; +import { getPythonBinary, LATEST_VERSION } from "../../../deploy/functions/runtimes/python"; +import { runWithVirtualEnv } from "../../../functions/python"; +import { promptOnce } from "../../../prompt"; + +const TEMPLATE_ROOT = path.resolve(__dirname, "../../../../templates/init/functions/python"); +const MAIN_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "main.py"), "utf8"); +const REQUIREMENTS_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "requirements.txt"), "utf8"); +const GITIGNORE_TEMPLATE = fs.readFileSync(path.join(TEMPLATE_ROOT, "_gitignore"), "utf8"); + +/** + * Create a Python Firebase Functions project. + */ +export async function setup(setup: any, config: Config): Promise { + await config.askWriteProjectFile( + `${setup.functions.source}/requirements.txt`, + REQUIREMENTS_TEMPLATE + ); + await config.askWriteProjectFile(`${setup.functions.source}/.gitignore`, GITIGNORE_TEMPLATE); + await config.askWriteProjectFile(`${setup.functions.source}/main.py`, MAIN_TEMPLATE); + + // Write the latest supported runtime version to the config. + config.set("functions.runtime", LATEST_VERSION); + // Add python specific ignores to config. + config.set("functions.ignore", ["venv", "__pycache__"]); + + // Setup VENV. + const venvProcess = spawn(getPythonBinary(LATEST_VERSION), ["-m", "venv", "venv"], { + shell: true, + cwd: config.path(setup.functions.source), + stdio: [/* stdin= */ "pipe", /* stdout= */ "pipe", /* stderr= */ "pipe", "pipe"], + }); + await new Promise((resolve, reject) => { + venvProcess.on("exit", resolve); + venvProcess.on("error", reject); + }); + + const install = await promptOnce({ + name: "install", + type: "confirm", + message: "Do you want to install dependencies now?", + default: true, + }); + if (install) { + // Update pip to support dependencies like pyyaml. + const upgradeProcess = runWithVirtualEnv( + ["pip3", "install", "--upgrade", "pip"], + config.path(setup.functions.source), + {}, + { stdio: ["inherit", "inherit", "inherit"] } + ); + await new Promise((resolve, reject) => { + upgradeProcess.on("exit", resolve); + upgradeProcess.on("error", reject); + }); + const installProcess = runWithVirtualEnv( + [getPythonBinary(LATEST_VERSION), "-m", "pip", "install", "-r", "requirements.txt"], + config.path(setup.functions.source), + {}, + { stdio: ["inherit", "inherit", "inherit"] } + ); + await new Promise((resolve, reject) => { + installProcess.on("exit", resolve); + installProcess.on("error", reject); + }); + } +} diff --git a/templates/init/functions/python/_gitignore b/templates/init/functions/python/_gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/templates/init/functions/python/main.py b/templates/init/functions/python/main.py new file mode 100644 index 00000000000..1d82099501f --- /dev/null +++ b/templates/init/functions/python/main.py @@ -0,0 +1,13 @@ +# Welcome to Cloud Functions for Firebase for Python! +# To get started, simply uncomment the below code or create your own. +# Deploy with `firebase deploy` + +from firebase_functions import https +from firebase_admin import initialize_app + +# initialize_app() +# +# +# @https.on_request() +# def on_request_example(req: https.Request) -> https.Response: +# return https.Response("Hello world!") \ No newline at end of file diff --git a/templates/init/functions/python/requirements.txt b/templates/init/functions/python/requirements.txt new file mode 100644 index 00000000000..b762b1f2877 --- /dev/null +++ b/templates/init/functions/python/requirements.txt @@ -0,0 +1 @@ +git+https://github.com/firebase/firebase-functions-python.git@main#egg=firebase-functions \ No newline at end of file From 416d03438e44ff462035e6fe4f8b15ee65d00edc Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 31 Jan 2023 15:43:31 -0800 Subject: [PATCH 0783/1699] Fix emulator support for cloudevent to better conform to production specification. (#5466) Fix few bugs in emulator support for cloudevent that was uncovered while testing python functions. 1. Pubsub emulator sends timestamps with nano-second precision, not mili-second precision as done in Production. Not usually a problem, but the the ISO timestamp parsing function for Python doesn't work with timestamps with nano-second precision. 2. `specversion` attribute of cloudevent should be `1.0` not `1` 3. Pubsub cloudevent should include snake_cased AND camelCase event attributes. Also including a small change to improve debugging experience when discovering python functions fail due to user error - e.g. python code includes syntax error. --- CHANGELOG.md | 1 + src/deploy/functions/runtimes/python/index.ts | 8 ++++++++ src/emulator/pubsubEmulator.ts | 17 +++++++++++++---- src/emulator/storage/cloudFunctions.ts | 6 +++--- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 349b92a3ed8..75601213bf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Refactor the way timeouts are enforced by the Functions Emulator (#5464) +- Fix bug where cloudevent emitted by various emulators didn't conform to spec (#5466) diff --git a/src/deploy/functions/runtimes/python/index.ts b/src/deploy/functions/runtimes/python/index.ts index 8825e383f5a..37a6c546fcd 100644 --- a/src/deploy/functions/runtimes/python/index.ts +++ b/src/deploy/functions/runtimes/python/index.ts @@ -129,6 +129,14 @@ export class Delegate implements runtimes.RuntimeDelegate { )} in ${this.sourceDir}` ); const childProcess = runWithVirtualEnv(args, this.sourceDir, envWithAdminPort); + childProcess.stdout?.on("data", (chunk: Buffer) => { + const chunkString = chunk.toString(); + logger.debug(`stdout: ${chunkString}`); + }); + childProcess.stderr?.on("data", (chunk: Buffer) => { + const chunkString = chunk.toString(); + logger.debug(`stderr: ${chunkString}`); + }); return Promise.resolve(async () => { await fetch(`http://127.0.0.1:${port}/__/quitquitquit`); const quitTimeout = setTimeout(() => { diff --git a/src/emulator/pubsubEmulator.ts b/src/emulator/pubsubEmulator.ts index a208407a930..1f8be4716b1 100644 --- a/src/emulator/pubsubEmulator.ts +++ b/src/emulator/pubsubEmulator.ts @@ -189,20 +189,29 @@ export class PubsubEmulator implements EmulatorInstance { topic: string, message: Message ): CloudEvent { + // Pubsub events from Pubsub Emulator include a date with nanoseconds. + // Prod Pubsub doesn't publish timestamp at that level of precision. Timestamp with nanosecond precision also + // are difficult to parse in languages other than Node.js (e.g. python). + const truncatedPublishTime = new Date(message.publishTime.getMilliseconds()).toISOString(); const data: MessagePublishedData = { message: { messageId: message.id, - publishTime: message.publishTime, + publishTime: truncatedPublishTime, attributes: message.attributes, orderingKey: message.orderingKey, data: message.data.toString("base64"), - }, + + // NOTE: We include camel_cased attributes since they also available and depended on by other runtimes + // like python. + message_id: message.id, + publish_time: truncatedPublishTime, + } as MessagePublishedData["message"], subscription: this.subscriptionForTopic.get(topic)!.name, }; return { - specversion: "1", + specversion: "1.0", id: uuid.v4(), - time: message.publishTime.toISOString(), + time: truncatedPublishTime, type: "google.cloud.pubsub.topic.v1.messagePublished", source: `//pubsub.googleapis.com/projects/${this.args.projectId}/topics/${topic}`, data, diff --git a/src/emulator/storage/cloudFunctions.ts b/src/emulator/storage/cloudFunctions.ts index 7308c67018e..bdd8aca82d3 100644 --- a/src/emulator/storage/cloudFunctions.ts +++ b/src/emulator/storage/cloudFunctions.ts @@ -106,7 +106,7 @@ export class StorageCloudFunctions { time = typeof data.updated === "string" ? data.updated : data.updated.toISOString(); } return { - specversion: "1", + specversion: "1.0", id: uuid.v4(), type: `google.cloud.storage.object.v1.${ceAction}`, source: `//storage.googleapis.com/projects/_/buckets/${objectMetadataPayload.bucket}/objects/${objectMetadataPayload.name}`, @@ -255,9 +255,9 @@ export interface ObjectMetadataPayload { * Customer-supplied encryption key. * * This object contains the following properties: - * * `encryptionAlgorithm` (`string|undefined`): The encryption algorithm that + * `encryptionAlgorithm` (`string|undefined`): The encryption algorithm that * was used. Always contains the value `AES256`. - * * `keySha256` (`string|undefined`): An RFC 4648 base64-encoded string of the + * `keySha256` (`string|undefined`): An RFC 4648 base64-encoded string of the * SHA256 hash of your encryption key. You can use this SHA256 hash to * uniquely identify the AES-256 encryption key required to decrypt the * object, which you must store securely. From 5c5c17964258da5dab2c559087bef9cea887b609 Mon Sep 17 00:00:00 2001 From: Yuangwang Date: Wed, 1 Feb 2023 14:32:18 -0500 Subject: [PATCH 0784/1699] Fix download name issue (#5478) * Fix download name issue * add changelog * fix tests * Update CHANGELOG.md --- CHANGELOG.md | 1 + .../conformance/firebase-js-sdk.test.ts | 3 +++ .../conformance/gcs.endpoints.test.ts | 2 +- src/emulator/storage/apis/shared.ts | 8 +++++++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75601213bf4..4f63e4531df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ +- Fix storage download name issue #5478 - Refactor the way timeouts are enforced by the Functions Emulator (#5464) - Fix bug where cloudevent emitted by various emulators didn't conform to spec (#5466) diff --git a/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts b/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts index d39f87b2241..7ed04244fcb 100644 --- a/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts +++ b/scripts/storage-emulator-integration/conformance/firebase-js-sdk.test.ts @@ -473,6 +473,9 @@ describe("Firebase Storage JavaScript SDK conformance tests", () => { await new Promise((resolve, reject) => { TEST_ENV.requestClient.get(downloadUrl, (response) => { let data = Buffer.alloc(0); + expect(response.headers["content-disposition"]).to.be.eql( + "attachment; filename=testFile" + ); response .on("data", (chunk) => { data = Buffer.concat([data, chunk]); diff --git a/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts b/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts index 11e57fd3d16..1b7c33e3ed4 100644 --- a/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts +++ b/scripts/storage-emulator-integration/conformance/gcs.endpoints.test.ts @@ -99,7 +99,7 @@ describe("GCS endpoint conformance tests", () => { .then((res) => res); expect(res.header["content-type"]).to.be.eql("application/octet-stream"); - expect(res.header["content-disposition"]).to.be.eql("attachment"); + expect(res.header["content-disposition"]).to.be.eql("attachment; filename=testFile"); }); }); diff --git a/src/emulator/storage/apis/shared.ts b/src/emulator/storage/apis/shared.ts index 8cdb0fd09d9..2a57ffc7cea 100644 --- a/src/emulator/storage/apis/shared.ts +++ b/src/emulator/storage/apis/shared.ts @@ -21,7 +21,13 @@ export function sendFileBytes( } res.setHeader("Accept-Ranges", "bytes"); res.setHeader("Content-Type", md.contentType || "application/octet-stream"); - res.setHeader("Content-Disposition", md.contentDisposition || "attachment"); + + // remove the folder name from the downloaded file name + const fileName = md.name.split("/").pop(); + res.setHeader( + "Content-Disposition", + `${md.contentDisposition || "attachment"}; filename=${fileName}` + ); if (didGunzip) { // Set to mirror server behavior and supress express's "content-length" header. res.setHeader("Transfer-Encoding", "chunked"); From 3023c17ade02afefabd759b0cf01e5837fe2eb3b Mon Sep 17 00:00:00 2001 From: Austin Crim Date: Wed, 1 Feb 2023 13:51:47 -0600 Subject: [PATCH 0785/1699] add `file:` dependency support for web frameworks (#5440) Co-authored-by: James Daniels --- CHANGELOG.md | 1 + src/frameworks/index.ts | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f63e4531df..65ed9650751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Fix storage download name issue #5478 - Refactor the way timeouts are enforced by the Functions Emulator (#5464) - Fix bug where cloudevent emitted by various emulators didn't conform to spec (#5466) +- Web frameworks deploys can once again bundle local NPM dependencies (#5440) diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 8b5bb7c8e08..645ba9ed239 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -1,4 +1,4 @@ -import { join, relative, extname } from "path"; +import { join, relative, extname, basename } from "path"; import { exit } from "process"; import { execSync, spawnSync } from "child_process"; import { readdirSync, statSync } from "fs"; @@ -511,6 +511,27 @@ export async function prepareFrameworks( packageJson.engines ||= {}; packageJson.engines.node ||= NODE_VERSION; + for (const [name, version] of Object.entries( + packageJson.dependencies as Record + )) { + if (version.startsWith("file:")) { + const path = version.replace(/^file:/, ""); + if (!(await pathExists(path))) continue; + const stats = await stat(path); + if (stats.isDirectory()) { + const result = spawnSync("npm", ["pack", relative(functionsDist, path)], { + cwd: functionsDist, + }); + if (!result.stdout) throw new Error(`Error running \`npm pack\` at ${path}`); + const filename = result.stdout.toString().trim(); + packageJson.dependencies[name] = `file:${filename}`; + } else { + const filename = basename(path); + await copyFile(path, join(functionsDist, filename)); + packageJson.dependencies[name] = `file:${filename}`; + } + } + } await writeFile(join(functionsDist, "package.json"), JSON.stringify(packageJson, null, 2)); // TODO do we add the append the local .env? From e8a59e64d85dee0273e58e1bc5eb86860e5f660d Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Wed, 1 Feb 2023 12:19:14 -0800 Subject: [PATCH 0786/1699] Upgrade the emulator suite UI to 1.11.3. (#5479) --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ed9650751..5deea7eb40e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ - Fix storage download name issue #5478 - Refactor the way timeouts are enforced by the Functions Emulator (#5464) - Fix bug where cloudevent emitted by various emulators didn't conform to spec (#5466) +- Upgrade the emulator suite UI to 1.11.3 to capture some bug fixes (#5479) - Web frameworks deploys can once again bundle local NPM dependencies (#5440) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 5681beb14f6..8be85567fbd 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -45,9 +45,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet ui: experiments.isEnabled("emulatoruisnapshot") ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" } : { - version: "1.11.2", - expectedSize: 3062873, - expectedChecksum: "fe7f668437d0e3c3b92677aaaade78bf", + version: "1.11.3", + expectedSize: 3062910, + expectedChecksum: "641b375b67f0d9aee11a5b769b074118", }, pubsub: { version: "0.7.1", From fa6ab5613e90bde07543e2957f113248a6540594 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Thu, 2 Feb 2023 13:54:19 -0500 Subject: [PATCH 0787/1699] Explicitly check for channel existence before creating it (#5443) --- src/deploy/functions/release/fabricator.ts | 7 +++++ .../functions/release/fabricator.spec.ts | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 9c13eaca235..68f0cf47ec5 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -316,6 +316,13 @@ export class Fabricator { await this.executor .run(async () => { try { + // eventarc.createChannel doesn't always return 409 when channel already exists. + // Ex. when channel exists and has active triggers the API will return 400 (bad + // request) with message saying something about active triggers. So instead of + // relying on 409 response we explicitly check for channel existense. + if ((await eventarc.getChannel(channel)) !== undefined) { + return; + } const op: { name: string } = await eventarc.createChannel({ name: channel }); return await poller.pollOperation({ ...eventarcPollerOptions, diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 6636b7371a1..1b34776d040 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -61,6 +61,7 @@ describe("Fabricator", () => { gcfv2.createFunction.rejects(new Error("unexpected gcfv2.createFunction")); gcfv2.updateFunction.rejects(new Error("unexpected gcfv2.updateFunction")); gcfv2.deleteFunction.rejects(new Error("unexpected gcfv2.deleteFunction")); + eventarc.getChannel.rejects(new Error("unexpected eventarc.getChannel")); eventarc.createChannel.rejects(new Error("unexpected eventarc.createChannel")); eventarc.deleteChannel.rejects(new Error("unexpected eventarc.deleteChannel")); eventarc.getChannel.rejects(new Error("unexpected eventarc.getChannel")); @@ -489,6 +490,31 @@ describe("Fabricator", () => { }); it("handles already existing eventarc channels", async () => { + eventarc.getChannel.resolves({ name: "channel" }); + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + + const ep = endpoint( + { + eventTrigger: { + eventType: "custom.test.event", + channel: "channel", + retry: false, + }, + }, + { + platform: "gcfv2", + } + ); + + await fab.createV2Function(ep); + expect(eventarc.getChannel).to.have.been.called; + expect(eventarc.createChannel).to.not.have.been.called; + expect(gcfv2.createFunction).to.have.been.called; + }); + + it("handles already existing eventarc channels (createChannel return 409)", async () => { + eventarc.getChannel.resolves(undefined); eventarc.createChannel.callsFake(({ name }) => { expect(name).to.equal("channel"); const err = new Error("Already exists"); @@ -518,6 +544,7 @@ describe("Fabricator", () => { it("creates channels if necessary", async () => { const channelName = "channel"; + eventarc.getChannel.resolves(undefined); eventarc.createChannel.callsFake(({ name }) => { expect(name).to.equal(channelName); return Promise.resolve({ @@ -554,6 +581,7 @@ describe("Fabricator", () => { }); it("wraps errors thrown while creating channels", async () => { + eventarc.getChannel.resolves(undefined); eventarc.createChannel.callsFake(() => { const err = new Error("🤷â€â™‚ï¸"); (err as any).status = 400; From 4ba170f75d53736893b598bb738dd1d14fd44018 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 2 Feb 2023 17:19:54 -0800 Subject: [PATCH 0788/1699] Cleaning up init templates for functions and extensions (#5485) * Cleaning up init templates * Cleaning up init templates for functions and extensions * add changelog entry --- CHANGELOG.md | 1 + src/commands/ext-dev-init.ts | 2 +- templates/extensions/CHANGELOG.md | 3 +- templates/extensions/extension.yaml | 2 +- templates/extensions/javascript/index.js | 20 ++++++------ .../extensions/javascript/package.lint.json | 8 +++-- .../extensions/javascript/package.nolint.json | 4 +-- templates/extensions/typescript/index.ts | 31 ++++++++++--------- .../extensions/typescript/package.lint.json | 9 ++++-- .../extensions/typescript/package.nolint.json | 6 ++-- templates/init/functions/javascript/_eslintrc | 15 +++++++-- .../functions/javascript/package.lint.json | 8 ++--- .../functions/javascript/package.nolint.json | 6 ++-- templates/init/functions/typescript/_eslintrc | 1 + .../functions/typescript/package.lint.json | 8 ++--- .../functions/typescript/package.nolint.json | 7 +++-- 16 files changed, 76 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5deea7eb40e..882e2bcec1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Fix bug where cloudevent emitted by various emulators didn't conform to spec (#5466) - Upgrade the emulator suite UI to 1.11.3 to capture some bug fixes (#5479) - Web frameworks deploys can once again bundle local NPM dependencies (#5440) +- Fixes a number of issues and outdated dependencies in templates for `init --only functions` and `ext:dev:init` diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index 86277dd87d0..285e8462ecb 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -68,7 +68,7 @@ export const command = new Command("ext:dev:init") } } - await npmDependencies.askInstallDependencies({}, config); + await npmDependencies.askInstallDependencies({ source: "functions" }, config); const welcome = fs.readFileSync(path.join(TEMPLATE_ROOT, lang, "WELCOME.md"), "utf8"); return logger.info("\n" + marked(welcome)); diff --git a/templates/extensions/CHANGELOG.md b/templates/extensions/CHANGELOG.md index 564babb0b61..6ba712636d1 100644 --- a/templates/extensions/CHANGELOG.md +++ b/templates/extensions/CHANGELOG.md @@ -1 +1,2 @@ -- Added support for publishing pre-release Extension versions. (#4804) \ No newline at end of file +## Version 0.0.1 +- Initial Version \ No newline at end of file diff --git a/templates/extensions/extension.yaml b/templates/extensions/extension.yaml index 4169806243b..99793b91683 100644 --- a/templates/extensions/extension.yaml +++ b/templates/extensions/extension.yaml @@ -40,7 +40,7 @@ resources: location: ${LOCATION} # httpsTrigger is used for an HTTP triggered function. httpsTrigger: {} - runtime: "nodejs12" + runtime: "nodejs16" # In the `params` field, set up your extension's user-configured parameters. # Learn more in the docs: https://firebase.google.com/docs/extensions/alpha/ref-extension-yaml#params-field diff --git a/templates/extensions/javascript/index.js b/templates/extensions/javascript/index.js index a75b9de5898..33837f73eae 100644 --- a/templates/extensions/javascript/index.js +++ b/templates/extensions/javascript/index.js @@ -1,22 +1,22 @@ /* - * This template contains a HTTP function that responds with a greeting when called - * - * Always use the FUNCTIONS HANDLER NAMESPACE - * when writing Cloud Functions for extensions. - * Learn more about the handler namespace in the docs + * This template contains a HTTP function that + * responds with a greeting when called * * Reference PARAMETERS in your functions code with: * `process.env.` - * Learn more about parameters in the docs + * Learn more about building extensions in the docs: + * https://firebase.google.com/docs/extensions/alpha/overview */ -const functions = require('firebase-functions'); +const functions = require("firebase-functions"); -exports.greetTheWorld = functions.handler.https.onRequest((req, res) => { - // Here we reference a user-provided parameter (its value is provided by the user during installation) +exports.greetTheWorld = functions.https.onRequest((req, res) => { + // Here we reference a user-provided parameter + // (its value is provided by the user during installation) const consumerProvidedGreeting = process.env.GREETING; - // And here we reference an auto-populated parameter (its value is provided by Firebase after installation) + // And here we reference an auto-populated parameter + // (its value is provided by Firebase after installation) const instanceId = process.env.EXT_INSTANCE_ID; const greeting = `${consumerProvidedGreeting} World from ${instanceId}`; diff --git a/templates/extensions/javascript/package.lint.json b/templates/extensions/javascript/package.lint.json index c5e9758d394..33de627354b 100644 --- a/templates/extensions/javascript/package.lint.json +++ b/templates/extensions/javascript/package.lint.json @@ -3,12 +3,14 @@ "description": "Greet the world", "main": "index.js", "dependencies": { - "firebase-admin": "^10.2.0", - "firebase-functions": "^3.21.0" + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.0" }, "devDependencies": { "eslint": "^8.15.1", - "eslint-plugin-promise": "^6.0.0" + "eslint-plugin-promise": "^6.0.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-import": "^2.25.4" }, "scripts": { "lint": "./node_modules/.bin/eslint --max-warnings=0 .." diff --git a/templates/extensions/javascript/package.nolint.json b/templates/extensions/javascript/package.nolint.json index 75918fd45a7..67867620a36 100644 --- a/templates/extensions/javascript/package.nolint.json +++ b/templates/extensions/javascript/package.nolint.json @@ -3,8 +3,8 @@ "description": "Greet the world", "main": "index.js", "dependencies": { - "firebase-admin": "^10.2.0", - "firebase-functions": "^3.21.0" + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.0" }, "private": true } \ No newline at end of file diff --git a/templates/extensions/typescript/index.ts b/templates/extensions/typescript/index.ts index 3c5151e7602..07fad5c91fe 100644 --- a/templates/extensions/typescript/index.ts +++ b/templates/extensions/typescript/index.ts @@ -1,25 +1,26 @@ /* - * This template contains a HTTP function that responds with a greeting when called - * - * Always use the FUNCTIONS HANDLER NAMESPACE - * when writing Cloud Functions for extensions. - * Learn more about the handler namespace in the docs + * This template contains a HTTP function that responds + * with a greeting when called * * Reference PARAMETERS in your functions code with: * `process.env.` - * Learn more about parameters in the docs + * Learn more about building extensions in the docs: + * https://firebase.google.com/docs/extensions/alpha/overview */ -import * as functions from 'firebase-functions'; +import * as functions from "firebase-functions"; -exports.greetTheWorld = functions.handler.https.onRequest((req, res) => { - // Here we reference a user-provided parameter (its value is provided by the user during installation) - const consumerProvidedGreeting = process.env.GREETING; +exports.greetTheWorld = functions.https.onRequest( + (req: functions.Request, res: functions.Response) => { + // Here we reference a user-provided parameter + // (its value is provided by the user during installation) + const consumerProvidedGreeting = process.env.GREETING; - // And here we reference an auto-populated parameter (its value is provided by Firebase after installation) - const instanceId = process.env.EXT_INSTANCE_ID; + // And here we reference an auto-populated parameter + // (its value is provided by Firebase after installation) + const instanceId = process.env.EXT_INSTANCE_ID; - const greeting = `${consumerProvidedGreeting} World from ${instanceId}`; + const greeting = `${consumerProvidedGreeting} World from ${instanceId}`; - res.send(greeting); -}); + res.send(greeting); + }); diff --git a/templates/extensions/typescript/package.lint.json b/templates/extensions/typescript/package.lint.json index 3bb412dbc11..5142ed474f7 100644 --- a/templates/extensions/typescript/package.lint.json +++ b/templates/extensions/typescript/package.lint.json @@ -7,13 +7,16 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^10.2.0", - "firebase-functions": "^3.21.0" + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.0" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.12.0", + "@typescript-eslint/parser": "^5.12.0", "eslint": "^8.15.1", "eslint-plugin-import": "^2.26.0", - "typescript": "^4.6.4" + "eslint-config-google": "^0.14.0", + "typescript": "^4.9.0" }, "private": true } \ No newline at end of file diff --git a/templates/extensions/typescript/package.nolint.json b/templates/extensions/typescript/package.nolint.json index 3dfb7de37fc..2d524dd3b17 100644 --- a/templates/extensions/typescript/package.nolint.json +++ b/templates/extensions/typescript/package.nolint.json @@ -6,11 +6,11 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^9.8.0", - "firebase-functions": "^3.14.1" + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.0" }, "devDependencies": { - "typescript": "^3.8.0" + "typescript": "^4.9.0" }, "private": true } diff --git a/templates/init/functions/javascript/_eslintrc b/templates/init/functions/javascript/_eslintrc index 973030b23cc..e8a4eed9395 100644 --- a/templates/init/functions/javascript/_eslintrc +++ b/templates/init/functions/javascript/_eslintrc @@ -1,5 +1,4 @@ module.exports = { - root: true, env: { es6: true, node: true, @@ -9,6 +8,18 @@ module.exports = { "google", ], rules: { - quotes: ["error", "double"], + "no-restricted-globals": ["error", "name", "length"], + "prefer-arrow-callback": "error", + "quotes": ["error", "double", {"allowTemplateLiterals": true}], }, + overrides: [ + { + files: ["*.spec.*"], + env: { + mocha: true, + }, + rules: {}, + }, + ], + globals: {}, }; diff --git a/templates/init/functions/javascript/package.lint.json b/templates/init/functions/javascript/package.lint.json index 70089faf002..240104ba573 100644 --- a/templates/init/functions/javascript/package.lint.json +++ b/templates/init/functions/javascript/package.lint.json @@ -14,13 +14,13 @@ }, "main": "index.js", "dependencies": { - "firebase-admin": "^10.0.2", - "firebase-functions": "^3.18.0" + "firebase-admin": "^111.5.0", + "firebase-functions": "^4.2.0" }, "devDependencies": { - "eslint": "^8.9.0", + "eslint": "^8.15.0", "eslint-config-google": "^0.14.0", - "firebase-functions-test": "^0.2.0" + "firebase-functions-test": "^3.0.0" }, "private": true } diff --git a/templates/init/functions/javascript/package.nolint.json b/templates/init/functions/javascript/package.nolint.json index 148afc6a8d6..f2e5ffbb8cf 100644 --- a/templates/init/functions/javascript/package.nolint.json +++ b/templates/init/functions/javascript/package.nolint.json @@ -13,11 +13,11 @@ }, "main": "index.js", "dependencies": { - "firebase-admin": "^10.0.2", - "firebase-functions": "^3.18.0" + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.0" }, "devDependencies": { - "firebase-functions-test": "^0.2.0" + "firebase-functions-test": "^3.0.0" }, "private": true } diff --git a/templates/init/functions/typescript/_eslintrc b/templates/init/functions/typescript/_eslintrc index ff21277afae..4ed894186d0 100644 --- a/templates/init/functions/typescript/_eslintrc +++ b/templates/init/functions/typescript/_eslintrc @@ -27,5 +27,6 @@ module.exports = { rules: { "quotes": ["error", "double"], "import/no-unresolved": 0, + "indent": ["error", 2] }, }; diff --git a/templates/init/functions/typescript/package.lint.json b/templates/init/functions/typescript/package.lint.json index a85202ba068..a452cd917f4 100644 --- a/templates/init/functions/typescript/package.lint.json +++ b/templates/init/functions/typescript/package.lint.json @@ -15,8 +15,8 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^10.0.2", - "firebase-functions": "^3.18.0" + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.0" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.12.0", @@ -24,8 +24,8 @@ "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", "eslint-plugin-import": "^2.25.4", - "firebase-functions-test": "^0.2.0", - "typescript": "^4.5.4" + "firebase-functions-test": "^3.0.0", + "typescript": "^4.9.0" }, "private": true } diff --git a/templates/init/functions/typescript/package.nolint.json b/templates/init/functions/typescript/package.nolint.json index 0b6edcb5ebe..23646a3344f 100644 --- a/templates/init/functions/typescript/package.nolint.json +++ b/templates/init/functions/typescript/package.nolint.json @@ -14,11 +14,12 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^10.2.0", - "firebase-functions": "^3.21.0" + "firebase-admin": "^11.5.0", + "firebase-functions": "^4.2.0" }, "devDependencies": { - "typescript": "^4.6.4" + "typescript": "^4.9.0", + "firebase-functions-test": "^3.0.0" }, "private": true } \ No newline at end of file From 2d5052d3d7027ca455741c00b95516b770f24140 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 6 Feb 2023 08:39:45 -0800 Subject: [PATCH 0789/1699] replace @manifoldco/swagger-to-ts with openapi-typescript and regenerate schema (#5488) --- npm-shrinkwrap.json | 621 +-- package.json | 5 +- scripts/gen-auth-api-spec.ts | 14 +- src/emulator/auth/apiSpec.ts | 22 +- src/emulator/auth/schema.ts | 8140 ++++++++++++++++++++++++++-------- 5 files changed, 6606 insertions(+), 2196 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a296c4bfbf8..040f82d12c0 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -77,7 +77,6 @@ "@angular-devkit/architect": "^0.1402.2", "@angular-devkit/core": "^14.2.2", "@google/events": "^5.1.1", - "@manifoldco/swagger-to-ts": "^2.0.0", "@types/archiver": "^5.1.0", "@types/async-lock": "^1.1.5", "@types/body-parser": "^1.17.0", @@ -90,7 +89,6 @@ "@types/cross-spawn": "^6.0.1", "@types/express": "^4.17.0", "@types/express-serve-static-core": "^4.17.8", - "@types/firebase": "^3.2.1", "@types/fs-extra": "^9.0.13", "@types/glob": "^7.1.1", "@types/inquirer": "^8.1.3", @@ -104,6 +102,7 @@ "@types/multer": "^1.4.3", "@types/node": "^14.18.18", "@types/node-fetch": "^2.5.12", + "@types/prettier": "^2.7.2", "@types/progress": "^2.0.3", "@types/puppeteer": "^5.4.2", "@types/react": "^18.0.20", @@ -116,6 +115,7 @@ "@types/sinon-chai": "^3.2.2", "@types/stream-json": "^1.7.2", "@types/supertest": "^2.0.12", + "@types/swagger2openapi": "^7.0.0", "@types/tar": "^6.1.1", "@types/tcp-port-used": "^1.0.1", "@types/tmp": "^0.2.3", @@ -145,6 +145,7 @@ "node-mocks-http": "^1.11.0", "nyc": "^15.1.0", "openapi-merge": "^1.0.23", + "openapi-typescript": "^4.5.0", "prettier": "^2.5.1", "proxy": "^1.0.2", "puppeteer": "^19.0.0", @@ -2200,96 +2201,6 @@ "node": ">=v12.0.0" } }, - "node_modules/@manifoldco/swagger-to-ts": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@manifoldco/swagger-to-ts/-/swagger-to-ts-2.1.0.tgz", - "integrity": "sha512-IH0FAHhwWHR3Gs3rnVHNEscZujGn+K6/2Zu5cWfZre3Vz2tx1SvvJKEbSM89MztfDDRjOpb+6pQD/vqdEoTBVg==", - "deprecated": "This package has changed to openapi-typescript", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "js-yaml": "^3.13.1", - "meow": "^7.0.0", - "prettier": "^2.0.5" - }, - "bin": { - "swagger-to-ts": "bin/cli.js" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@manifoldco/swagger-to-ts/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@manifoldco/swagger-to-ts/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": 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/@manifoldco/swagger-to-ts/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==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@manifoldco/swagger-to-ts/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==", - "dev": true - }, - "node_modules/@manifoldco/swagger-to-ts/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/@manifoldco/swagger-to-ts/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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@next/env": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.2.tgz", @@ -2955,16 +2866,6 @@ "@types/range-parser": "*" } }, - "node_modules/@types/firebase": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/firebase/-/firebase-3.2.1.tgz", - "integrity": "sha512-G8XgHMu2jHlElfc2xVNaYP50F0qrqeTCjgeG1v5b4SRwWG4XKC4fCuEdVZuZaMRmVygcnbRZBAo9O7RsDvmkGQ==", - "deprecated": "This is a stub types definition for Firebase API (https://www.firebase.com/docs/javascript/firebase). Firebase API provides its own type definitions, so you don't need @types/firebase installed!", - "dev": true, - "dependencies": { - "firebase": "*" - } - }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -3157,6 +3058,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, "node_modules/@types/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/progress/-/progress-2.0.3.tgz", @@ -3340,6 +3247,16 @@ "@types/superagent": "*" } }, + "node_modules/@types/swagger2openapi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/swagger2openapi/-/swagger2openapi-7.0.0.tgz", + "integrity": "sha512-jbjunFpBQqbYt9JZYPDe1G9TkTVzQ8MqT1z7qMq/f7EZzdoA/G8WCZt8dr5gLkATkaE2n8FX7HlrBUTNyYRAJA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "openapi-types": "^12.1.0" + } + }, "node_modules/@types/tar": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.1.tgz", @@ -8026,6 +7943,12 @@ "node": ">=4" } }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true + }, "node_modules/globby": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", @@ -8046,6 +7969,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "node_modules/google-auth-library": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", @@ -10061,6 +9990,15 @@ "graceful-fs": "^4.1.9" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", @@ -10625,56 +10563,6 @@ "node": ">= 0.6" } }, - "node_modules/meow": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", - "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -11890,6 +11778,158 @@ "ts-is-present": "^1.1.1" } }, + "node_modules/openapi-types": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.0.tgz", + "integrity": "sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA==", + "dev": true + }, + "node_modules/openapi-typescript": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-4.5.0.tgz", + "integrity": "sha512-++gWZLTKmbZP608JHMerllAs84HzULWfVjfH7otkWBLrKxUvzHMFqI6R4JSW1LoNDZnS4KKiRTZW66Fxyp6z4Q==", + "dev": true, + "dependencies": { + "hosted-git-info": "^3.0.8", + "js-yaml": "^4.1.0", + "kleur": "^4.1.4", + "meow": "^9.0.0", + "mime": "^3.0.0", + "node-fetch": "^2.6.6", + "prettier": "^2.5.1", + "slash": "^3.0.0", + "tiny-glob": "^0.2.9" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "engines": { + "node": ">= 12.0.0", + "npm": ">= 7.0.0" + } + }, + "node_modules/openapi-typescript/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/openapi-typescript/node_modules/hosted-git-info": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openapi-typescript/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/openapi-typescript/node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-typescript/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/openapi-typescript/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openapi-typescript/node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openapi-typescript/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openapi-typescript/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openapi3-ts": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.1.tgz", @@ -14551,6 +14591,16 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -17461,70 +17511,6 @@ "lodash": "^4.17.21" } }, - "@manifoldco/swagger-to-ts": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@manifoldco/swagger-to-ts/-/swagger-to-ts-2.1.0.tgz", - "integrity": "sha512-IH0FAHhwWHR3Gs3rnVHNEscZujGn+K6/2Zu5cWfZre3Vz2tx1SvvJKEbSM89MztfDDRjOpb+6pQD/vqdEoTBVg==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "js-yaml": "^3.13.1", - "meow": "^7.0.0", - "prettier": "^2.0.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "dev": true - }, - "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 - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "@next/env": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.2.tgz", @@ -18017,15 +18003,6 @@ "@types/range-parser": "*" } }, - "@types/firebase": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/firebase/-/firebase-3.2.1.tgz", - "integrity": "sha512-G8XgHMu2jHlElfc2xVNaYP50F0qrqeTCjgeG1v5b4SRwWG4XKC4fCuEdVZuZaMRmVygcnbRZBAo9O7RsDvmkGQ==", - "dev": true, - "requires": { - "firebase": "*" - } - }, "@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -18219,6 +18196,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, "@types/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/progress/-/progress-2.0.3.tgz", @@ -18401,6 +18384,16 @@ "@types/superagent": "*" } }, + "@types/swagger2openapi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/swagger2openapi/-/swagger2openapi-7.0.0.tgz", + "integrity": "sha512-jbjunFpBQqbYt9JZYPDe1G9TkTVzQ8MqT1z7qMq/f7EZzdoA/G8WCZt8dr5gLkATkaE2n8FX7HlrBUTNyYRAJA==", + "dev": true, + "requires": { + "@types/node": "*", + "openapi-types": "^12.1.0" + } + }, "@types/tar": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.1.tgz", @@ -21850,6 +21843,12 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true + }, "globby": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", @@ -21864,6 +21863,12 @@ "slash": "^3.0.0" } }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "google-auth-library": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", @@ -23433,6 +23438,12 @@ "graceful-fs": "^4.1.9" } }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true + }, "kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", @@ -23900,43 +23911,6 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "meow": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", - "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - }, - "dependencies": { - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -24851,6 +24825,119 @@ "ts-is-present": "^1.1.1" } }, + "openapi-types": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.0.tgz", + "integrity": "sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA==", + "dev": true + }, + "openapi-typescript": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-4.5.0.tgz", + "integrity": "sha512-++gWZLTKmbZP608JHMerllAs84HzULWfVjfH7otkWBLrKxUvzHMFqI6R4JSW1LoNDZnS4KKiRTZW66Fxyp6z4Q==", + "dev": true, + "requires": { + "hosted-git-info": "^3.0.8", + "js-yaml": "^4.1.0", + "kleur": "^4.1.4", + "meow": "^9.0.0", + "mime": "^3.0.0", + "node-fetch": "^2.6.6", + "prettier": "^2.5.1", + "slash": "^3.0.0", + "tiny-glob": "^0.2.9" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "hosted-git-info": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + } + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true + }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + } + } + }, "openapi3-ts": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.1.tgz", @@ -26903,6 +26990,16 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "requires": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", diff --git a/package.json b/package.json index 86433758444..0bf207754d4 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,6 @@ "@angular-devkit/architect": "^0.1402.2", "@angular-devkit/core": "^14.2.2", "@google/events": "^5.1.1", - "@manifoldco/swagger-to-ts": "^2.0.0", "@types/archiver": "^5.1.0", "@types/async-lock": "^1.1.5", "@types/body-parser": "^1.17.0", @@ -172,7 +171,6 @@ "@types/cross-spawn": "^6.0.1", "@types/express": "^4.17.0", "@types/express-serve-static-core": "^4.17.8", - "@types/firebase": "^3.2.1", "@types/fs-extra": "^9.0.13", "@types/glob": "^7.1.1", "@types/inquirer": "^8.1.3", @@ -186,6 +184,7 @@ "@types/multer": "^1.4.3", "@types/node": "^14.18.18", "@types/node-fetch": "^2.5.12", + "@types/prettier": "^2.7.2", "@types/progress": "^2.0.3", "@types/puppeteer": "^5.4.2", "@types/react": "^18.0.20", @@ -198,6 +197,7 @@ "@types/sinon-chai": "^3.2.2", "@types/stream-json": "^1.7.2", "@types/supertest": "^2.0.12", + "@types/swagger2openapi": "^7.0.0", "@types/tar": "^6.1.1", "@types/tcp-port-used": "^1.0.1", "@types/tmp": "^0.2.3", @@ -227,6 +227,7 @@ "node-mocks-http": "^1.11.0", "nyc": "^15.1.0", "openapi-merge": "^1.0.23", + "openapi-typescript": "^4.5.0", "prettier": "^2.5.1", "proxy": "^1.0.2", "puppeteer": "^19.0.0", diff --git a/scripts/gen-auth-api-spec.ts b/scripts/gen-auth-api-spec.ts index d5d9cc54509..94bcf51e823 100644 --- a/scripts/gen-auth-api-spec.ts +++ b/scripts/gen-auth-api-spec.ts @@ -14,13 +14,10 @@ import * as https from "https"; import { resolve } from "path"; import { writeFileSync } from "fs"; -// @ts-ignore import * as prettier from "prettier"; -// @ts-ignore import * as swagger2openapi from "swagger2openapi"; -// @ts-ignore import { merge, isErrorResult } from "openapi-merge"; -import swaggerToTS from "@manifoldco/swagger-to-ts"; +import openapiTS from "openapi-typescript"; // Convert Google API Discovery format to OpenAPI using this library in order // to use OpenAPI tooling, recommended by https://googleapis.github.io/#openapi. @@ -72,8 +69,9 @@ async function main(): Promise { writeFileSync(specFile, prettier.format(specContent, { ...prettierOptions, filepath: specFile })); // Also generate TypeScript definitions for use in implementation. - const prettierConfig = resolve(__dirname, "../.prettierrc"); - const defsContent = header + swaggerToTS(merged.output as any, { prettierConfig }); + const prettierConfig = resolve(__dirname, "../.prettierrc.js"); + const output = await openapiTS(merged.output as any, { prettierConfig }); + const defsContent = header + output; writeFileSync(resolve(__dirname, "../src/emulator/auth/schema.ts"), defsContent); } @@ -116,10 +114,10 @@ async function toOpenapi3(discovery: Discovery): Promise { // tools offer one single API call for the entire conversion, but perform // indirect conversion under the hood. We'll just do it explicitly and that // also gives us more control (such as .setStrict above) and less deps. - const swagger = await googleDiscoveryToSwagger.convert(discovery); + const swagger: any = await googleDiscoveryToSwagger.convert(discovery); const result = await swagger2openapi.convertObj(swagger, {}); const openapi3 = result.openapi; - openapi3.servers.forEach((server: { url: string }) => { + openapi3.servers?.forEach((server: { url: string }) => { // Server URL should not end with slash since it is prefixed to paths. server.url = server.url.replace(/\/$/, ""); }); diff --git a/src/emulator/auth/apiSpec.ts b/src/emulator/auth/apiSpec.ts index c62f8f2387e..b3329728b65 100644 --- a/src/emulator/auth/apiSpec.ts +++ b/src/emulator/auth/apiSpec.ts @@ -5413,6 +5413,11 @@ export default { description: "Request message for SignInWithGameCenter", properties: { displayName: { description: "The user's Game Center display name.", type: "string" }, + gamePlayerId: { + description: + "The user's Game Center game player ID. A unique identifier for a player of the game. https://developer.apple.com/documentation/gamekit/gkplayer/3113960-gameplayerid", + type: "string", + }, idToken: { description: "A valid ID token for an Identity Platform account. If present, this request will link the Game Center player ID to the account represented by this ID token.", @@ -5432,6 +5437,11 @@ export default { description: "Required. The verification signature data generated by Apple.", type: "string", }, + teamPlayerId: { + description: + "The user's Game Center team player ID. A unique identifier for a player of all the games that you distribute using your developer account. https://developer.apple.com/documentation/gamekit/gkplayer/3174857-teamplayerid", + type: "string", + }, tenantId: { description: "The ID of the Identity Platform tenant the user is signing in to.", type: "string", @@ -5454,6 +5464,11 @@ export default { format: "int64", type: "string", }, + gamePlayerId: { + description: + "The user's Game Center game player ID. A unique identifier for a player of the game. https://developer.apple.com/documentation/gamekit/gkplayer/3113960-gameplayerid", + type: "string", + }, idToken: { description: "An Identity Platform ID token for the authenticated user.", type: "string", @@ -5471,6 +5486,11 @@ export default { description: "An Identity Platform refresh token for the authenticated user.", type: "string", }, + teamPlayerId: { + description: + "The user's Game Center team player ID. A unique identifier for a player of all the games that you distribute using your developer account. https://developer.apple.com/documentation/gamekit/gkplayer/3174857-teamplayerid", + type: "string", + }, }, type: "object", }, @@ -7436,7 +7456,7 @@ export default { condition: { $ref: "#/components/schemas/GoogleTypeExpr" }, members: { description: - "Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. Does not include identities that come from external identity providers (IdPs) through identity federation. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a Google service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `serviceAccount:{projectid}.svc.id.goog[{namespace}/{kubernetes-sa}]`: An identifier for a [Kubernetes service account](https://cloud.google.com/kubernetes-engine/docs/how-to/kubernetes-service-accounts). For example, `my-project.svc.id.goog[my-namespace/my-kubernetes-sa]`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. ", + "Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. Does not include identities that come from external identity providers (IdPs) through identity federation. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a Google service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `serviceAccount:{projectid}.svc.id.goog[{namespace}/{kubernetes-sa}]`: An identifier for a [Kubernetes service account](https://cloud.google.com/kubernetes-engine/docs/how-to/kubernetes-service-accounts). For example, `my-project.svc.id.goog[my-namespace/my-kubernetes-sa]`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding.", items: { type: "string" }, type: "array", }, diff --git a/src/emulator/auth/schema.ts b/src/emulator/auth/schema.ts index fa8d7a47174..a115c37ae2b 100644 --- a/src/emulator/auth/schema.ts +++ b/src/emulator/auth/schema.ts @@ -3,422 +3,2099 @@ /* eslint-disable */ /** - * This file was auto-generated by swagger-to-ts. + * This file was auto-generated by openapi-typescript. * Do not make direct changes to the file. */ +export interface paths { + "/v1/accounts:createAuthUri": { + /** If an email identifier is specified, checks and returns if any user account is registered with the email. If there is a registered account, fetches all providers associated with the account's email. If the provider ID of an Identity Provider (IdP) is specified, creates an authorization URI for the IdP. The user can be directed to this URI to sign in with the IdP. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.createAuthUri"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:delete": { + /** Deletes a user's account. */ + post: operations["identitytoolkit.accounts.delete"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:issueSamlResponse": { + /** Experimental */ + post: operations["identitytoolkit.accounts.issueSamlResponse"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:lookup": { + /** Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria. */ + post: operations["identitytoolkit.accounts.lookup"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:resetPassword": { + /** Resets the password of an account either using an out-of-band code generated by sendOobCode or by specifying the email and password of the account to be modified. Can also check the purpose of an out-of-band code without consuming it. */ + post: operations["identitytoolkit.accounts.resetPassword"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:sendOobCode": { + /** Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it. */ + post: operations["identitytoolkit.accounts.sendOobCode"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:sendVerificationCode": { + /** Sends a SMS verification code for phone number sign-in. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.sendVerificationCode"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:signInWithCustomToken": { + /** Signs in or signs up a user by exchanging a custom Auth token. Upon a successful sign-in or sign-up, a new Identity Platform ID token and refresh token are issued for the user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.signInWithCustomToken"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:signInWithEmailLink": { + /** Signs in or signs up a user with a out-of-band code from an email link. If a user does not exist with the given email address, a user record will be created. If the sign-in succeeds, an Identity Platform ID and refresh token are issued for the authenticated user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.signInWithEmailLink"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:signInWithGameCenter": { + /** Signs in or signs up a user with iOS Game Center credentials. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. The bundle ID is required in the request header as `x-ios-bundle-identifier`. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.signInWithGameCenter"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:signInWithIdp": { + /** Signs in or signs up a user using credentials from an Identity Provider (IdP). This is done by manually providing an IdP credential, or by providing the authorization response obtained via the authorization request from CreateAuthUri. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. A new Identity Platform user account will be created if the user has not previously signed in to the IdP with the same account. In addition, when the "One account per email address" setting is enabled, there should not be an existing Identity Platform user account with the same email address for a new user account to be created. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.signInWithIdp"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:signInWithPassword": { + /** Signs in a user with email and password. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.signInWithPassword"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:signInWithPhoneNumber": { + /** Completes a phone number authentication attempt. If a user already exists with the given phone number, an ID token is minted for that user. Otherwise, a new user is created and associated with the phone number. This method may also be used to link a phone number to an existing user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.signInWithPhoneNumber"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:signUp": { + /** Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.signUp"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:update": { + /** Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported. */ + post: operations["identitytoolkit.accounts.update"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/accounts:verifyIosClient": { + /** Verifies an iOS client is a real iOS device. If the request is valid, a receipt will be sent in the response and a secret will be sent via Apple Push Notification Service. The client should send both of them back to certain Identity Platform APIs in a later call (for example, /accounts:sendVerificationCode), in order to verify the client. The bundle ID is required in the request header as `x-ios-bundle-identifier`. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.accounts.verifyIosClient"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts": { + /** Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.projects.accounts"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}:createSessionCookie": { + /** Creates a session cookie for the given Identity Platform ID token. The session cookie is used by the client to preserve the user's login state. */ + post: operations["identitytoolkit.projects.createSessionCookie"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}:queryAccounts": { + /** Looks up user accounts within a project or a tenant based on conditions in the request. */ + post: operations["identitytoolkit.projects.queryAccounts"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts:batchCreate": { + /** Uploads multiple accounts into the Google Cloud project. If there is a problem uploading one or more of the accounts, the rest will be uploaded, and a list of the errors will be returned. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ + post: operations["identitytoolkit.projects.accounts.batchCreate"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts:batchDelete": { + /** Batch deletes multiple accounts. For accounts that fail to be deleted, error info is contained in the response. The method ignores accounts that do not exist or are duplicated in the request. This method requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). */ + post: operations["identitytoolkit.projects.accounts.batchDelete"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts:batchGet": { + /** Download account information for all accounts on the project in a paginated manner. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control).. Furthermore, additional permissions are needed to get password hash, password salt, and password version from accounts; otherwise these fields are redacted. */ + get: operations["identitytoolkit.projects.accounts.batchGet"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts:delete": { + /** Deletes a user's account. */ + post: operations["identitytoolkit.projects.accounts.delete"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts:lookup": { + /** Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria. */ + post: operations["identitytoolkit.projects.accounts.lookup"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts:query": { + /** Looks up user accounts within a project or a tenant based on conditions in the request. */ + post: operations["identitytoolkit.projects.accounts.query"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts:sendOobCode": { + /** Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it. */ + post: operations["identitytoolkit.projects.accounts.sendOobCode"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/accounts:update": { + /** Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported. */ + post: operations["identitytoolkit.projects.accounts.update"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts": { + /** Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + post: operations["identitytoolkit.projects.tenants.accounts"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}:createSessionCookie": { + /** Creates a session cookie for the given Identity Platform ID token. The session cookie is used by the client to preserve the user's login state. */ + post: operations["identitytoolkit.projects.tenants.createSessionCookie"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:batchCreate": { + /** Uploads multiple accounts into the Google Cloud project. If there is a problem uploading one or more of the accounts, the rest will be uploaded, and a list of the errors will be returned. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ + post: operations["identitytoolkit.projects.tenants.accounts.batchCreate"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:batchDelete": { + /** Batch deletes multiple accounts. For accounts that fail to be deleted, error info is contained in the response. The method ignores accounts that do not exist or are duplicated in the request. This method requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). */ + post: operations["identitytoolkit.projects.tenants.accounts.batchDelete"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:batchGet": { + /** Download account information for all accounts on the project in a paginated manner. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control).. Furthermore, additional permissions are needed to get password hash, password salt, and password version from accounts; otherwise these fields are redacted. */ + get: operations["identitytoolkit.projects.tenants.accounts.batchGet"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:delete": { + /** Deletes a user's account. */ + post: operations["identitytoolkit.projects.tenants.accounts.delete"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:lookup": { + /** Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria. */ + post: operations["identitytoolkit.projects.tenants.accounts.lookup"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:query": { + /** Looks up user accounts within a project or a tenant based on conditions in the request. */ + post: operations["identitytoolkit.projects.tenants.accounts.query"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:sendOobCode": { + /** Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it. */ + post: operations["identitytoolkit.projects.tenants.accounts.sendOobCode"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts:update": { + /** Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported. */ + post: operations["identitytoolkit.projects.tenants.accounts.update"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/projects": { + /** Gets a project's public Identity Toolkit configuration. (Legacy) This method also supports authenticated calls from a developer to retrieve non-public configuration. */ + get: operations["identitytoolkit.getProjects"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/recaptchaParams": { + /** Gets parameters needed for generating a reCAPTCHA challenge. */ + get: operations["identitytoolkit.getRecaptchaParams"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/sessionCookiePublicKeys": { + /** Retrieves the set of public keys of the session cookie JSON Web Token (JWT) signer that can be used to validate the session cookie created through createSessionCookie. */ + get: operations["identitytoolkit.getSessionCookiePublicKeys"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/accounts/mfaEnrollment:finalize": { + /** Finishes enrolling a second factor for the user. */ + post: operations["identitytoolkit.accounts.mfaEnrollment.finalize"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/accounts/mfaEnrollment:start": { + /** Step one of the MFA enrollment process. In SMS case, this sends an SMS verification code to the user. */ + post: operations["identitytoolkit.accounts.mfaEnrollment.start"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/accounts/mfaEnrollment:withdraw": { + /** Revokes one second factor from the enrolled second factors for an account. */ + post: operations["identitytoolkit.accounts.mfaEnrollment.withdraw"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/accounts/mfaSignIn:finalize": { + /** Verifies the MFA challenge and performs sign-in */ + post: operations["identitytoolkit.accounts.mfaSignIn.finalize"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/accounts/mfaSignIn:start": { + /** Sends the MFA challenge */ + post: operations["identitytoolkit.accounts.mfaSignIn.start"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/defaultSupportedIdps": { + /** List all default supported Idps. */ + get: operations["identitytoolkit.defaultSupportedIdps.list"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/config": { + /** Retrieve an Identity Toolkit project configuration. */ + get: operations["identitytoolkit.projects.getConfig"]; + /** Update an Identity Toolkit project configuration. */ + patch: operations["identitytoolkit.projects.updateConfig"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/defaultSupportedIdpConfigs": { + /** List all default supported Idp configurations for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.defaultSupportedIdpConfigs.list"]; + /** Create a default supported Idp configuration for an Identity Toolkit project. */ + post: operations["identitytoolkit.projects.defaultSupportedIdpConfigs.create"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/defaultSupportedIdpConfigs/{defaultSupportedIdpConfigsId}": { + /** Retrieve a default supported Idp configuration for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.defaultSupportedIdpConfigs.get"]; + /** Delete a default supported Idp configuration for an Identity Toolkit project. */ + delete: operations["identitytoolkit.projects.defaultSupportedIdpConfigs.delete"]; + /** Update a default supported Idp configuration for an Identity Toolkit project. */ + patch: operations["identitytoolkit.projects.defaultSupportedIdpConfigs.patch"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/identityPlatform:initializeAuth": { + /** Initialize Identity Platform for a Cloud project. Identity Platform is an end-to-end authentication system for third-party users to access your apps and services. These could include mobile/web apps, games, APIs and beyond. This is the publicly available variant of EnableIdentityPlatform that is only available to billing-enabled projects. */ + post: operations["identitytoolkit.projects.identityPlatform.initializeAuth"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/inboundSamlConfigs": { + /** List all inbound SAML configurations for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.inboundSamlConfigs.list"]; + /** Create an inbound SAML configuration for an Identity Toolkit project. */ + post: operations["identitytoolkit.projects.inboundSamlConfigs.create"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/inboundSamlConfigs/{inboundSamlConfigsId}": { + /** Retrieve an inbound SAML configuration for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.inboundSamlConfigs.get"]; + /** Delete an inbound SAML configuration for an Identity Toolkit project. */ + delete: operations["identitytoolkit.projects.inboundSamlConfigs.delete"]; + /** Update an inbound SAML configuration for an Identity Toolkit project. */ + patch: operations["identitytoolkit.projects.inboundSamlConfigs.patch"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/oauthIdpConfigs": { + /** List all Oidc Idp configurations for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.oauthIdpConfigs.list"]; + /** Create an Oidc Idp configuration for an Identity Toolkit project. */ + post: operations["identitytoolkit.projects.oauthIdpConfigs.create"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/oauthIdpConfigs/{oauthIdpConfigsId}": { + /** Retrieve an Oidc Idp configuration for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.oauthIdpConfigs.get"]; + /** Delete an Oidc Idp configuration for an Identity Toolkit project. */ + delete: operations["identitytoolkit.projects.oauthIdpConfigs.delete"]; + /** Update an Oidc Idp configuration for an Identity Toolkit project. */ + patch: operations["identitytoolkit.projects.oauthIdpConfigs.patch"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants": { + /** List tenants under the given agent project. Requires read permission on the Agent project. */ + get: operations["identitytoolkit.projects.tenants.list"]; + /** Create a tenant. Requires write permission on the Agent project. */ + post: operations["identitytoolkit.projects.tenants.create"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}": { + /** Get a tenant. Requires read permission on the Tenant resource. */ + get: operations["identitytoolkit.projects.tenants.get"]; + /** Delete a tenant. Requires write permission on the Agent project. */ + delete: operations["identitytoolkit.projects.tenants.delete"]; + /** Update a tenant. Requires write permission on the Tenant resource. */ + patch: operations["identitytoolkit.projects.tenants.patch"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}:getIamPolicy": { + /** Gets the access control policy for a resource. An error is returned if the resource does not exist. An empty policy is returned if the resource exists but does not have a policy set on it. Caller must have the right Google IAM permission on the resource. */ + post: operations["identitytoolkit.projects.tenants.getIamPolicy"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}:setIamPolicy": { + /** Sets the access control policy for a resource. If the policy exists, it is replaced. Caller must have the right Google IAM permission on the resource. */ + post: operations["identitytoolkit.projects.tenants.setIamPolicy"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}:testIamPermissions": { + /** Returns the caller's permissions on a resource. An error is returned if the resource does not exist. A caller is not required to have Google IAM permission to make this request. */ + post: operations["identitytoolkit.projects.tenants.testIamPermissions"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}/defaultSupportedIdpConfigs": { + /** List all default supported Idp configurations for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.list"]; + /** Create a default supported Idp configuration for an Identity Toolkit project. */ + post: operations["identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.create"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}/defaultSupportedIdpConfigs/{defaultSupportedIdpConfigsId}": { + /** Retrieve a default supported Idp configuration for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.get"]; + /** Delete a default supported Idp configuration for an Identity Toolkit project. */ + delete: operations["identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.delete"]; + /** Update a default supported Idp configuration for an Identity Toolkit project. */ + patch: operations["identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.patch"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}/inboundSamlConfigs": { + /** List all inbound SAML configurations for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.tenants.inboundSamlConfigs.list"]; + /** Create an inbound SAML configuration for an Identity Toolkit project. */ + post: operations["identitytoolkit.projects.tenants.inboundSamlConfigs.create"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}/inboundSamlConfigs/{inboundSamlConfigsId}": { + /** Retrieve an inbound SAML configuration for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.tenants.inboundSamlConfigs.get"]; + /** Delete an inbound SAML configuration for an Identity Toolkit project. */ + delete: operations["identitytoolkit.projects.tenants.inboundSamlConfigs.delete"]; + /** Update an inbound SAML configuration for an Identity Toolkit project. */ + patch: operations["identitytoolkit.projects.tenants.inboundSamlConfigs.patch"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}/oauthIdpConfigs": { + /** List all Oidc Idp configurations for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.tenants.oauthIdpConfigs.list"]; + /** Create an Oidc Idp configuration for an Identity Toolkit project. */ + post: operations["identitytoolkit.projects.tenants.oauthIdpConfigs.create"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v2/projects/{targetProjectId}/tenants/{tenantId}/oauthIdpConfigs/{oauthIdpConfigsId}": { + /** Retrieve an Oidc Idp configuration for an Identity Toolkit project. */ + get: operations["identitytoolkit.projects.tenants.oauthIdpConfigs.get"]; + /** Delete an Oidc Idp configuration for an Identity Toolkit project. */ + delete: operations["identitytoolkit.projects.tenants.oauthIdpConfigs.delete"]; + /** Update an Oidc Idp configuration for an Identity Toolkit project. */ + patch: operations["identitytoolkit.projects.tenants.oauthIdpConfigs.patch"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/v1/token": { + /** The Token Service API lets you exchange either an ID token or a refresh token for an access token and a new refresh token. You can use the access token to securely call APIs that require user authorization. */ + post: operations["securetoken.token"]; + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + }; + "/emulator/v1/projects/{targetProjectId}/accounts": { + /** Remove all accounts in the project, regardless of state. */ + delete: operations["emulator.projects.accounts.delete"]; + parameters: { + path: { + /** The ID of the Google Cloud project that the accounts belong to. */ + targetProjectId: string; + }; + }; + }; + "/emulator/v1/projects/{targetProjectId}/tenants/{tenantId}/accounts": { + /** Remove all accounts in the project, regardless of state. */ + delete: operations["emulator.projects.accounts.delete"]; + parameters: { + path: { + /** The ID of the Google Cloud project that the accounts belong to. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned. */ + tenantId: string; + }; + }; + }; + "/emulator/v1/projects/{targetProjectId}/config": { + /** Get emulator-specific configuration for the project. */ + get: operations["emulator.projects.config.get"]; + /** Update emulator-specific configuration for the project. */ + patch: operations["emulator.projects.config.update"]; + parameters: { + path: { + /** The ID of the Google Cloud project that the config belongs to. */ + targetProjectId: string; + }; + }; + }; + "/emulator/v1/projects/{targetProjectId}/oobCodes": { + /** List all pending confirmation codes for the project. */ + get: operations["emulator.projects.oobCodes.list"]; + parameters: { + path: { + /** The ID of the Google Cloud project that the confirmation codes belongs to. */ + targetProjectId: string; + }; + }; + }; + "/emulator/v1/projects/{targetProjectId}/tenants/{tenantId}/oobCodes": { + /** List all pending confirmation codes for the project. */ + get: operations["emulator.projects.oobCodes.list"]; + parameters: { + path: { + /** The ID of the Google Cloud project that the confirmation codes belongs to. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned. */ + tenantId: string; + }; + }; + }; + "/emulator/v1/projects/{targetProjectId}/verificationCodes": { + /** List all pending phone verification codes for the project. */ + get: operations["emulator.projects.verificationCodes.list"]; + parameters: { + path: { + /** The ID of the Google Cloud project that the verification codes belongs to. */ + targetProjectId: string; + }; + }; + }; + "/emulator/v1/projects/{targetProjectId}/tenants/{tenantId}/verificationCodes": { + /** List all pending phone verification codes for the project. */ + get: operations["emulator.projects.verificationCodes.list"]; + parameters: { + path: { + /** The ID of the Google Cloud project that the verification codes belongs to. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned. */ + tenantId: string; + }; + }; + }; +} + export interface components { schemas: { - /** - * The parameters for Argon2 hashing algorithm. - */ + /** @description The parameters for Argon2 hashing algorithm. */ GoogleCloudIdentitytoolkitV1Argon2Parameters: { /** - * The additional associated data, if provided, is appended to the hash value to provide an additional layer of security. A base64-encoded string if specified via JSON. + * Format: byte + * @description The additional associated data, if provided, is appended to the hash value to provide an additional layer of security. A base64-encoded string if specified via JSON. */ associatedData?: string; /** - * Required. The desired hash length in bytes. Minimum is 4 and maximum is 1024. + * Format: int32 + * @description Required. The desired hash length in bytes. Minimum is 4 and maximum is 1024. */ hashLengthBytes?: number; - /** - * Required. Must not be HASH_TYPE_UNSPECIFIED. - */ + /** @description Required. Must not be HASH_TYPE_UNSPECIFIED. */ hashType?: "HASH_TYPE_UNSPECIFIED" | "ARGON2_D" | "ARGON2_ID" | "ARGON2_I"; /** - * Required. The number of iterations to perform. Minimum is 1, maximum is 16. + * Format: int32 + * @description Required. The number of iterations to perform. Minimum is 1, maximum is 16. */ iterations?: number; /** - * Required. The memory cost in kibibytes. Maximum is 32768. + * Format: int32 + * @description Required. The memory cost in kibibytes. Maximum is 32768. */ memoryCostKib?: number; /** - * Required. The degree of parallelism, also called threads or lanes. Minimum is 1, maximum is 16. + * Format: int32 + * @description Required. The degree of parallelism, also called threads or lanes. Minimum is 1, maximum is 16. */ parallelism?: number; - /** - * The version of the Argon2 algorithm. This defaults to VERSION_13 if not specified. - */ + /** @description The version of the Argon2 algorithm. This defaults to VERSION_13 if not specified. */ version?: "VERSION_UNSPECIFIED" | "VERSION_10" | "VERSION_13"; }; - /** - * The information required to auto-retrieve an SMS. - */ + /** @description The information required to auto-retrieve an SMS. */ GoogleCloudIdentitytoolkitV1AutoRetrievalInfo: { - /** - * The Android app's signature hash for Google Play Service's SMS Retriever API. - */ + /** @description The Android app's signature hash for Google Play Service's SMS Retriever API. */ appSignatureHash?: string; }; - /** - * Request message for BatchDeleteAccounts. - */ + /** @description Request message for BatchDeleteAccounts. */ GoogleCloudIdentitytoolkitV1BatchDeleteAccountsRequest: { - /** - * Whether to force deleting accounts that are not in disabled state. If false, only disabled accounts will be deleted, and accounts that are not disabled will be added to the `errors`. - */ + /** @description Whether to force deleting accounts that are not in disabled state. If false, only disabled accounts will be deleted, and accounts that are not disabled will be added to the `errors`. */ force?: boolean; - /** - * Required. List of user IDs to be deleted. - */ + /** @description Required. List of user IDs to be deleted. */ localIds?: string[]; - /** - * If the accounts belong to an Identity Platform tenant, the ID of the tenant. If the accounts belong to an default Identity Platform project, the field is not needed. - */ + /** @description If the accounts belong to an Identity Platform tenant, the ID of the tenant. If the accounts belong to an default Identity Platform project, the field is not needed. */ tenantId?: string; }; - /** - * Response message to BatchDeleteAccounts. - */ + /** @description Response message to BatchDeleteAccounts. */ GoogleCloudIdentitytoolkitV1BatchDeleteAccountsResponse: { - /** - * Detailed error info for accounts that cannot be deleted. - */ + /** @description Detailed error info for accounts that cannot be deleted. */ errors?: components["schemas"]["GoogleCloudIdentitytoolkitV1BatchDeleteErrorInfo"][]; }; - /** - * Error info for account failed to be deleted. - */ + /** @description Error info for account failed to be deleted. */ GoogleCloudIdentitytoolkitV1BatchDeleteErrorInfo: { /** - * The index of the errored item in the original local_ids field. + * Format: int32 + * @description The index of the errored item in the original local_ids field. */ index?: number; - /** - * The corresponding user ID. - */ + /** @description The corresponding user ID. */ localId?: string; - /** - * Detailed error message. - */ + /** @description Detailed error message. */ message?: string; }; - /** - * Request message for CreateAuthUri. - */ + /** @description Request message for CreateAuthUri. */ GoogleCloudIdentitytoolkitV1CreateAuthUriRequest: { appId?: string; - /** - * Used for the Google provider. The type of the authentication flow to be used. If present, this should be `CODE_FLOW` to specify the authorization code flow. Otherwise, the default ID Token flow will be used. - */ + /** @description Used for the Google provider. The type of the authentication flow to be used. If present, this should be `CODE_FLOW` to specify the authorization code flow. Otherwise, the default ID Token flow will be used. */ authFlowType?: string; - /** - * An opaque string used to maintain contextual information between the authentication request and the callback from the IdP. - */ + /** @description An opaque string used to maintain contextual information between the authentication request and the callback from the IdP. */ context?: string; - /** - * A valid URL for the IdP to redirect the user back to. The URL cannot contain fragments or the reserved `state` query parameter. - */ + /** @description A valid URL for the IdP to redirect the user back to. The URL cannot contain fragments or the reserved `state` query parameter. */ continueUri?: string; - /** - * Additional customized query parameters to be added to the authorization URI. The following parameters are reserved and cannot be added: `client_id`, `response_type`, `scope`, `redirect_uri`, `state`. For the Microsoft provider, the Azure AD tenant to sign-in to can be specified in the `tenant` custom parameter. - */ + /** @description Additional customized query parameters to be added to the authorization URI. The following parameters are reserved and cannot be added: `client_id`, `response_type`, `scope`, `redirect_uri`, `state`. For the Microsoft provider, the Azure AD tenant to sign-in to can be specified in the `tenant` custom parameter. */ customParameter?: { [key: string]: string }; - /** - * Used for the Google provider. The G Suite hosted domain of the user in order to restrict sign-in to users at that domain. - */ + /** @description Used for the Google provider. The G Suite hosted domain of the user in order to restrict sign-in to users at that domain. */ hostedDomain?: string; - /** - * The email identifier of the user account to fetch associated providers for. At least one of the fields `identifier` and `provider_id` must be set. The length of the email address should be less than 256 characters and in the format of `name@domain.tld`. The email address should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. - */ + /** @description The email identifier of the user account to fetch associated providers for. At least one of the fields `identifier` and `provider_id` must be set. The length of the email address should be less than 256 characters and in the format of `name@domain.tld`. The email address should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. */ identifier?: string; oauthConsumerKey?: string; - /** - * Additional space-delimited OAuth 2.0 scopes specifying the scope of the authentication request with the IdP. Used for OAuth 2.0 IdPs. For the Google provider, the authorization code flow will be used if this field is set. - */ + /** @description Additional space-delimited OAuth 2.0 scopes specifying the scope of the authentication request with the IdP. Used for OAuth 2.0 IdPs. For the Google provider, the authorization code flow will be used if this field is set. */ oauthScope?: string; openidRealm?: string; otaApp?: string; - /** - * The provider ID of the IdP for the user to sign in with. This should be a provider ID enabled for sign-in, which is either from the list of [default supported IdPs](https://cloud.google.com/identity-platform/docs/reference/rest/v2/defaultSupportedIdps/list), or of the format `oidc.*` or `saml.*`. Some examples are `google.com`, `facebook.com`, `oidc.testapp`, and `saml.testapp`. At least one of the fields `identifier` and `provider_id` must be set. - */ + /** @description The provider ID of the IdP for the user to sign in with. This should be a provider ID enabled for sign-in, which is either from the list of [default supported IdPs](https://cloud.google.com/identity-platform/docs/reference/rest/v2/defaultSupportedIdps/list), or of the format `oidc.*` or `saml.*`. Some examples are `google.com`, `facebook.com`, `oidc.testapp`, and `saml.testapp`. At least one of the fields `identifier` and `provider_id` must be set. */ providerId?: string; - /** - * A session ID that can be verified against in SignInWithIdp to prevent session fixation attacks. If absent, a random string will be generated and returned as the session ID. - */ + /** @description A session ID that can be verified against in SignInWithIdp to prevent session fixation attacks. If absent, a random string will be generated and returned as the session ID. */ sessionId?: string; - /** - * The ID of the Identity Platform tenant to create an authorization URI or lookup an email identifier for. If not set, the operation will be performed in the default Identity Platform instance in the project. - */ + /** @description The ID of the Identity Platform tenant to create an authorization URI or lookup an email identifier for. If not set, the operation will be performed in the default Identity Platform instance in the project. */ tenantId?: string; }; - /** - * Response message for CreateAuthUri. - */ + /** @description Response message for CreateAuthUri. */ GoogleCloudIdentitytoolkitV1CreateAuthUriResponse: { allProviders?: string[]; - /** - * The authorization URI for the requested provider. Present only when a provider ID is set in the request. - */ + /** @description The authorization URI for the requested provider. Present only when a provider ID is set in the request. */ authUri?: string; - /** - * Whether a CAPTCHA is needed because there have been too many failed login attempts by the user. Present only when a registered email identifier is set in the request. - */ + /** @description Whether a CAPTCHA is needed because there have been too many failed login attempts by the user. Present only when a registered email identifier is set in the request. */ captchaRequired?: boolean; - /** - * Whether the user has previously signed in with the provider ID in the request. Present only when a registered email identifier is set in the request. - */ + /** @description Whether the user has previously signed in with the provider ID in the request. Present only when a registered email identifier is set in the request. */ forExistingProvider?: boolean; kind?: string; - /** - * The provider ID from the request, if provided. - */ + /** @description The provider ID from the request, if provided. */ providerId?: string; - /** - * Whether the email identifier represents an existing account. Present only when an email identifier is set in the request. - */ + /** @description Whether the email identifier represents an existing account. Present only when an email identifier is set in the request. */ registered?: boolean; - /** - * The session ID from the request, or a random string generated by CreateAuthUri if absent. It is used to prevent session fixation attacks. - */ + /** @description The session ID from the request, or a random string generated by CreateAuthUri if absent. It is used to prevent session fixation attacks. */ sessionId?: string; - /** - * The list of sign-in methods that the user has previously used. Each element is one of `password`, `emailLink`, or the provider ID of an IdP. Present only when a registered email identifier is set in the request. - */ + /** @description The list of sign-in methods that the user has previously used. Each element is one of `password`, `emailLink`, or the provider ID of an IdP. Present only when a registered email identifier is set in the request. */ signinMethods?: string[]; }; - /** - * Request message for CreateSessionCookie. - */ + /** @description Request message for CreateSessionCookie. */ GoogleCloudIdentitytoolkitV1CreateSessionCookieRequest: { - /** - * Required. A valid Identity Platform ID token. - */ + /** @description Required. A valid Identity Platform ID token. */ idToken?: string; - /** - * The tenant ID of the Identity Platform tenant the account belongs to. - */ + /** @description The tenant ID of the Identity Platform tenant the account belongs to. */ tenantId?: string; /** - * The number of seconds until the session cookie expires. Specify a duration in seconds, between five minutes and fourteen days, inclusively. + * Format: int64 + * @description The number of seconds until the session cookie expires. Specify a duration in seconds, between five minutes and fourteen days, inclusively. */ validDuration?: string; }; - /** - * Response message for CreateSessionCookie. - */ + /** @description Response message for CreateSessionCookie. */ GoogleCloudIdentitytoolkitV1CreateSessionCookieResponse: { - /** - * The session cookie that has been created from the Identity Platform ID token specified in the request. It is in the form of a JSON Web Token (JWT). Always present. - */ + /** @description The session cookie that has been created from the Identity Platform ID token specified in the request. It is in the form of a JSON Web Token (JWT). Always present. */ sessionCookie?: string; }; - /** - * Request message for DeleteAccount. - */ + /** @description Request message for DeleteAccount. */ GoogleCloudIdentitytoolkitV1DeleteAccountRequest: { + /** Format: int64 */ delegatedProjectNumber?: string; - /** - * The Identity Platform ID token of the account to delete. Require to be specified for requests from end users that lack Google OAuth 2.0 credential. Authenticated requests bearing a Google OAuth2 credential with proper permissions may pass local_id to specify the account to delete alternatively. - */ + /** @description The Identity Platform ID token of the account to delete. Require to be specified for requests from end users that lack Google OAuth 2.0 credential. Authenticated requests bearing a Google OAuth2 credential with proper permissions may pass local_id to specify the account to delete alternatively. */ idToken?: string; - /** - * The ID of user account to delete. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). Requests from users lacking the credential should pass an ID token instead. - */ + /** @description The ID of user account to delete. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). Requests from users lacking the credential should pass an ID token instead. */ localId?: string; - /** - * The ID of the project which the account belongs to. Should only be specified in authenticated requests that specify local_id of an account. - */ + /** @description The ID of the project which the account belongs to. Should only be specified in authenticated requests that specify local_id of an account. */ targetProjectId?: string; - /** - * The ID of the tenant that the account belongs to, if applicable. Only require to be specified for authenticated requests bearing a Google OAuth 2.0 credential that specify local_id of an account that belongs to an Identity Platform tenant. - */ + /** @description The ID of the tenant that the account belongs to, if applicable. Only require to be specified for authenticated requests bearing a Google OAuth 2.0 credential that specify local_id of an account that belongs to an Identity Platform tenant. */ tenantId?: string; }; - /** - * Response message for DeleteAccount. - */ - GoogleCloudIdentitytoolkitV1DeleteAccountResponse: { kind?: string }; - /** - * Response message for DownloadAccount. - */ + /** @description Response message for DeleteAccount. */ + GoogleCloudIdentitytoolkitV1DeleteAccountResponse: { + kind?: string; + }; + /** @description Response message for DownloadAccount. */ GoogleCloudIdentitytoolkitV1DownloadAccountResponse: { kind?: string; - /** - * If there are more accounts to be downloaded, a token that can be passed back to DownloadAccount to get more accounts. Otherwise, this is blank. - */ + /** @description If there are more accounts to be downloaded, a token that can be passed back to DownloadAccount to get more accounts. Otherwise, this is blank. */ nextPageToken?: string; - /** - * All accounts belonging to the project/tenant limited by max_results in the request. - */ + /** @description All accounts belonging to the project/tenant limited by max_results in the request. */ users?: components["schemas"]["GoogleCloudIdentitytoolkitV1UserInfo"][]; }; - /** - * Email template - */ + /** @description Email template */ GoogleCloudIdentitytoolkitV1EmailTemplate: { - /** - * Email body - */ + /** @description Email body */ body?: string; - /** - * Whether the body or subject of the email is customized. - */ + /** @description Whether the body or subject of the email is customized. */ customized?: boolean; - /** - * Whether the template is disabled. If true, a default template will be used. - */ + /** @description Whether the template is disabled. If true, a default template will be used. */ disabled?: boolean; - /** - * Email body format - */ + /** @description Email body format */ format?: "EMAIL_BODY_FORMAT_UNSPECIFIED" | "PLAINTEXT" | "HTML"; - /** - * From address of the email - */ + /** @description From address of the email */ from?: string; - /** - * From display name - */ + /** @description From display name */ fromDisplayName?: string; - /** - * Local part of From address - */ + /** @description Local part of From address */ fromLocalPart?: string; - /** - * Value is in III language code format (e.g. "zh-CN", "es"). Both '-' and '_' separators are accepted. - */ + /** @description Value is in III language code format (e.g. "zh-CN", "es"). Both '-' and '_' separators are accepted. */ locale?: string; - /** - * Reply-to address - */ + /** @description Reply-to address */ replyTo?: string; - /** - * Subject of the email - */ + /** @description Subject of the email */ subject?: string; }; - /** - * Error information explaining why an account cannot be uploaded. batch upload. - */ + /** @description Error information explaining why an account cannot be uploaded. batch upload. */ GoogleCloudIdentitytoolkitV1ErrorInfo: { /** - * The index of the item, range is [0, request.size - 1] + * Format: int32 + * @description The index of the item, range is [0, request.size - 1] */ index?: number; - /** - * Detailed error message - */ + /** @description Detailed error message */ message?: string; }; - /** - * Federated user identifier at an Identity Provider. - */ + /** @description Federated user identifier at an Identity Provider. */ GoogleCloudIdentitytoolkitV1FederatedUserIdentifier: { - /** - * The ID of supported identity providers. This should be a provider ID enabled for sign-in, which is either from the list of [default supported IdPs](https://cloud.google.com/identity-platform/docs/reference/rest/v2/defaultSupportedIdps/list), or of the format `oidc.*` or `saml.*`. Some examples are `google.com`, `facebook.com`, `oidc.testapp`, and `saml.testapp`. - */ + /** @description The ID of supported identity providers. This should be a provider ID enabled for sign-in, which is either from the list of [default supported IdPs](https://cloud.google.com/identity-platform/docs/reference/rest/v2/defaultSupportedIdps/list), or of the format `oidc.*` or `saml.*`. Some examples are `google.com`, `facebook.com`, `oidc.testapp`, and `saml.testapp`. */ providerId?: string; - /** - * The user ID of the account at the third-party Identity Provider specified by `provider_id`. - */ + /** @description The user ID of the account at the third-party Identity Provider specified by `provider_id`. */ rawId?: string; }; - /** - * Request message for GetAccountInfo. - */ + /** @description Request message for GetAccountInfo. */ GoogleCloudIdentitytoolkitV1GetAccountInfoRequest: { + /** Format: int64 */ delegatedProjectNumber?: string; - /** - * The email address of one or more accounts to fetch. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. Should only be specified by authenticated requests from a developer. - */ + /** @description The email address of one or more accounts to fetch. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. Should only be specified by authenticated requests from a developer. */ email?: string[]; - /** - * The federated user identifier of one or more accounts to fetch. Should only be specified by authenticated requests bearing a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description The federated user identifier of one or more accounts to fetch. Should only be specified by authenticated requests bearing a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ federatedUserId?: components["schemas"]["GoogleCloudIdentitytoolkitV1FederatedUserIdentifier"][]; - /** - * The Identity Platform ID token of the account to fetch. Require to be specified for requests from end users. - */ + /** @description The Identity Platform ID token of the account to fetch. Require to be specified for requests from end users. */ idToken?: string; - /** - * The initial email of one or more accounts to fetch. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. Should only be specified by authenticated requests from a developer. - */ + /** @description The initial email of one or more accounts to fetch. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. Should only be specified by authenticated requests from a developer. */ initialEmail?: string[]; - /** - * The ID of one or more accounts to fetch. Should only be specified by authenticated requests bearing a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description The ID of one or more accounts to fetch. Should only be specified by authenticated requests bearing a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ localId?: string[]; - /** - * The phone number of one or more accounts to fetch. Should only be specified by authenticated requests from a developer and should be in E.164 format, for example, +15555555555. - */ + /** @description The phone number of one or more accounts to fetch. Should only be specified by authenticated requests from a developer and should be in E.164 format, for example, +15555555555. */ phoneNumber?: string[]; - /** - * The ID of the Google Cloud project that the account or the Identity Platform tenant specified by `tenant_id` belongs to. Should only be specified by authenticated requests bearing a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description The ID of the Google Cloud project that the account or the Identity Platform tenant specified by `tenant_id` belongs to. Should only be specified by authenticated requests bearing a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ targetProjectId?: string; - /** - * The ID of the tenant that the account belongs to. Should only be specified by authenticated requests from a developer. - */ + /** @description The ID of the tenant that the account belongs to. Should only be specified by authenticated requests from a developer. */ tenantId?: string; }; - /** - * Response message for GetAccountInfo. - */ + /** @description Response message for GetAccountInfo. */ GoogleCloudIdentitytoolkitV1GetAccountInfoResponse: { kind?: string; - /** - * The information of specific user account(s) matching the parameters in the request. - */ + /** @description The information of specific user account(s) matching the parameters in the request. */ users?: components["schemas"]["GoogleCloudIdentitytoolkitV1UserInfo"][]; }; - /** - * Request message for GetOobCode. - */ + /** @description Request message for GetOobCode. */ GoogleCloudIdentitytoolkitV1GetOobCodeRequest: { - /** - * If an associated android app can handle the OOB code, whether or not to install the android app on the device where the link is opened if the app is not already installed. - */ + /** @description If an associated android app can handle the OOB code, whether or not to install the android app on the device where the link is opened if the app is not already installed. */ androidInstallApp?: boolean; - /** - * If an associated android app can handle the OOB code, the minimum version of the app. If the version on the device is lower than this version then the user is taken to Google Play Store to upgrade the app. - */ + /** @description If an associated android app can handle the OOB code, the minimum version of the app. If the version on the device is lower than this version then the user is taken to Google Play Store to upgrade the app. */ androidMinimumVersion?: string; - /** - * If an associated android app can handle the OOB code, the Android package name of the android app that will handle the callback when this OOB code is used. This will allow the correct app to open if it is already installed, or allow Google Play Store to open to the correct app if it is not yet installed. - */ + /** @description If an associated android app can handle the OOB code, the Android package name of the android app that will handle the callback when this OOB code is used. This will allow the correct app to open if it is already installed, or allow Google Play Store to open to the correct app if it is not yet installed. */ androidPackageName?: string; - /** - * When set to true, the OOB code link will be be sent as a Universal Link or an Android App Link and will be opened by the corresponding app if installed. If not set, or set to false, the OOB code will be sent to the web widget first and then on continue will redirect to the app if installed. - */ + /** @description When set to true, the OOB code link will be be sent as a Universal Link or an Android App Link and will be opened by the corresponding app if installed. If not set, or set to false, the OOB code will be sent to the web widget first and then on continue will redirect to the app if installed. */ canHandleCodeInApp?: boolean; - /** - * For a PASSWORD_RESET request, a reCaptcha response is required when the system detects possible abuse activity. In those cases, this is the response from the reCaptcha challenge used to verify the caller. - */ + /** @description For a PASSWORD_RESET request, a reCaptcha response is required when the system detects possible abuse activity. In those cases, this is the response from the reCaptcha challenge used to verify the caller. */ captchaResp?: string; challenge?: string; - /** - * The Url to continue after user clicks the link sent in email. This is the url that will allow the web widget to handle the OOB code. - */ + /** @description The Url to continue after user clicks the link sent in email. This is the url that will allow the web widget to handle the OOB code. */ continueUrl?: string; - /** - * In order to ensure that the url used can be easily opened up in iOS or android, we create a [Firebase Dynamic Link](https://firebase.google.com/docs/dynamic-links). Most Identity Platform projects will only have one Dynamic Link domain enabled, and can leave this field blank. This field contains a specified Dynamic Link domain for projects that have multiple enabled. - */ + /** @description In order to ensure that the url used can be easily opened up in iOS or android, we create a [Firebase Dynamic Link](https://firebase.google.com/docs/dynamic-links). Most Identity Platform projects will only have one Dynamic Link domain enabled, and can leave this field blank. This field contains a specified Dynamic Link domain for projects that have multiple enabled. */ dynamicLinkDomain?: string; - /** - * The account's email address to send the OOB code to, and generally the email address of the account that needs to be updated. Required for PASSWORD_RESET, EMAIL_SIGNIN, and VERIFY_EMAIL. Only required for VERIFY_AND_CHANGE_EMAIL requests when return_oob_link is set to true. In this case, it is the original email of the user. - */ + /** @description The account's email address to send the OOB code to, and generally the email address of the account that needs to be updated. Required for PASSWORD_RESET, EMAIL_SIGNIN, and VERIFY_EMAIL. Only required for VERIFY_AND_CHANGE_EMAIL requests when return_oob_link is set to true. In this case, it is the original email of the user. */ email?: string; - /** - * If an associated iOS app can handle the OOB code, the App Store id of this app. This will allow App Store to open to the correct app if the app is not yet installed. - */ + /** @description If an associated iOS app can handle the OOB code, the App Store id of this app. This will allow App Store to open to the correct app if the app is not yet installed. */ iOSAppStoreId?: string; - /** - * If an associated iOS app can handle the OOB code, the iOS bundle id of this app. This will allow the correct app to open if it is already installed. - */ + /** @description If an associated iOS app can handle the OOB code, the iOS bundle id of this app. This will allow the correct app to open if it is already installed. */ iOSBundleId?: string; - /** - * An ID token for the account. It is required for VERIFY_AND_CHANGE_EMAIL and VERIFY_EMAIL requests unless return_oob_link is set to true. - */ + /** @description An ID token for the account. It is required for VERIFY_AND_CHANGE_EMAIL and VERIFY_EMAIL requests unless return_oob_link is set to true. */ idToken?: string; - /** - * The email address the account is being updated to. Required only for VERIFY_AND_CHANGE_EMAIL requests. - */ + /** @description The email address the account is being updated to. Required only for VERIFY_AND_CHANGE_EMAIL requests. */ newEmail?: string; - /** - * Required. The type of out-of-band (OOB) code to send. Depending on this value, other fields in this request will be required and/or have different meanings. There are 4 different OOB codes that can be sent: * PASSWORD_RESET * EMAIL_SIGNIN * VERIFY_EMAIL * VERIFY_AND_CHANGE_EMAIL - */ + /** @description Required. The type of out-of-band (OOB) code to send. Depending on this value, other fields in this request will be required and/or have different meanings. There are 4 different OOB codes that can be sent: * PASSWORD_RESET * EMAIL_SIGNIN * VERIFY_EMAIL * VERIFY_AND_CHANGE_EMAIL */ requestType?: | "OOB_REQ_TYPE_UNSPECIFIED" | "PASSWORD_RESET" @@ -429,126 +2106,75 @@ export interface components { | "EMAIL_SIGNIN" | "VERIFY_AND_CHANGE_EMAIL" | "REVERT_SECOND_FACTOR_ADDITION"; - /** - * Whether the confirmation link containing the OOB code should be returned in the response (no email is sent). Used when a developer wants to construct the email template and send it on their own. By default this is false; to specify this field, and to set it to true, it requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control) - */ + /** @description Whether the confirmation link containing the OOB code should be returned in the response (no email is sent). Used when a developer wants to construct the email template and send it on their own. By default this is false; to specify this field, and to set it to true, it requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control) */ returnOobLink?: boolean; - /** - * The Project ID of the Identity Platform project which the account belongs to. To specify this field, it requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description The Project ID of the Identity Platform project which the account belongs to. To specify this field, it requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ targetProjectId?: string; - /** - * The tenant ID of the Identity Platform tenant the account belongs to. - */ + /** @description The tenant ID of the Identity Platform tenant the account belongs to. */ tenantId?: string; - /** - * The IP address of the caller. Required only for PASSWORD_RESET requests. - */ + /** @description The IP address of the caller. Required only for PASSWORD_RESET requests. */ userIp?: string; }; - /** - * Response message for GetOobCode. - */ + /** @description Response message for GetOobCode. */ GoogleCloudIdentitytoolkitV1GetOobCodeResponse: { - /** - * If return_oob_link is false in the request, the email address the verification was sent to. - */ + /** @description If return_oob_link is false in the request, the email address the verification was sent to. */ email?: string; kind?: string; - /** - * If return_oob_link is true in the request, the OOB code to send. - */ + /** @description If return_oob_link is true in the request, the OOB code to send. */ oobCode?: string; - /** - * If return_oob_link is true in the request, the OOB link to be sent to the user. This returns the constructed link including [Firebase Dynamic Link](https://firebase.google.com/docs/dynamic-links) related parameters. - */ + /** @description If return_oob_link is true in the request, the OOB link to be sent to the user. This returns the constructed link including [Firebase Dynamic Link](https://firebase.google.com/docs/dynamic-links) related parameters. */ oobLink?: string; }; - /** - * Response message for GetProjectConfig. - */ + /** @description Response message for GetProjectConfig. */ GoogleCloudIdentitytoolkitV1GetProjectConfigResponse: { - /** - * Whether to allow password account sign up. This field is only returned for authenticated calls from a developer. - */ + /** @description Whether to allow password account sign up. This field is only returned for authenticated calls from a developer. */ allowPasswordUser?: boolean; - /** - * Google Cloud API key. This field is only returned for authenticated calls from a developer. - */ + /** @description Google Cloud API key. This field is only returned for authenticated calls from a developer. */ apiKey?: string; - /** - * Authorized domains for widget redirect. - */ + /** @description Authorized domains for widget redirect. */ authorizedDomains?: string[]; changeEmailTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitV1EmailTemplate"]; - /** - * The Firebase Dynamic Links domain used to construct links for redirects to native apps. - */ + /** @description The Firebase Dynamic Links domain used to construct links for redirects to native apps. */ dynamicLinksDomain?: string; - /** - * Whether anonymous user is enabled. This field is only returned for authenticated calls from a developer. - */ + /** @description Whether anonymous user is enabled. This field is only returned for authenticated calls from a developer. */ enableAnonymousUser?: boolean; - /** - * OAuth2 provider config. This field is only returned for authenticated calls from a developer. - */ + /** @description OAuth2 provider config. This field is only returned for authenticated calls from a developer. */ idpConfig?: components["schemas"]["GoogleCloudIdentitytoolkitV1IdpConfig"][]; legacyResetPasswordTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitV1EmailTemplate"]; - /** - * The project id of the retrieved configuration. - */ + /** @description The project id of the retrieved configuration. */ projectId?: string; resetPasswordTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitV1EmailTemplate"]; revertSecondFactorAdditionTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitV1EmailTemplate"]; - /** - * Whether to use email sending. This field is only returned for authenticated calls from a developer. - */ + /** @description Whether to use email sending. This field is only returned for authenticated calls from a developer. */ useEmailSending?: boolean; verifyEmailTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitV1EmailTemplate"]; }; - /** - * Response message for GetRecaptchaParam. - */ + /** @description Response message for GetRecaptchaParam. */ GoogleCloudIdentitytoolkitV1GetRecaptchaParamResponse: { kind?: string; - /** - * The producer project number used to generate PIA tokens - */ + /** @description The producer project number used to generate PIA tokens */ producerProjectNumber?: string; - /** - * The reCAPTCHA v2 site key used to invoke the reCAPTCHA service. Always present. - */ + /** @description The reCAPTCHA v2 site key used to invoke the reCAPTCHA service. Always present. */ recaptchaSiteKey?: string; recaptchaStoken?: string; }; - /** - * Response message for GetSessionCookiePublicKeys. - */ + /** @description Response message for GetSessionCookiePublicKeys. */ GoogleCloudIdentitytoolkitV1GetSessionCookiePublicKeysResponse: { - /** - * Public keys of the session cookie signer, formatted as [JSON Web Keys (JWK)](https://tools.ietf.org/html/rfc7517). - */ + /** @description Public keys of the session cookie signer, formatted as [JSON Web Keys (JWK)](https://tools.ietf.org/html/rfc7517). */ keys?: components["schemas"]["GoogleCloudIdentitytoolkitV1OpenIdConnectKey"][]; }; - /** - * Config of an identity provider. - */ + /** @description Config of an identity provider. */ GoogleCloudIdentitytoolkitV1IdpConfig: { - /** - * OAuth2 client ID. - */ + /** @description OAuth2 client ID. */ clientId?: string; - /** - * True if allows the user to sign in with the provider. - */ + /** @description True if allows the user to sign in with the provider. */ enabled?: boolean; /** - * Percent of users who will be prompted/redirected federated login for this IdP + * Format: int32 + * @description Percent of users who will be prompted/redirected federated login for this IdP */ experimentPercent?: number; - /** - * Name of the identity provider. - */ + /** @description Name of the identity provider. */ provider?: | "PROVIDER_UNSPECIFIED" | "MSLIVE" @@ -562,202 +2188,117 @@ export interface components { | "GOOGLE_PLAY_GAMES" | "LINKEDIN" | "IOS_GAME_CENTER"; - /** - * OAuth2 client secret. - */ + /** @description OAuth2 client secret. */ secret?: string; - /** - * Whitelisted client IDs for audience check. - */ + /** @description Whitelisted client IDs for audience check. */ whitelistedAudiences?: string[]; }; - /** - * Request message for IssueSamlResponse. - */ + /** @description Request message for IssueSamlResponse. */ GoogleCloudIdentitytoolkitV1IssueSamlResponseRequest: { - /** - * The Identity Platform ID token. It will be verified and then converted to a new SAMLResponse. - */ + /** @description The Identity Platform ID token. It will be verified and then converted to a new SAMLResponse. */ idToken?: string; - /** - * Relying Party identifier, which is the audience of issued SAMLResponse. - */ + /** @description Relying Party identifier, which is the audience of issued SAMLResponse. */ rpId?: string; - /** - * SAML app entity id specified in Google Admin Console for each app. If developers want to redirect to a third-party app rather than a G Suite app, they'll probably they need this. When it's used, we'll return a RelayState. This includes a SAMLRequest, which can be used to trigger a SP-initiated SAML flow to redirect to the real app. - */ + /** @description SAML app entity id specified in Google Admin Console for each app. If developers want to redirect to a third-party app rather than a G Suite app, they'll probably they need this. When it's used, we'll return a RelayState. This includes a SAMLRequest, which can be used to trigger a SP-initiated SAML flow to redirect to the real app. */ samlAppEntityId?: string; }; - /** - * Response for IssueSamlResponse request. - */ + /** @description Response for IssueSamlResponse request. */ GoogleCloudIdentitytoolkitV1IssueSamlResponseResponse: { - /** - * The ACS endpoint which consumes the returned SAMLResponse. - */ + /** @description The ACS endpoint which consumes the returned SAMLResponse. */ acsEndpoint?: string; - /** - * Email of the user. - */ + /** @description Email of the user. */ email?: string; - /** - * First name of the user. - */ + /** @description First name of the user. */ firstName?: string; - /** - * Whether the logged in user was created by this request. - */ + /** @description Whether the logged in user was created by this request. */ isNewUser?: boolean; - /** - * Last name of the user. - */ + /** @description Last name of the user. */ lastName?: string; - /** - * Generated RelayState. - */ + /** @description Generated RelayState. */ relayState?: string; - /** - * Signed SAMLResponse created for the Relying Party. - */ + /** @description Signed SAMLResponse created for the Relying Party. */ samlResponse?: string; }; - /** - * Information on which multi-factor authentication (MFA) providers are enabled for an account. - */ + /** @description Information on which multi-factor authentication (MFA) providers are enabled for an account. */ GoogleCloudIdentitytoolkitV1MfaEnrollment: { - /** - * Display name for this mfa option e.g. "corp cell phone". - */ + /** @description Display name for this mfa option e.g. "corp cell phone". */ displayName?: string; /** - * Timestamp when the account enrolled this second factor. + * Format: google-datetime + * @description Timestamp when the account enrolled this second factor. */ enrolledAt?: string; - /** - * ID of this MFA option. - */ + /** @description ID of this MFA option. */ mfaEnrollmentId?: string; - /** - * Normally this will show the phone number associated with this enrollment. In some situations, such as after a first factor sign in, it will only show the obfuscated version of the associated phone number. - */ + /** @description Normally this will show the phone number associated with this enrollment. In some situations, such as after a first factor sign in, it will only show the obfuscated version of the associated phone number. */ phoneInfo?: string; - /** - * Output only. Unobfuscated phone_info. - */ + /** @description Output only. Unobfuscated phone_info. */ unobfuscatedPhoneInfo?: string; }; GoogleCloudIdentitytoolkitV1MfaFactor: { - /** - * Display name for this mfa option e.g. "corp cell phone". - */ + /** @description Display name for this mfa option e.g. "corp cell phone". */ displayName?: string; - /** - * Phone number to receive OTP for MFA. - */ + /** @description Phone number to receive OTP for MFA. */ phoneInfo?: string; }; - /** - * Multi-factor authentication related information. - */ + /** @description Multi-factor authentication related information. */ GoogleCloudIdentitytoolkitV1MfaInfo: { - /** - * The second factors the user has enrolled. - */ + /** @description The second factors the user has enrolled. */ enrollments?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaEnrollment"][]; }; - /** - * Represents a public key of the session cookie signer, formatted as a [JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517). - */ + /** @description Represents a public key of the session cookie signer, formatted as a [JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517). */ GoogleCloudIdentitytoolkitV1OpenIdConnectKey: { - /** - * Signature algorithm. - */ + /** @description Signature algorithm. */ alg?: string; - /** - * Exponent for the RSA public key, it is represented as the base64url encoding of the value's big endian representation. - */ + /** @description Exponent for the RSA public key, it is represented as the base64url encoding of the value's big endian representation. */ e?: string; - /** - * Unique string to identify this key. - */ + /** @description Unique string to identify this key. */ kid?: string; - /** - * Key type. - */ + /** @description Key type. */ kty?: string; - /** - * Modulus for the RSA public key, it is represented as the base64url encoding of the value's big endian representation. - */ + /** @description Modulus for the RSA public key, it is represented as the base64url encoding of the value's big endian representation. */ n?: string; - /** - * Key use. - */ + /** @description Key use. */ use?: string; }; - /** - * Information about the user as provided by various Identity Providers. - */ + /** @description Information about the user as provided by various Identity Providers. */ GoogleCloudIdentitytoolkitV1ProviderUserInfo: { - /** - * The user's display name at the Identity Provider. - */ + /** @description The user's display name at the Identity Provider. */ displayName?: string; - /** - * The user's email address at the Identity Provider. - */ + /** @description The user's email address at the Identity Provider. */ email?: string; - /** - * The user's identifier at the Identity Provider. - */ + /** @description The user's identifier at the Identity Provider. */ federatedId?: string; - /** - * The user's phone number at the Identity Provider. - */ + /** @description The user's phone number at the Identity Provider. */ phoneNumber?: string; - /** - * The user's profile photo URL at the Identity Provider. - */ + /** @description The user's profile photo URL at the Identity Provider. */ photoUrl?: string; - /** - * The ID of the Identity Provider. - */ + /** @description The ID of the Identity Provider. */ providerId?: string; - /** - * The user's raw identifier directly returned from Identity Provider. - */ + /** @description The user's raw identifier directly returned from Identity Provider. */ rawId?: string; - /** - * The user's screen_name at Twitter or login name at GitHub. - */ + /** @description The user's screen_name at Twitter or login name at GitHub. */ screenName?: string; }; - /** - * Request message for QueryUserInfo. - */ + /** @description Request message for QueryUserInfo. */ GoogleCloudIdentitytoolkitV1QueryUserInfoRequest: { - /** - * Query conditions used to filter results. If more than one is passed, only the first SqlExpression is evaluated. - */ + /** @description Query conditions used to filter results. If more than one is passed, only the first SqlExpression is evaluated. */ expression?: components["schemas"]["GoogleCloudIdentitytoolkitV1SqlExpression"][]; /** - * The maximum number of accounts to return with an upper limit of __500__. Defaults to _500_. Only valid when `return_user_info` is set to `true`. + * Format: int64 + * @description The maximum number of accounts to return with an upper limit of __500__. Defaults to _500_. Only valid when `return_user_info` is set to `true`. */ limit?: string; /** - * The number of accounts to skip from the beginning of matching records. Only valid when `return_user_info` is set to `true`. + * Format: int64 + * @description The number of accounts to skip from the beginning of matching records. Only valid when `return_user_info` is set to `true`. */ offset?: string; - /** - * The order for sorting query result. Defaults to __ascending__ order. Only valid when `return_user_info` is set to `true`. - */ + /** @description The order for sorting query result. Defaults to __ascending__ order. Only valid when `return_user_info` is set to `true`. */ order?: "ORDER_UNSPECIFIED" | "ASC" | "DESC"; - /** - * If `true`, this request will return the accounts matching the query. If `false`, only the __count__ of accounts matching the query will be returned. Defaults to `true`. - */ + /** @description If `true`, this request will return the accounts matching the query. If `false`, only the __count__ of accounts matching the query will be returned. Defaults to `true`. */ returnUserInfo?: boolean; - /** - * The field to use for sorting user accounts. Defaults to `USER_ID`. Note: when `phone_number` is specified in `expression`, the result ignores the sorting. Only valid when `return_user_info` is set to `true`. - */ + /** @description The field to use for sorting user accounts. Defaults to `USER_ID`. Note: when `phone_number` is specified in `expression`, the result ignores the sorting. Only valid when `return_user_info` is set to `true`. */ sortBy?: | "SORT_BY_FIELD_UNSPECIFIED" | "USER_ID" @@ -765,56 +2306,35 @@ export interface components { | "CREATED_AT" | "LAST_LOGIN_AT" | "USER_EMAIL"; - /** - * The ID of the tenant to which the result is scoped. - */ + /** @description The ID of the tenant to which the result is scoped. */ tenantId?: string; }; - /** - * Response message for QueryUserInfo. - */ + /** @description Response message for QueryUserInfo. */ GoogleCloudIdentitytoolkitV1QueryUserInfoResponse: { /** - * If `return_user_info` in the request is true, this is the number of returned accounts in this message. Otherwise, this is the total number of accounts matching the query. + * Format: int64 + * @description If `return_user_info` in the request is true, this is the number of returned accounts in this message. Otherwise, this is the total number of accounts matching the query. */ recordsCount?: string; - /** - * If `return_user_info` in the request is true, this is the accounts matching the query. - */ + /** @description If `return_user_info` in the request is true, this is the accounts matching the query. */ userInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1UserInfo"][]; }; - /** - * Request message for ResetPassword. - */ + /** @description Request message for ResetPassword. */ GoogleCloudIdentitytoolkitV1ResetPasswordRequest: { - /** - * The email of the account to be modified. Specify this and the old password in order to change an account's password without using an out-of-band code. - */ + /** @description The email of the account to be modified. Specify this and the old password in order to change an account's password without using an out-of-band code. */ email?: string; - /** - * The new password to be set for this account. Specifying this field will result in a change to the account and consume the out-of-band code if one was specified and it was of type PASSWORD_RESET. - */ + /** @description The new password to be set for this account. Specifying this field will result in a change to the account and consume the out-of-band code if one was specified and it was of type PASSWORD_RESET. */ newPassword?: string; - /** - * The current password of the account to be modified. Specify this and email to change an account's password without using an out-of-band code. - */ + /** @description The current password of the account to be modified. Specify this and email to change an account's password without using an out-of-band code. */ oldPassword?: string; - /** - * An out-of-band (OOB) code generated by GetOobCode request. Specify only this parameter (or only this parameter and a tenant ID) to get the out-of-band code's type in the response without mutating the account's state. Only a PASSWORD_RESET out-of-band code can be consumed via this method. - */ + /** @description An out-of-band (OOB) code generated by GetOobCode request. Specify only this parameter (or only this parameter and a tenant ID) to get the out-of-band code's type in the response without mutating the account's state. Only a PASSWORD_RESET out-of-band code can be consumed via this method. */ oobCode?: string; - /** - * The tenant ID of the Identity Platform tenant the account belongs to. - */ + /** @description The tenant ID of the Identity Platform tenant the account belongs to. */ tenantId?: string; }; - /** - * Response message for ResetPassword. - */ + /** @description Response message for ResetPassword. */ GoogleCloudIdentitytoolkitV1ResetPasswordResponse: { - /** - * The email associated with the out-of-band code that was used. - */ + /** @description The email associated with the out-of-band code that was used. */ email?: string; kind?: string; mfaInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaEnrollment"]; @@ -830,66 +2350,42 @@ export interface components { | "VERIFY_AND_CHANGE_EMAIL" | "REVERT_SECOND_FACTOR_ADDITION"; }; - /** - * Request message for SendVerificationCode. At least one of (`ios_receipt` and `ios_secret`), `recaptcha_token`, or `safety_net_token` must be specified to verify the verification code is being sent on behalf of a real app and not an emulator. - */ + /** @description Request message for SendVerificationCode. At least one of (`ios_receipt` and `ios_secret`), `recaptcha_token`, or `safety_net_token` must be specified to verify the verification code is being sent on behalf of a real app and not an emulator. */ GoogleCloudIdentitytoolkitV1SendVerificationCodeRequest: { autoRetrievalInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1AutoRetrievalInfo"]; - /** - * Receipt of successful iOS app token validation. At least one of (`ios_receipt` and `ios_secret`), `recaptcha_token`, or `safety_net_token` must be specified to verify the verification code is being sent on behalf of a real app and not an emulator. This should come from the response of verifyIosClient. If present, the caller should also provide the `ios_secret`, as well as a bundle ID in the `x-ios-bundle-identifier` header, which must match the bundle ID from the verifyIosClient request. - */ + /** @description Receipt of successful iOS app token validation. At least one of (`ios_receipt` and `ios_secret`), `recaptcha_token`, or `safety_net_token` must be specified to verify the verification code is being sent on behalf of a real app and not an emulator. This should come from the response of verifyIosClient. If present, the caller should also provide the `ios_secret`, as well as a bundle ID in the `x-ios-bundle-identifier` header, which must match the bundle ID from the verifyIosClient request. */ iosReceipt?: string; - /** - * Secret delivered to iOS app as a push notification. Should be passed with an `ios_receipt` as well as the `x-ios-bundle-identifier` header. - */ + /** @description Secret delivered to iOS app as a push notification. Should be passed with an `ios_receipt` as well as the `x-ios-bundle-identifier` header. */ iosSecret?: string; - /** - * The phone number to send the verification code to in E.164 format. - */ + /** @description The phone number to send the verification code to in E.164 format. */ phoneNumber?: string; - /** - * Recaptcha token for app verification. At least one of (`ios_receipt` and `ios_secret`), `recaptcha_token`, or `safety_net_token` must be specified to verify the verification code is being sent on behalf of a real app and not an emulator. The recaptcha should be generated by calling getRecaptchaParams and the recaptcha token will be generated on user completion of the recaptcha challenge. - */ + /** @description Recaptcha token for app verification. At least one of (`ios_receipt` and `ios_secret`), `recaptcha_token`, or `safety_net_token` must be specified to verify the verification code is being sent on behalf of a real app and not an emulator. The recaptcha should be generated by calling getRecaptchaParams and the recaptcha token will be generated on user completion of the recaptcha challenge. */ recaptchaToken?: string; - /** - * Android only. Used to assert application identity in place of a recaptcha token. At least one of (`ios_receipt` and `ios_secret`), `recaptcha_token`, or `safety_net_token` must be specified to verify the verification code is being sent on behalf of a real app and not an emulator. A SafetyNet Token can be generated via the [SafetyNet Android Attestation API](https://developer.android.com/training/safetynet/attestation.html), with the Base64 encoding of the `phone_number` field as the nonce. - */ + /** @description Android only. Used to assert application identity in place of a recaptcha token. At least one of (`ios_receipt` and `ios_secret`), `recaptcha_token`, or `safety_net_token` must be specified to verify the verification code is being sent on behalf of a real app and not an emulator. A SafetyNet Token can be generated via the [SafetyNet Android Attestation API](https://developer.android.com/training/safetynet/attestation.html), with the Base64 encoding of the `phone_number` field as the nonce. */ safetyNetToken?: string; - /** - * Tenant ID of the Identity Platform tenant the user is signing in to. - */ + /** @description Tenant ID of the Identity Platform tenant the user is signing in to. */ tenantId?: string; }; - /** - * Response message for SendVerificationCode. - */ + /** @description Response message for SendVerificationCode. */ GoogleCloudIdentitytoolkitV1SendVerificationCodeResponse: { - /** - * Encrypted session information. This can be used in signInWithPhoneNumber to authenticate the phone number. - */ + /** @description Encrypted session information. This can be used in signInWithPhoneNumber to authenticate the phone number. */ sessionInfo?: string; }; - /** - * Request message for SetAccountInfo. - */ + /** @description Request message for SetAccountInfo. */ GoogleCloudIdentitytoolkitV1SetAccountInfoRequest: { captchaChallenge?: string; - /** - * The response from reCaptcha challenge. This is required when the system detects possible abuse activities. - */ + /** @description The response from reCaptcha challenge. This is required when the system detects possible abuse activities. */ captchaResponse?: string; /** - * The timestamp in milliseconds when the account was created. + * Format: int64 + * @description The timestamp in milliseconds when the account was created. */ createdAt?: string; - /** - * JSON formatted custom attributes to be stored in the Identity Platform ID token. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description JSON formatted custom attributes to be stored in the Identity Platform ID token. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). */ customAttributes?: string; + /** Format: int64 */ delegatedProjectNumber?: string; - /** - * The account's attributes to be deleted. - */ + /** @description The account's attributes to be deleted. */ deleteAttribute?: ( | "USER_ATTRIBUTE_NAME_UNSPECIFIED" | "EMAIL" @@ -899,1090 +2395,688 @@ export interface components { | "PASSWORD" | "RAW_USER_INFO" )[]; - /** - * The Identity Providers to unlink from the user's account. - */ + /** @description The Identity Providers to unlink from the user's account. */ deleteProvider?: string[]; - /** - * If true, marks the account as disabled, meaning the user will no longer be able to sign-in. - */ + /** @description If true, marks the account as disabled, meaning the user will no longer be able to sign-in. */ disableUser?: boolean; - /** - * The user's new display name to be updated in the account's attributes. The length of the display name must be less than or equal to 256 characters. - */ + /** @description The user's new display name to be updated in the account's attributes. The length of the display name must be less than or equal to 256 characters. */ displayName?: string; - /** - * The user's new email to be updated in the account's attributes. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. - */ + /** @description The user's new email to be updated in the account's attributes. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. */ email?: string; - /** - * Whether the user's email has been verified. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description Whether the user's email has been verified. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). */ emailVerified?: boolean; - /** - * A valid Identity Platform ID token. Required when attempting to change user-related information. - */ + /** @description A valid Identity Platform ID token. Required when attempting to change user-related information. */ idToken?: string; instanceId?: string; /** - * The timestamp in milliseconds when the account last logged in. + * Format: int64 + * @description The timestamp in milliseconds when the account last logged in. */ lastLoginAt?: string; linkProviderUserInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1ProviderUserInfo"]; - /** - * The ID of the user. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). For requests from end-users, an ID token should be passed instead. - */ + /** @description The ID of the user. Specifying this field requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). For requests from end-users, an ID token should be passed instead. */ localId?: string; mfa?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaInfo"]; - /** - * The out-of-band code to be applied on the user's account. The following out-of-band code types are supported: * VERIFY_EMAIL * RECOVER_EMAIL * REVERT_SECOND_FACTOR_ADDITION * VERIFY_AND_CHANGE_EMAIL - */ + /** @description The out-of-band code to be applied on the user's account. The following out-of-band code types are supported: * VERIFY_EMAIL * RECOVER_EMAIL * REVERT_SECOND_FACTOR_ADDITION * VERIFY_AND_CHANGE_EMAIL */ oobCode?: string; - /** - * The user's new password to be updated in the account's attributes. The password must be at least 6 characters long. - */ + /** @description The user's new password to be updated in the account's attributes. The password must be at least 6 characters long. */ password?: string; - /** - * The phone number to be updated in the account's attributes. - */ + /** @description The phone number to be updated in the account's attributes. */ phoneNumber?: string; - /** - * The user's new photo URL for the account's profile photo to be updated in the account's attributes. The length of the URL must be less than or equal to 2048 characters. - */ + /** @description The user's new photo URL for the account's profile photo to be updated in the account's attributes. The length of the URL must be less than or equal to 2048 characters. */ photoUrl?: string; - /** - * The Identity Providers that the account should be associated with. - */ + /** @description The Identity Providers that the account should be associated with. */ provider?: string[]; - /** - * Whether or not to return an ID and refresh token. Should always be true. - */ + /** @description Whether or not to return an ID and refresh token. Should always be true. */ returnSecureToken?: boolean; - /** - * The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead. - */ + /** @description The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead. */ targetProjectId?: string; - /** - * The tenant ID of the Identity Platform tenant that the account belongs to. Requests from end users should pass an Identity Platform ID token rather than setting this field. - */ + /** @description The tenant ID of the Identity Platform tenant that the account belongs to. Requests from end users should pass an Identity Platform ID token rather than setting this field. */ tenantId?: string; - /** - * Whether the account should be restricted to only using federated login. - */ + /** @description Whether the account should be restricted to only using federated login. */ upgradeToFederatedLogin?: boolean; /** - * Specifies the minimum timestamp in seconds for an Identity Platform ID token to be considered valid. + * Format: int64 + * @description Specifies the minimum timestamp in seconds for an Identity Platform ID token to be considered valid. */ validSince?: string; }; - /** - * Response message for SetAccountInfo - */ + /** @description Response message for SetAccountInfo */ GoogleCloudIdentitytoolkitV1SetAccountInfoResponse: { - /** - * The account's display name. - */ + /** @description The account's display name. */ displayName?: string; - /** - * The account's email address. - */ + /** @description The account's email address. */ email?: string; - /** - * Whether the account's email has been verified. - */ + /** @description Whether the account's email has been verified. */ emailVerified?: boolean; /** - * The number of seconds until the Identity Platform ID token expires. + * Format: int64 + * @description The number of seconds until the Identity Platform ID token expires. */ expiresIn?: string; - /** - * An Identity Platform ID token for the account. This is used for legacy user sign up. - */ + /** @description An Identity Platform ID token for the account. This is used for legacy user sign up. */ idToken?: string; kind?: string; - /** - * The ID of the authenticated user. - */ + /** @description The ID of the authenticated user. */ localId?: string; - /** - * The new email that has been set on the user's account attributes. - */ + /** @description The new email that has been set on the user's account attributes. */ newEmail?: string; - /** - * Deprecated. No actual password hash is currently returned. - */ + /** @description Deprecated. No actual password hash is currently returned. */ passwordHash?: string; - /** - * The user's photo URL for the account's profile photo. - */ + /** @description The user's photo URL for the account's profile photo. */ photoUrl?: string; - /** - * The linked Identity Providers on the account. - */ + /** @description The linked Identity Providers on the account. */ providerUserInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1ProviderUserInfo"][]; - /** - * A refresh token for the account. This is used for legacy user sign up. - */ + /** @description A refresh token for the account. This is used for legacy user sign up. */ refreshToken?: string; }; - /** - * Request message for SignInWithCustomToken. - */ + /** @description Request message for SignInWithCustomToken. */ GoogleCloudIdentitytoolkitV1SignInWithCustomTokenRequest: { + /** Format: int64 */ delegatedProjectNumber?: string; instanceId?: string; - /** - * Should always be true. - */ + /** @description Should always be true. */ returnSecureToken?: boolean; - /** - * The ID of the Identity Platform tenant the user is signing in to. If present, the ID should match the tenant_id in the token. - */ + /** @description The ID of the Identity Platform tenant the user is signing in to. If present, the ID should match the tenant_id in the token. */ tenantId?: string; - /** - * Required. The custom Auth token asserted by the developer. The token should be a [JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) that includes the claims listed in the [API reference](https://cloud.google.com/identity-platform/docs/reference/rest/client/) under the "Custom Token Claims" section. - */ + /** @description Required. The custom Auth token asserted by the developer. The token should be a [JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) that includes the claims listed in the [API reference](https://cloud.google.com/identity-platform/docs/reference/rest/client/) under the "Custom Token Claims" section. */ token?: string; }; - /** - * Response message for SignInWithCustomToken. - */ + /** @description Response message for SignInWithCustomToken. */ GoogleCloudIdentitytoolkitV1SignInWithCustomTokenResponse: { /** - * The number of seconds until the ID token expires. + * Format: int64 + * @description The number of seconds until the ID token expires. */ expiresIn?: string; - /** - * An Identity Platform ID token for the authenticated user. - */ + /** @description An Identity Platform ID token for the authenticated user. */ idToken?: string; - /** - * Whether the authenticated user was created by this request. - */ + /** @description Whether the authenticated user was created by this request. */ isNewUser?: boolean; kind?: string; - /** - * An Identity Platform refresh token for the authenticated user. - */ + /** @description An Identity Platform refresh token for the authenticated user. */ refreshToken?: string; }; - /** - * Request message for SignInWithEmailLink - */ + /** @description Request message for SignInWithEmailLink */ GoogleCloudIdentitytoolkitV1SignInWithEmailLinkRequest: { - /** - * Required. The email address the sign-in link was sent to. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. - */ + /** @description Required. The email address the sign-in link was sent to. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. */ email?: string; - /** - * A valid ID token for an Identity Platform account. If passed, this request will link the email address to the user represented by this ID token and enable sign-in with email link on the account for the future. - */ + /** @description A valid ID token for an Identity Platform account. If passed, this request will link the email address to the user represented by this ID token and enable sign-in with email link on the account for the future. */ idToken?: string; - /** - * Required. The out-of-band code from the email link. - */ + /** @description Required. The out-of-band code from the email link. */ oobCode?: string; - /** - * The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. */ tenantId?: string; }; - /** - * Response message for SignInWithEmailLink. - */ + /** @description Response message for SignInWithEmailLink. */ GoogleCloudIdentitytoolkitV1SignInWithEmailLinkResponse: { - /** - * The email the user signed in with. Always present in the response. - */ + /** @description The email the user signed in with. Always present in the response. */ email?: string; /** - * The number of seconds until the ID token expires. + * Format: int64 + * @description The number of seconds until the ID token expires. */ expiresIn?: string; - /** - * An Identity Platform ID token for the authenticated user. - */ + /** @description An Identity Platform ID token for the authenticated user. */ idToken?: string; - /** - * Whether the authenticated user was created by this request. - */ + /** @description Whether the authenticated user was created by this request. */ isNewUser?: boolean; kind?: string; - /** - * The ID of the authenticated user. Always present in the response. - */ + /** @description The ID of the authenticated user. Always present in the response. */ localId?: string; - /** - * Info on which multi-factor authentication providers are enabled. Present if the user needs to complete the sign-in using multi-factor authentication. - */ + /** @description Info on which multi-factor authentication providers are enabled. Present if the user needs to complete the sign-in using multi-factor authentication. */ mfaInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaEnrollment"][]; - /** - * An opaque string that functions as proof that the user has successfully passed the first factor check. - */ + /** @description An opaque string that functions as proof that the user has successfully passed the first factor check. */ mfaPendingCredential?: string; - /** - * Refresh token for the authenticated user. - */ + /** @description Refresh token for the authenticated user. */ refreshToken?: string; }; - /** - * Request message for SignInWithGameCenter - */ + /** @description Request message for SignInWithGameCenter */ GoogleCloudIdentitytoolkitV1SignInWithGameCenterRequest: { - /** - * The user's Game Center display name. - */ + /** @description The user's Game Center display name. */ displayName?: string; - /** - * A valid ID token for an Identity Platform account. If present, this request will link the Game Center player ID to the account represented by this ID token. - */ + /** @description The user's Game Center game player ID. A unique identifier for a player of the game. https://developer.apple.com/documentation/gamekit/gkplayer/3113960-gameplayerid */ + gamePlayerId?: string; + /** @description A valid ID token for an Identity Platform account. If present, this request will link the Game Center player ID to the account represented by this ID token. */ idToken?: string; - /** - * Required. The user's Game Center player ID. - */ + /** @description Required. The user's Game Center player ID. */ playerId?: string; - /** - * Required. The URL to fetch the Apple public key in order to verify the given signature is signed by Apple. - */ + /** @description Required. The URL to fetch the Apple public key in order to verify the given signature is signed by Apple. */ publicKeyUrl?: string; - /** - * Required. A random string used to generate the given signature. - */ + /** @description Required. A random string used to generate the given signature. */ salt?: string; - /** - * Required. The verification signature data generated by Apple. - */ + /** @description Required. The verification signature data generated by Apple. */ signature?: string; - /** - * The ID of the Identity Platform tenant the user is signing in to. - */ + /** @description The user's Game Center team player ID. A unique identifier for a player of all the games that you distribute using your developer account. https://developer.apple.com/documentation/gamekit/gkplayer/3174857-teamplayerid */ + teamPlayerId?: string; + /** @description The ID of the Identity Platform tenant the user is signing in to. */ tenantId?: string; /** - * Required. The time when the signature was created by Apple, in milliseconds since the epoch. + * Format: int64 + * @description Required. The time when the signature was created by Apple, in milliseconds since the epoch. */ timestamp?: string; }; - /** - * Response message for SignInWithGameCenter - */ + /** @description Response message for SignInWithGameCenter */ GoogleCloudIdentitytoolkitV1SignInWithGameCenterResponse: { - /** - * Display name of the authenticated user. - */ + /** @description Display name of the authenticated user. */ displayName?: string; /** - * The number of seconds until the ID token expires. + * Format: int64 + * @description The number of seconds until the ID token expires. */ expiresIn?: string; - /** - * An Identity Platform ID token for the authenticated user. - */ + /** @description The user's Game Center game player ID. A unique identifier for a player of the game. https://developer.apple.com/documentation/gamekit/gkplayer/3113960-gameplayerid */ + gamePlayerId?: string; + /** @description An Identity Platform ID token for the authenticated user. */ idToken?: string; - /** - * Whether the logged in user was created by this request. - */ + /** @description Whether the logged in user was created by this request. */ isNewUser?: boolean; - /** - * The ID of the authenticated user. Always present in the response. - */ + /** @description The ID of the authenticated user. Always present in the response. */ localId?: string; - /** - * The user's Game Center player ID. - */ + /** @description The user's Game Center player ID. */ playerId?: string; - /** - * An Identity Platform refresh token for the authenticated user. - */ + /** @description An Identity Platform refresh token for the authenticated user. */ refreshToken?: string; + /** @description The user's Game Center team player ID. A unique identifier for a player of all the games that you distribute using your developer account. https://developer.apple.com/documentation/gamekit/gkplayer/3174857-teamplayerid */ + teamPlayerId?: string; }; - /** - * Request message for SignInWithIdp. - */ + /** @description Request message for SignInWithIdp. */ GoogleCloudIdentitytoolkitV1SignInWithIdpRequest: { autoCreate?: boolean; + /** Format: int64 */ delegatedProjectNumber?: string; - /** - * A valid Identity Platform ID token. If passed, the user's account at the IdP will be linked to the account represented by this ID token. - */ + /** @description A valid Identity Platform ID token. If passed, the user's account at the IdP will be linked to the account represented by this ID token. */ idToken?: string; pendingIdToken?: string; - /** - * An opaque string from a previous SignInWithIdp response. If set, it can be used to repeat the sign-in operation from the previous SignInWithIdp operation. - */ + /** @description An opaque string from a previous SignInWithIdp response. If set, it can be used to repeat the sign-in operation from the previous SignInWithIdp operation. */ pendingToken?: string; - /** - * If the user is signing in with an authorization response obtained via a previous CreateAuthUri authorization request, this is the body of the HTTP POST callback from the IdP, if present. Otherwise, if the user is signing in with a manually provided IdP credential, this should be a URL-encoded form that contains the credential (e.g. an ID token or access token for OAuth 2.0 IdPs) and the provider ID of the IdP that issued the credential. For example, if the user is signing in to the Google provider using a Google ID token, this should be set to `id_token=[GOOGLE_ID_TOKEN]&providerId=google.com`, where `[GOOGLE_ID_TOKEN]` should be replaced with the Google ID token. If the user is signing in to the Facebook provider using a Facebook authentication token, this should be set to `id_token=[FACEBOOK_AUTHENTICATION_TOKEN]&providerId=facebook.com&nonce= [NONCE]`, where `[FACEBOOK_AUTHENTICATION_TOKEN]` should be replaced with the Facebook authentication token. Nonce is required for validating the token. The request will fail if no nonce is provided. If the user is signing in to the Facebook provider using a Facebook access token, this should be set to `access_token=[FACEBOOK_ACCESS_TOKEN]&providerId=facebook.com`, where `[FACEBOOK_ACCESS_TOKEN]` should be replaced with the Facebook access token. If the user is signing in to the Twitter provider using a Twitter OAuth 1.0 credential, this should be set to `access_token=[TWITTER_ACCESS_TOKEN]&oauth_token_secret=[TWITTER_TOKEN_SECRET]&providerId=twitter.com`, where `[TWITTER_ACCESS_TOKEN]` and `[TWITTER_TOKEN_SECRET]` should be replaced with the Twitter OAuth access token and Twitter OAuth token secret respectively. - */ + /** @description If the user is signing in with an authorization response obtained via a previous CreateAuthUri authorization request, this is the body of the HTTP POST callback from the IdP, if present. Otherwise, if the user is signing in with a manually provided IdP credential, this should be a URL-encoded form that contains the credential (e.g. an ID token or access token for OAuth 2.0 IdPs) and the provider ID of the IdP that issued the credential. For example, if the user is signing in to the Google provider using a Google ID token, this should be set to `id_token=[GOOGLE_ID_TOKEN]&providerId=google.com`, where `[GOOGLE_ID_TOKEN]` should be replaced with the Google ID token. If the user is signing in to the Facebook provider using a Facebook authentication token, this should be set to `id_token=[FACEBOOK_AUTHENTICATION_TOKEN]&providerId=facebook.com&nonce= [NONCE]`, where `[FACEBOOK_AUTHENTICATION_TOKEN]` should be replaced with the Facebook authentication token. Nonce is required for validating the token. The request will fail if no nonce is provided. If the user is signing in to the Facebook provider using a Facebook access token, this should be set to `access_token=[FACEBOOK_ACCESS_TOKEN]&providerId=facebook.com`, where `[FACEBOOK_ACCESS_TOKEN]` should be replaced with the Facebook access token. If the user is signing in to the Twitter provider using a Twitter OAuth 1.0 credential, this should be set to `access_token=[TWITTER_ACCESS_TOKEN]&oauth_token_secret=[TWITTER_TOKEN_SECRET]&providerId=twitter.com`, where `[TWITTER_ACCESS_TOKEN]` and `[TWITTER_TOKEN_SECRET]` should be replaced with the Twitter OAuth access token and Twitter OAuth token secret respectively. */ postBody?: string; - /** - * Required. The URL to which the IdP redirects the user back. This can be set to `http://localhost` if the user is signing in with a manually provided IdP credential. - */ + /** @description Required. The URL to which the IdP redirects the user back. This can be set to `http://localhost` if the user is signing in with a manually provided IdP credential. */ requestUri?: string; - /** - * Whether or not to return OAuth credentials from the IdP on the following errors: `FEDERATED_USER_ID_ALREADY_LINKED` and `EMAIL_EXISTS`. - */ + /** @description Whether or not to return OAuth credentials from the IdP on the following errors: `FEDERATED_USER_ID_ALREADY_LINKED` and `EMAIL_EXISTS`. */ returnIdpCredential?: boolean; - /** - * Whether or not to return the OAuth refresh token from the IdP, if available. - */ + /** @description Whether or not to return the OAuth refresh token from the IdP, if available. */ returnRefreshToken?: boolean; - /** - * Should always be true. - */ + /** @description Should always be true. */ returnSecureToken?: boolean; - /** - * The session ID returned from a previous CreateAuthUri call. This field is verified against that session ID to prevent session fixation attacks. Required if the user is signing in with an authorization response from a previous CreateAuthUri authorization request. - */ + /** @description The session ID returned from a previous CreateAuthUri call. This field is verified against that session ID to prevent session fixation attacks. Required if the user is signing in with an authorization response from a previous CreateAuthUri authorization request. */ sessionId?: string; - /** - * The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. */ tenantId?: string; }; - /** - * Response message for SignInWithIdp. - */ + /** @description Response message for SignInWithIdp. */ GoogleCloudIdentitytoolkitV1SignInWithIdpResponse: { - /** - * The opaque string set in CreateAuthUri that is used to maintain contextual information between the authentication request and the callback from the IdP. - */ + /** @description The opaque string set in CreateAuthUri that is used to maintain contextual information between the authentication request and the callback from the IdP. */ context?: string; - /** - * The date of birth for the user's account at the IdP. - */ + /** @description The date of birth for the user's account at the IdP. */ dateOfBirth?: string; - /** - * The display name for the user's account at the IdP. - */ + /** @description The display name for the user's account at the IdP. */ displayName?: string; - /** - * The email address of the user's account at the IdP. - */ + /** @description The email address of the user's account at the IdP. */ email?: string; - /** - * Whether or not there is an existing Identity Platform user account with the same email address but linked to a different account at the same IdP. Only present if the "One account per email address" setting is enabled and the email address at the IdP is verified. - */ + /** @description Whether or not there is an existing Identity Platform user account with the same email address but linked to a different account at the same IdP. Only present if the "One account per email address" setting is enabled and the email address at the IdP is verified. */ emailRecycled?: boolean; - /** - * Whether the user account's email address is verified. - */ + /** @description Whether the user account's email address is verified. */ emailVerified?: boolean; - /** - * The error message returned if `return_idp_credential` is set to `true` and either the `FEDERATED_USER_ID_ALREADY_LINKED` or `EMAIL_EXISTS` error is encountered. This field's value is either `FEDERATED_USER_ID_ALREADY_LINKED` or `EMAIL_EXISTS`. - */ + /** @description The error message returned if `return_idp_credential` is set to `true` and either the `FEDERATED_USER_ID_ALREADY_LINKED` or `EMAIL_EXISTS` error is encountered. This field's value is either `FEDERATED_USER_ID_ALREADY_LINKED` or `EMAIL_EXISTS`. */ errorMessage?: string; /** - * The number of seconds until the Identity Platform ID token expires. + * Format: int64 + * @description The number of seconds until the Identity Platform ID token expires. */ expiresIn?: string; - /** - * The user's account ID at the IdP. Always present in the response. - */ + /** @description The user's account ID at the IdP. Always present in the response. */ federatedId?: string; - /** - * The first name for the user's account at the IdP. - */ + /** @description The first name for the user's account at the IdP. */ firstName?: string; - /** - * The full name for the user's account at the IdP. - */ + /** @description The full name for the user's account at the IdP. */ fullName?: string; - /** - * An Identity Platform ID token for the authenticated user. - */ + /** @description An Identity Platform ID token for the authenticated user. */ idToken?: string; inputEmail?: string; - /** - * Whether or not a new Identity Platform account was created for the authenticated user. - */ + /** @description Whether or not a new Identity Platform account was created for the authenticated user. */ isNewUser?: boolean; kind?: string; - /** - * The language preference for the user's account at the IdP. - */ + /** @description The language preference for the user's account at the IdP. */ language?: string; - /** - * The last name for the user's account at the IdP. - */ + /** @description The last name for the user's account at the IdP. */ lastName?: string; - /** - * The ID of the authenticated Identity Platform user. Always present in the response. - */ + /** @description The ID of the authenticated Identity Platform user. Always present in the response. */ localId?: string; - /** - * Info on which multi-factor authentication providers are enabled for the account. Present if the user needs to complete the sign-in using multi-factor authentication. - */ + /** @description Info on which multi-factor authentication providers are enabled for the account. Present if the user needs to complete the sign-in using multi-factor authentication. */ mfaInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaEnrollment"][]; - /** - * An opaque string that functions as proof that the user has successfully passed the first factor authentication. - */ + /** @description An opaque string that functions as proof that the user has successfully passed the first factor authentication. */ mfaPendingCredential?: string; - /** - * Whether or not there is an existing Identity Platform user account with the same email address as the current account signed in at the IdP, and the account's email addresss is not verified at the IdP. The user will need to sign in to the existing Identity Platform account and then link the current credential from the IdP to it. Only present if the "One account per email address" setting is enabled. - */ + /** @description Whether or not there is an existing Identity Platform user account with the same email address as the current account signed in at the IdP, and the account's email addresss is not verified at the IdP. The user will need to sign in to the existing Identity Platform account and then link the current credential from the IdP to it. Only present if the "One account per email address" setting is enabled. */ needConfirmation?: boolean; needEmail?: boolean; - /** - * The nickname for the user's account at the IdP. - */ + /** @description The nickname for the user's account at the IdP. */ nickName?: string; - /** - * The OAuth access token from the IdP, if available. - */ + /** @description The OAuth access token from the IdP, if available. */ oauthAccessToken?: string; - /** - * The OAuth 2.0 authorization code, if available. Only present for the Google provider. - */ + /** @description The OAuth 2.0 authorization code, if available. Only present for the Google provider. */ oauthAuthorizationCode?: string; /** - * The number of seconds until the OAuth access token from the IdP expires. + * Format: int32 + * @description The number of seconds until the OAuth access token from the IdP expires. */ oauthExpireIn?: number; - /** - * The OpenID Connect ID token from the IdP, if available. - */ + /** @description The OpenID Connect ID token from the IdP, if available. */ oauthIdToken?: string; - /** - * The OAuth 2.0 refresh token from the IdP, if available and `return_refresh_token` is set to `true`. - */ + /** @description The OAuth 2.0 refresh token from the IdP, if available and `return_refresh_token` is set to `true`. */ oauthRefreshToken?: string; - /** - * The OAuth 1.0 token secret from the IdP, if available. Only present for the Twitter provider. - */ + /** @description The OAuth 1.0 token secret from the IdP, if available. Only present for the Twitter provider. */ oauthTokenSecret?: string; - /** - * The main (top-level) email address of the user's Identity Platform account, if different from the email address at the IdP. Only present if the "One account per email address" setting is enabled. - */ + /** @description The main (top-level) email address of the user's Identity Platform account, if different from the email address at the IdP. Only present if the "One account per email address" setting is enabled. */ originalEmail?: string; - /** - * An opaque string that can be used as a credential from the IdP the user is signing into. The pending token obtained here can be set in a future SignInWithIdp request to sign the same user in with the IdP again. - */ + /** @description An opaque string that can be used as a credential from the IdP the user is signing into. The pending token obtained here can be set in a future SignInWithIdp request to sign the same user in with the IdP again. */ pendingToken?: string; - /** - * The URL of the user's profile picture at the IdP. - */ + /** @description The URL of the user's profile picture at the IdP. */ photoUrl?: string; - /** - * The provider ID of the IdP that the user is signing in to. Always present in the response. - */ + /** @description The provider ID of the IdP that the user is signing in to. Always present in the response. */ providerId?: string; - /** - * The stringified JSON response containing all the data corresponding to the user's account at the IdP. - */ + /** @description The stringified JSON response containing all the data corresponding to the user's account at the IdP. */ rawUserInfo?: string; - /** - * An Identity Platform refresh token for the authenticated user. - */ + /** @description An Identity Platform refresh token for the authenticated user. */ refreshToken?: string; - /** - * The screen name for the user's account at the Twitter IdP or the login name for the user's account at the GitHub IdP. - */ + /** @description The screen name for the user's account at the Twitter IdP or the login name for the user's account at the GitHub IdP. */ screenName?: string; - /** - * The value of the `tenant_id` field in the request. - */ + /** @description The value of the `tenant_id` field in the request. */ tenantId?: string; - /** - * The time zone for the user's account at the IdP. - */ + /** @description The time zone for the user's account at the IdP. */ timeZone?: string; - /** - * A list of provider IDs that the user can sign in to in order to resolve a `need_confirmation` error. Only present if `need_confirmation` is set to `true`. - */ + /** @description A list of provider IDs that the user can sign in to in order to resolve a `need_confirmation` error. Only present if `need_confirmation` is set to `true`. */ verifiedProvider?: string[]; }; - /** - * Request message for SignInWithPassword. - */ + /** @description Request message for SignInWithPassword. */ GoogleCloudIdentitytoolkitV1SignInWithPasswordRequest: { captchaChallenge?: string; - /** - * The reCAPTCHA token provided by the reCAPTCHA client-side integration. reCAPTCHA Enterprise uses it for risk assessment. Required when reCAPTCHA Enterprise is enabled. - */ + /** @description The reCAPTCHA token provided by the reCAPTCHA client-side integration. reCAPTCHA Enterprise uses it for risk assessment. Required when reCAPTCHA Enterprise is enabled. */ captchaResponse?: string; + /** Format: int64 */ delegatedProjectNumber?: string; - /** - * Required. The email the user is signing in with. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. - */ + /** @description Required. The email the user is signing in with. The length of email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. */ email?: string; idToken?: string; instanceId?: string; - /** - * Required. The password the user provides to sign in to the account. - */ + /** @description Required. The password the user provides to sign in to the account. */ password?: string; pendingIdToken?: string; - /** - * Should always be true. - */ + /** @description Should always be true. */ returnSecureToken?: boolean; - /** - * The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform instance in the project. - */ + /** @description The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform instance in the project. */ tenantId?: string; }; - /** - * Response message for SignInWithPassword. - */ + /** @description Response message for SignInWithPassword. */ GoogleCloudIdentitytoolkitV1SignInWithPasswordResponse: { - /** - * The user's display name stored in the account's attributes. - */ + /** @description The user's display name stored in the account's attributes. */ displayName?: string; - /** - * The email of the authenticated user. Always present in the response. - */ + /** @description The email of the authenticated user. Always present in the response. */ email?: string; /** - * The number of seconds until the Identity Platform ID token expires. + * Format: int64 + * @description The number of seconds until the Identity Platform ID token expires. */ expiresIn?: string; - /** - * An Identity Platform ID token for the authenticated user. - */ + /** @description An Identity Platform ID token for the authenticated user. */ idToken?: string; kind?: string; - /** - * The ID of the authenticated user. Always present in the response. - */ + /** @description The ID of the authenticated user. Always present in the response. */ localId?: string; - /** - * Info on which multi-factor authentication providers are enabled for the account. Present if the user needs to complete the sign-in using multi-factor authentication. - */ + /** @description Info on which multi-factor authentication providers are enabled for the account. Present if the user needs to complete the sign-in using multi-factor authentication. */ mfaInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaEnrollment"][]; - /** - * An opaque string that functions as proof that the user has successfully passed the first factor authentication. - */ + /** @description An opaque string that functions as proof that the user has successfully passed the first factor authentication. */ mfaPendingCredential?: string; - /** - * The OAuth2 access token. - */ + /** @description The OAuth2 access token. */ oauthAccessToken?: string; oauthAuthorizationCode?: string; /** - * The access token expiration time in seconds. + * Format: int32 + * @description The access token expiration time in seconds. */ oauthExpireIn?: number; - /** - * The user's profile picture stored in the account's attributes. - */ + /** @description The user's profile picture stored in the account's attributes. */ profilePicture?: string; - /** - * An Identity Platform refresh token for the authenticated user. - */ + /** @description An Identity Platform refresh token for the authenticated user. */ refreshToken?: string; - /** - * Whether the email is for an existing account. Always true. - */ + /** @description Whether the email is for an existing account. Always true. */ registered?: boolean; }; - /** - * Request message for SignInWithPhoneNumber. - */ + /** @description Request message for SignInWithPhoneNumber. */ GoogleCloudIdentitytoolkitV1SignInWithPhoneNumberRequest: { - /** - * User-entered verification code from an SMS sent to the user's phone. - */ + /** @description User-entered verification code from an SMS sent to the user's phone. */ code?: string; - /** - * A valid ID token for an Identity Platform account. If passed, this request will link the phone number to the user represented by this ID token if the phone number is not in use, or will reauthenticate the user if the phone number is already linked to the user. - */ + /** @description A valid ID token for an Identity Platform account. If passed, this request will link the phone number to the user represented by this ID token if the phone number is not in use, or will reauthenticate the user if the phone number is already linked to the user. */ idToken?: string; operation?: "VERIFY_OP_UNSPECIFIED" | "SIGN_UP_OR_IN" | "REAUTH" | "UPDATE" | "LINK"; - /** - * The user's phone number to sign in with. This is necessary in the case of uing a temporary proof, in which case it must match the phone number that was authenticated in the request that generated the temporary proof. This field is ignored if a session info is passed. - */ + /** @description The user's phone number to sign in with. This is necessary in the case of uing a temporary proof, in which case it must match the phone number that was authenticated in the request that generated the temporary proof. This field is ignored if a session info is passed. */ phoneNumber?: string; - /** - * Encrypted session information from the response of sendVerificationCode. In the case of authenticating with an SMS code this must be specified, but in the case of using a temporary proof it can be unspecified. - */ + /** @description Encrypted session information from the response of sendVerificationCode. In the case of authenticating with an SMS code this must be specified, but in the case of using a temporary proof it can be unspecified. */ sessionInfo?: string; - /** - * A proof of the phone number verification, provided from a previous signInWithPhoneNumber request. If this is passed, the caller must also pass in the phone_number field the phone number that was verified in the previous request. - */ + /** @description A proof of the phone number verification, provided from a previous signInWithPhoneNumber request. If this is passed, the caller must also pass in the phone_number field the phone number that was verified in the previous request. */ temporaryProof?: string; - /** - * The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. */ tenantId?: string; - /** - * Do not use. - */ + /** @description Do not use. */ verificationProof?: string; }; - /** - * Response message for SignInWithPhoneNumber. - */ + /** @description Response message for SignInWithPhoneNumber. */ GoogleCloudIdentitytoolkitV1SignInWithPhoneNumberResponse: { /** - * The number of seconds until the ID token expires. + * Format: int64 + * @description The number of seconds until the ID token expires. */ expiresIn?: string; - /** - * Identity Platform ID token for the authenticated user. - */ + /** @description Identity Platform ID token for the authenticated user. */ idToken?: string; - /** - * Whether the authenticated user was created by this request. - */ + /** @description Whether the authenticated user was created by this request. */ isNewUser?: boolean; - /** - * The id of the authenticated user. Present in the case of a successful authentication. In the case when the phone could be verified but the account operation could not be performed, a temporary proof will be returned instead. - */ + /** @description The id of the authenticated user. Present in the case of a successful authentication. In the case when the phone could be verified but the account operation could not be performed, a temporary proof will be returned instead. */ localId?: string; - /** - * Phone number of the authenticated user. Always present in the response. - */ + /** @description Phone number of the authenticated user. Always present in the response. */ phoneNumber?: string; - /** - * Refresh token for the authenticated user. - */ + /** @description Refresh token for the authenticated user. */ refreshToken?: string; + /** @description A proof of the phone number verification, provided if a phone authentication is successful but the user operation fails. This happens when the request tries to link a phone number to a user with an ID token or reauthenticate with an ID token but the phone number is linked to a different user. */ + temporaryProof?: string; /** - * A proof of the phone number verification, provided if a phone authentication is successful but the user operation fails. This happens when the request tries to link a phone number to a user with an ID token or reauthenticate with an ID token but the phone number is linked to a different user. - */ - temporaryProof?: string; - /** - * The number of seconds until the temporary proof expires. + * Format: int64 + * @description The number of seconds until the temporary proof expires. */ temporaryProofExpiresIn?: string; - /** - * Do not use. - */ + /** @description Do not use. */ verificationProof?: string; /** - * Do not use. + * Format: int64 + * @description Do not use. */ verificationProofExpiresIn?: string; }; - /** - * Request message for SignUp. - */ + /** @description Request message for SignUp. */ GoogleCloudIdentitytoolkitV1SignUpRequest: { captchaChallenge?: string; - /** - * The reCAPTCHA token provided by the reCAPTCHA client-side integration. reCAPTCHA Enterprise uses it for assessment. Required when reCAPTCHA enterprise is enabled. - */ + /** @description The reCAPTCHA token provided by the reCAPTCHA client-side integration. reCAPTCHA Enterprise uses it for assessment. Required when reCAPTCHA enterprise is enabled. */ captchaResponse?: string; - /** - * Whether the user will be disabled upon creation. Disabled accounts are inaccessible except for requests bearing a Google OAuth2 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description Whether the user will be disabled upon creation. Disabled accounts are inaccessible except for requests bearing a Google OAuth2 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ disabled?: boolean; - /** - * The display name of the user to be created. - */ + /** @description The display name of the user to be created. */ displayName?: string; - /** - * The email to assign to the created user. The length of the email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. An anonymous user will be created if not provided. - */ + /** @description The email to assign to the created user. The length of the email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec production. An anonymous user will be created if not provided. */ email?: string; - /** - * Whether the user's email is verified. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description Whether the user's email is verified. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ emailVerified?: boolean; - /** - * A valid ID token for an Identity Platform user. If set, this request will link the authentication credential to the user represented by this ID token. For a non-admin request, both the `email` and `password` fields must be set. For an admin request, `local_id` must not be set. - */ + /** @description A valid ID token for an Identity Platform user. If set, this request will link the authentication credential to the user represented by this ID token. For a non-admin request, both the `email` and `password` fields must be set. For an admin request, `local_id` must not be set. */ idToken?: string; instanceId?: string; - /** - * The ID of the user to create. The ID must be unique within the project that the user is being created under. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description The ID of the user to create. The ID must be unique within the project that the user is being created under. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ localId?: string; - /** - * The multi-factor authentication providers for the user to create. - */ + /** @description The multi-factor authentication providers for the user to create. */ mfaInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaFactor"][]; - /** - * The password to assign to the created user. The password must be be at least 6 characters long. If set, the `email` field must also be set. - */ + /** @description The password to assign to the created user. The password must be be at least 6 characters long. If set, the `email` field must also be set. */ password?: string; - /** - * The phone number of the user to create. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). - */ + /** @description The phone number of the user to create. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ phoneNumber?: string; - /** - * The profile photo url of the user to create. - */ + /** @description The profile photo url of the user to create. */ photoUrl?: string; - /** - * The project ID of the project which the user should belong to. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). If this is not set, the target project is inferred from the scope associated to the Bearer access token. - */ + /** @description The project ID of the project which the user should belong to. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). If this is not set, the target project is inferred from the scope associated to the Bearer access token. */ targetProjectId?: string; - /** - * The ID of the Identity Platform tenant to create a user under. If not set, the user will be created under the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant to create a user under. If not set, the user will be created under the default Identity Platform project. */ tenantId?: string; }; - /** - * Response message for SignUp. - */ + /** @description Response message for SignUp. */ GoogleCloudIdentitytoolkitV1SignUpResponse: { - /** - * The created user's display name. - */ + /** @description The created user's display name. */ displayName?: string; - /** - * The created user's email. - */ + /** @description The created user's email. */ email?: string; /** - * The number of seconds until the ID token expires. + * Format: int64 + * @description The number of seconds until the ID token expires. */ expiresIn?: string; - /** - * An Identity Platform ID token for the created user. This field is only set for non-admin requests. - */ + /** @description An Identity Platform ID token for the created user. This field is only set for non-admin requests. */ idToken?: string; kind?: string; - /** - * The ID of the created user. Always present in the response. - */ + /** @description The ID of the created user. Always present in the response. */ localId?: string; - /** - * An Identity Platform refresh token for the created user. This field is only set for non-admin requests. - */ + /** @description An Identity Platform refresh token for the created user. This field is only set for non-admin requests. */ refreshToken?: string; }; - /** - * Query conditions used to filter results. - */ + /** @description Query conditions used to filter results. */ GoogleCloudIdentitytoolkitV1SqlExpression: { - /** - * A case insensitive string that the account's email should match. Only one of `email`, `phone_number`, or `user_id` should be specified in a SqlExpression. If more than one is specified, only the first (in that order) will be applied. - */ + /** @description A case insensitive string that the account's email should match. Only one of `email`, `phone_number`, or `user_id` should be specified in a SqlExpression. If more than one is specified, only the first (in that order) will be applied. */ email?: string; - /** - * A string that the account's phone number should match. Only one of `email`, `phone_number`, or `user_id` should be specified in a SqlExpression. If more than one is specified, only the first (in that order) will be applied. - */ + /** @description A string that the account's phone number should match. Only one of `email`, `phone_number`, or `user_id` should be specified in a SqlExpression. If more than one is specified, only the first (in that order) will be applied. */ phoneNumber?: string; - /** - * A string that the account's local ID should match. Only one of `email`, `phone_number`, or `user_id` should be specified in a SqlExpression If more than one is specified, only the first (in that order) will be applied. - */ + /** @description A string that the account's local ID should match. Only one of `email`, `phone_number`, or `user_id` should be specified in a SqlExpression If more than one is specified, only the first (in that order) will be applied. */ userId?: string; }; - /** - * Request message for UploadAccount. - */ + /** @description Request message for UploadAccount. */ GoogleCloudIdentitytoolkitV1UploadAccountRequest: { - /** - * Whether to overwrite an existing account in Identity Platform with a matching `local_id` in the request. If true, the existing account will be overwritten. If false, an error will be returned. - */ + /** @description Whether to overwrite an existing account in Identity Platform with a matching `local_id` in the request. If true, the existing account will be overwritten. If false, an error will be returned. */ allowOverwrite?: boolean; argon2Parameters?: components["schemas"]["GoogleCloudIdentitytoolkitV1Argon2Parameters"]; /** - * The block size parameter used by the STANDARD_SCRYPT hashing function. This parameter, along with parallelization and cpu_mem_cost help tune the resources needed to hash a password, and should be tuned as processor speeds and memory technologies advance. + * Format: int32 + * @description The block size parameter used by the STANDARD_SCRYPT hashing function. This parameter, along with parallelization and cpu_mem_cost help tune the resources needed to hash a password, and should be tuned as processor speeds and memory technologies advance. */ blockSize?: number; /** - * The CPU memory cost parameter to be used by the STANDARD_SCRYPT hashing function. This parameter, along with block_size and cpu_mem_cost help tune the resources needed to hash a password, and should be tuned as processor speeds and memory technologies advance. + * Format: int32 + * @description The CPU memory cost parameter to be used by the STANDARD_SCRYPT hashing function. This parameter, along with block_size and cpu_mem_cost help tune the resources needed to hash a password, and should be tuned as processor speeds and memory technologies advance. */ cpuMemCost?: number; + /** Format: int64 */ delegatedProjectNumber?: string; /** - * The desired key length for the STANDARD_SCRYPT hashing function. Must be at least 1. + * Format: int32 + * @description The desired key length for the STANDARD_SCRYPT hashing function. Must be at least 1. */ dkLen?: number; - /** - * Required. The hashing function used to hash the account passwords. Must be one of the following: * HMAC_SHA256 * HMAC_SHA1 * HMAC_MD5 * SCRYPT * PBKDF_SHA1 * MD5 * HMAC_SHA512 * SHA1 * BCRYPT * PBKDF2_SHA256 * SHA256 * SHA512 * STANDARD_SCRYPT * ARGON2 - */ + /** @description Required. The hashing function used to hash the account passwords. Must be one of the following: * HMAC_SHA256 * HMAC_SHA1 * HMAC_MD5 * SCRYPT * PBKDF_SHA1 * MD5 * HMAC_SHA512 * SHA1 * BCRYPT * PBKDF2_SHA256 * SHA256 * SHA512 * STANDARD_SCRYPT * ARGON2 */ hashAlgorithm?: string; /** - * Memory cost for hash calculation. Only required when the hashing function is SCRYPT. + * Format: int32 + * @description Memory cost for hash calculation. Only required when the hashing function is SCRYPT. */ memoryCost?: number; /** - * The parallelization cost parameter to be used by the STANDARD_SCRYPT hashing function. This parameter, along with block_size and cpu_mem_cost help tune the resources needed to hash a password, and should be tuned as processor speeds and memory technologies advance. + * Format: int32 + * @description The parallelization cost parameter to be used by the STANDARD_SCRYPT hashing function. This parameter, along with block_size and cpu_mem_cost help tune the resources needed to hash a password, and should be tuned as processor speeds and memory technologies advance. */ parallelization?: number; - /** - * Password and salt order when verify password. - */ + /** @description Password and salt order when verify password. */ passwordHashOrder?: "UNSPECIFIED_ORDER" | "SALT_AND_PASSWORD" | "PASSWORD_AND_SALT"; /** - * The number of rounds used for hash calculation. Only required for the following hashing functions: * MD5 * SHA1 * SHA256 * SHA512 * PBKDF_SHA1 * PBKDF2_SHA256 * SCRYPT + * Format: int32 + * @description The number of rounds used for hash calculation. Only required for the following hashing functions: * MD5 * SHA1 * SHA256 * SHA512 * PBKDF_SHA1 * PBKDF2_SHA256 * SCRYPT */ rounds?: number; /** - * One or more bytes to be inserted between the salt and plain text password. For stronger security, this should be a single non-printable character. + * Format: byte + * @description One or more bytes to be inserted between the salt and plain text password. For stronger security, this should be a single non-printable character. */ saltSeparator?: string; - /** - * If true, the service will do the following list of checks before an account is uploaded: * Duplicate emails * Duplicate federated IDs * Federated ID provider validation If the duplication exists within the list of accounts to be uploaded, it will prevent the entire list from being uploaded. If the email or federated ID is a duplicate of a user already within the project/tenant, the account will not be uploaded, but the rest of the accounts will be unaffected. If false, these checks will be skipped. - */ + /** @description If true, the service will do the following list of checks before an account is uploaded: * Duplicate emails * Duplicate federated IDs * Federated ID provider validation If the duplication exists within the list of accounts to be uploaded, it will prevent the entire list from being uploaded. If the email or federated ID is a duplicate of a user already within the project/tenant, the account will not be uploaded, but the rest of the accounts will be unaffected. If false, these checks will be skipped. */ sanityCheck?: boolean; /** - * The signer key used to hash the password. Required for the following hashing functions: * SCRYPT, * HMAC_MD5, * HMAC_SHA1, * HMAC_SHA256, * HMAC_SHA512 + * Format: byte + * @description The signer key used to hash the password. Required for the following hashing functions: * SCRYPT, * HMAC_MD5, * HMAC_SHA1, * HMAC_SHA256, * HMAC_SHA512 */ signerKey?: string; - /** - * The ID of the Identity Platform tenant the account belongs to. - */ + /** @description The ID of the Identity Platform tenant the account belongs to. */ tenantId?: string; - /** - * A list of accounts to upload. `local_id` is required for each user; everything else is optional. - */ + /** @description A list of accounts to upload. `local_id` is required for each user; everything else is optional. */ users?: components["schemas"]["GoogleCloudIdentitytoolkitV1UserInfo"][]; }; - /** - * Response message for UploadAccount. - */ + /** @description Response message for UploadAccount. */ GoogleCloudIdentitytoolkitV1UploadAccountResponse: { - /** - * Detailed error info for accounts that cannot be uploaded. - */ + /** @description Detailed error info for accounts that cannot be uploaded. */ error?: components["schemas"]["GoogleCloudIdentitytoolkitV1ErrorInfo"][]; kind?: string; }; - /** - * An Identity Platform account's information. - */ + /** @description An Identity Platform account's information. */ GoogleCloudIdentitytoolkitV1UserInfo: { /** - * The time, in milliseconds from epoch, when the account was created. + * Format: int64 + * @description The time, in milliseconds from epoch, when the account was created. */ createdAt?: string; - /** - * Custom claims to be added to any ID tokens minted for the account. Should be at most 1,000 characters in length and in valid JSON format. - */ + /** @description Custom claims to be added to any ID tokens minted for the account. Should be at most 1,000 characters in length and in valid JSON format. */ customAttributes?: string; - /** - * Output only. Whether this account has been authenticated using SignInWithCustomToken. - */ + /** @description Output only. Whether this account has been authenticated using SignInWithCustomToken. */ customAuth?: boolean; - /** - * Output only. The date of birth set for the account. This account attribute is not used by Identity Platform. It is available for informational purposes only. - */ + /** @description Output only. The date of birth set for the account. This account attribute is not used by Identity Platform. It is available for informational purposes only. */ dateOfBirth?: string; - /** - * Whether the account is disabled. Disabled accounts are inaccessible except for requests bearing a Google OAuth2 credential with proper permissions. - */ + /** @description Whether the account is disabled. Disabled accounts are inaccessible except for requests bearing a Google OAuth2 credential with proper permissions. */ disabled?: boolean; - /** - * The display name of the account. This account attribute is not used by Identity Platform. It is available for informational purposes only. - */ + /** @description The display name of the account. This account attribute is not used by Identity Platform. It is available for informational purposes only. */ displayName?: string; - /** - * The account's email address. The length of the email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec. - */ + /** @description The account's email address. The length of the email should be less than 256 characters and in the format of `name@domain.tld`. The email should also match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec. */ email?: string; - /** - * Output only. Whether the account can authenticate with email link. - */ + /** @description Output only. Whether the account can authenticate with email link. */ emailLinkSignin?: boolean; - /** - * Whether the account's email address has been verified. - */ + /** @description Whether the account's email address has been verified. */ emailVerified?: boolean; - /** - * The first email address associated with this account. The account's initial email cannot be changed once set and is used to recover access to this account if lost via the RECOVER_EMAIL flow in GetOobCode. Should match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec. - */ + /** @description The first email address associated with this account. The account's initial email cannot be changed once set and is used to recover access to this account if lost via the RECOVER_EMAIL flow in GetOobCode. Should match the [RFC 822](https://tools.ietf.org/html/rfc822) addr-spec. */ initialEmail?: string; - /** - * Output only. The language preference of the account. This account attribute is not used by Identity Platform. It is available for informational purposes only. - */ + /** @description Output only. The language preference of the account. This account attribute is not used by Identity Platform. It is available for informational purposes only. */ language?: string; /** - * The last time, in milliseconds from epoch, this account was logged into. + * Format: int64 + * @description The last time, in milliseconds from epoch, this account was logged into. */ lastLoginAt?: string; /** - * Timestamp when an ID token was last minted for this account. + * Format: google-datetime + * @description Timestamp when an ID token was last minted for this account. */ lastRefreshAt?: string; - /** - * Immutable. The unique ID of the account. - */ + /** @description Immutable. The unique ID of the account. */ localId?: string; - /** - * Information on which multi-factor authentication providers are enabled for this account. - */ + /** @description Information on which multi-factor authentication providers are enabled for this account. */ mfaInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1MfaEnrollment"][]; /** - * The account's hashed password. Only accessible by requests bearing a Google OAuth2 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). + * Format: byte + * @description The account's hashed password. Only accessible by requests bearing a Google OAuth2 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ passwordHash?: string; /** - * The timestamp, in milliseconds from the epoch of 1970-01-01T00:00:00Z, when the account's password was last updated. + * Format: double + * @description The timestamp, in milliseconds from the epoch of 1970-01-01T00:00:00Z, when the account's password was last updated. */ passwordUpdatedAt?: number; - /** - * The account's phone number. - */ + /** @description The account's phone number. */ phoneNumber?: string; - /** - * The URL of the account's profile photo. This account attribute is not used by Identity Platform. It is available for informational purposes only. - */ + /** @description The URL of the account's profile photo. This account attribute is not used by Identity Platform. It is available for informational purposes only. */ photoUrl?: string; - /** - * Information about the user as provided by various Identity Providers. - */ + /** @description Information about the user as provided by various Identity Providers. */ providerUserInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV1ProviderUserInfo"][]; - /** - * Input only. Plain text password used to update a account's password. This field is only ever used as input in a request. Identity Platform uses cryptographically secure hashing when managing passwords and will never store or transmit a user's password in plain text. - */ + /** @description Input only. Plain text password used to update a account's password. This field is only ever used as input in a request. Identity Platform uses cryptographically secure hashing when managing passwords and will never store or transmit a user's password in plain text. */ rawPassword?: string; /** - * The account's password salt. Only accessible by requests bearing a Google OAuth2 credential with proper permissions. + * Format: byte + * @description The account's password salt. Only accessible by requests bearing a Google OAuth2 credential with proper permissions. */ salt?: string; - /** - * Output only. This account's screen name at Twitter or login name at GitHub. - */ + /** @description Output only. This account's screen name at Twitter or login name at GitHub. */ screenName?: string; - /** - * ID of the tenant this account belongs to. Only set if this account belongs to a tenant. - */ + /** @description ID of the tenant this account belongs to. Only set if this account belongs to a tenant. */ tenantId?: string; - /** - * Output only. The time zone preference of the account. This account attribute is not used by Identity Platform. It is available for informational purposes only. - */ + /** @description Output only. The time zone preference of the account. This account attribute is not used by Identity Platform. It is available for informational purposes only. */ timeZone?: string; /** - * Oldest timestamp, in seconds since epoch, that an ID token should be considered valid. All ID tokens issued before this time are considered invalid. + * Format: int64 + * @description Oldest timestamp, in seconds since epoch, that an ID token should be considered valid. All ID tokens issued before this time are considered invalid. */ validSince?: string; /** - * The version of the account's password. Only accessible by requests bearing a Google OAuth2 credential with proper permissions. + * Format: int32 + * @description The version of the account's password. Only accessible by requests bearing a Google OAuth2 credential with proper permissions. */ version?: number; }; - /** - * Request message for VerifyIosClient - */ + /** @description Request message for VerifyIosClient */ GoogleCloudIdentitytoolkitV1VerifyIosClientRequest: { - /** - * A device token that the iOS client gets after registering to APNs (Apple Push Notification service). - */ + /** @description A device token that the iOS client gets after registering to APNs (Apple Push Notification service). */ appToken?: string; - /** - * Whether the app token is in the iOS sandbox. If false, the app token is in the production environment. - */ + /** @description Whether the app token is in the iOS sandbox. If false, the app token is in the production environment. */ isSandbox?: boolean; }; - /** - * Response message for VerifyIosClient. - */ + /** @description Response message for VerifyIosClient. */ GoogleCloudIdentitytoolkitV1VerifyIosClientResponse: { - /** - * Receipt of successful app token validation. - */ + /** @description Receipt of successful app token validation. */ receipt?: string; /** - * Suggested time that the client should wait in seconds for delivery of the push notification. + * Format: int64 + * @description Suggested time that the client should wait in seconds for delivery of the push notification. */ suggestedTimeout?: string; }; - /** - * Defines a policy of allowing every region by default and adding disallowed regions to a disallow list. - */ + /** @description Defines a policy of allowing every region by default and adding disallowed regions to a disallow list. */ GoogleCloudIdentitytoolkitAdminV2AllowByDefault: { - /** - * Two letter unicode region codes to disallow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json - */ + /** @description Two letter unicode region codes to disallow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json */ disallowedRegions?: string[]; }; - /** - * Defines a policy of only allowing regions by explicitly adding them to an allowlist. - */ + /** @description Defines a policy of only allowing regions by explicitly adding them to an allowlist. */ GoogleCloudIdentitytoolkitAdminV2AllowlistOnly: { - /** - * Two letter unicode region codes to allow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json - */ + /** @description Two letter unicode region codes to allow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json */ allowedRegions?: string[]; }; - /** - * Configuration options related to authenticating an anonymous user. - */ + /** @description Configuration options related to authenticating an anonymous user. */ GoogleCloudIdentitytoolkitAdminV2Anonymous: { - /** - * Whether anonymous user auth is enabled for the project or not. - */ + /** @description Whether anonymous user auth is enabled for the project or not. */ enabled?: boolean; }; - /** - * Additional config for SignInWithApple. - */ + /** @description Additional config for SignInWithApple. */ GoogleCloudIdentitytoolkitAdminV2AppleSignInConfig: { - /** - * A list of Bundle ID's usable by this project - */ + /** @description A list of Bundle ID's usable by this project */ bundleIds?: string[]; codeFlowConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2CodeFlowConfig"]; }; - /** - * Configuration related to Blocking Functions. - */ + /** @description Configuration related to Blocking Functions. */ GoogleCloudIdentitytoolkitAdminV2BlockingFunctionsConfig: { forwardInboundCredentials?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ForwardInboundCredentials"]; - /** - * Map of Trigger to event type. Key should be one of the supported event types: "beforeCreate", "beforeSignIn" - */ + /** @description Map of Trigger to event type. Key should be one of the supported event types: "beforeCreate", "beforeSignIn" */ triggers?: { [key: string]: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Trigger"]; }; }; - /** - * Options related to how clients making requests on behalf of a project should be configured. - */ + /** @description Options related to how clients making requests on behalf of a project should be configured. */ GoogleCloudIdentitytoolkitAdminV2ClientConfig: { - /** - * Output only. API key that can be used when making requests for this project. - */ + /** @description Output only. API key that can be used when making requests for this project. */ apiKey?: string; - /** - * Output only. Firebase subdomain. - */ + /** @description Output only. Firebase subdomain. */ firebaseSubdomain?: string; permissions?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Permissions"]; }; - /** - * Options related to how clients making requests on behalf of a tenant should be configured. - */ + /** @description Options related to how clients making requests on behalf of a tenant should be configured. */ GoogleCloudIdentitytoolkitAdminV2ClientPermissionConfig: { permissions?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ClientPermissions"]; }; - /** - * Configuration related to restricting a user's ability to affect their account. - */ + /** @description Configuration related to restricting a user's ability to affect their account. */ GoogleCloudIdentitytoolkitAdminV2ClientPermissions: { - /** - * When true, end users cannot delete their account on the associated project through any of our API methods - */ + /** @description When true, end users cannot delete their account on the associated project through any of our API methods */ disabledUserDeletion?: boolean; - /** - * When true, end users cannot sign up for a new account on the associated project through any of our API methods - */ + /** @description When true, end users cannot sign up for a new account on the associated project through any of our API methods */ disabledUserSignup?: boolean; }; - /** - * Additional config for Apple for code flow. - */ + /** @description Additional config for Apple for code flow. */ GoogleCloudIdentitytoolkitAdminV2CodeFlowConfig: { - /** - * Key ID for the private key. - */ + /** @description Key ID for the private key. */ keyId?: string; - /** - * Private key used for signing the client secret JWT. - */ + /** @description Private key used for signing the client secret JWT. */ privateKey?: string; - /** - * Apple Developer Team ID. - */ + /** @description Apple Developer Team ID. */ teamId?: string; }; - /** - * Represents an Identity Toolkit project. - */ + /** @description Represents an Identity Toolkit project. */ GoogleCloudIdentitytoolkitAdminV2Config: { - /** - * List of domains authorized for OAuth redirects - */ + /** @description List of domains authorized for OAuth redirects */ authorizedDomains?: string[]; - /** - * Whether anonymous users will be auto-deleted after a period of 30 days. - */ + /** @description Whether anonymous users will be auto-deleted after a period of 30 days. */ autodeleteAnonymousUsers?: boolean; blockingFunctions?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2BlockingFunctionsConfig"]; client?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ClientConfig"]; @@ -1990,65 +3084,39 @@ export interface components { mfa?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MultiFactorAuthConfig"]; monitoring?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MonitoringConfig"]; multiTenant?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MultiTenantConfig"]; - /** - * Output only. The name of the Config resource. Example: "projects/my-awesome-project/config" - */ + /** @description Output only. The name of the Config resource. Example: "projects/my-awesome-project/config" */ name?: string; notification?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2NotificationConfig"]; quota?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2QuotaConfig"]; signIn?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SignInConfig"]; smsRegionConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig"]; - /** - * Output only. The subtype of this config. - */ + /** @description Output only. The subtype of this config. */ subtype?: "SUBTYPE_UNSPECIFIED" | "IDENTITY_PLATFORM" | "FIREBASE_AUTH"; }; - /** - * Standard Identity Toolkit-trusted IDPs. - */ + /** @description Standard Identity Toolkit-trusted IDPs. */ GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdp: { - /** - * Description of the Idp - */ + /** @description Description of the Idp */ description?: string; - /** - * Id the of Idp - */ + /** @description Id the of Idp */ idpId?: string; }; - /** - * Configurations options for authenticating with a the standard set of Identity Toolkit-trusted IDPs. - */ + /** @description Configurations options for authenticating with a the standard set of Identity Toolkit-trusted IDPs. */ GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig: { appleSignInConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2AppleSignInConfig"]; - /** - * OAuth client ID. - */ + /** @description OAuth client ID. */ clientId?: string; - /** - * OAuth client secret. - */ + /** @description OAuth client secret. */ clientSecret?: string; - /** - * True if allows the user to sign in with the provider. - */ + /** @description True if allows the user to sign in with the provider. */ enabled?: boolean; - /** - * The name of the DefaultSupportedIdpConfig resource, for example: "projects/my-awesome-project/defaultSupportedIdpConfigs/google.com" - */ + /** @description The name of the DefaultSupportedIdpConfig resource, for example: "projects/my-awesome-project/defaultSupportedIdpConfigs/google.com" */ name?: string; }; - /** - * Information of custom domain DNS verification. By default, default_domain will be used. A custom domain can be configured using VerifyCustomDomain. - */ + /** @description Information of custom domain DNS verification. By default, default_domain will be used. A custom domain can be configured using VerifyCustomDomain. */ GoogleCloudIdentitytoolkitAdminV2DnsInfo: { - /** - * Output only. The applied verified custom domain. - */ + /** @description Output only. The applied verified custom domain. */ customDomain?: string; - /** - * Output only. The current verification state of the custom domain. The custom domain will only be used once the domain verification is successful. - */ + /** @description Output only. The current verification state of the custom domain. The custom domain will only be used once the domain verification is successful. */ customDomainState?: | "VERIFICATION_STATE_UNSPECIFIED" | "NOT_STARTED" @@ -2056,97 +3124,56 @@ export interface components { | "FAILED" | "SUCCEEDED"; /** - * Output only. The timestamp of initial request for the current domain verification. + * Format: google-datetime + * @description Output only. The timestamp of initial request for the current domain verification. */ domainVerificationRequestTime?: string; - /** - * Output only. The custom domain that's to be verified. - */ + /** @description Output only. The custom domain that's to be verified. */ pendingCustomDomain?: string; - /** - * Whether to use custom domain. - */ + /** @description Whether to use custom domain. */ useCustomDomain?: boolean; }; - /** - * Configuration options related to authenticating a user by their email address. - */ + /** @description Configuration options related to authenticating a user by their email address. */ GoogleCloudIdentitytoolkitAdminV2Email: { - /** - * Whether email auth is enabled for the project or not. - */ + /** @description Whether email auth is enabled for the project or not. */ enabled?: boolean; - /** - * Whether a password is required for email auth or not. If true, both an email and password must be provided to sign in. If false, a user may sign in via either email/password or email link. - */ + /** @description Whether a password is required for email auth or not. If true, both an email and password must be provided to sign in. If false, a user may sign in via either email/password or email link. */ passwordRequired?: boolean; }; - /** - * Configuration for settings related to email privacy and public visibility. Settings in this config protect against email enumeration, but may make some trade-offs in user-friendliness. - */ + /** @description Configuration for settings related to email privacy and public visibility. Settings in this config protect against email enumeration, but may make some trade-offs in user-friendliness. */ GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig: { - /** - * Migrates the project to a state of improved email privacy. For example certain error codes are more generic to avoid giving away information on whether the account exists. In addition, this disables certain features that as a side-effect allow user enumeration. Enabling this toggle disables the fetchSignInMethodsForEmail functionality and changing the user's email to an unverified email. It is recommended to remove dependence on this functionality and enable this toggle to improve user privacy. - */ + /** @description Migrates the project to a state of improved email privacy. For example certain error codes are more generic to avoid giving away information on whether the account exists. In addition, this disables certain features that as a side-effect allow user enumeration. Enabling this toggle disables the fetchSignInMethodsForEmail functionality and changing the user's email to an unverified email. It is recommended to remove dependence on this functionality and enable this toggle to improve user privacy. */ enableImprovedEmailPrivacy?: boolean; }; - /** - * Email template. The subject and body fields can contain the following placeholders which will be replaced with the appropriate values: %LINK% - The link to use to redeem the send OOB code. %EMAIL% - The email where the email is being sent. %NEW_EMAIL% - The new email being set for the account (when applicable). %APP_NAME% - The GCP project's display name. %DISPLAY_NAME% - The user's display name. - */ + /** @description Email template. The subject and body fields can contain the following placeholders which will be replaced with the appropriate values: %LINK% - The link to use to redeem the send OOB code. %EMAIL% - The email where the email is being sent. %NEW_EMAIL% - The new email being set for the account (when applicable). %APP_NAME% - The GCP project's display name. %DISPLAY_NAME% - The user's display name. */ GoogleCloudIdentitytoolkitAdminV2EmailTemplate: { - /** - * Email body - */ + /** @description Email body */ body?: string; - /** - * Email body format - */ + /** @description Email body format */ bodyFormat?: "BODY_FORMAT_UNSPECIFIED" | "PLAIN_TEXT" | "HTML"; - /** - * Output only. Whether the body or subject of the email is customized. - */ + /** @description Output only. Whether the body or subject of the email is customized. */ customized?: boolean; - /** - * Reply-to address - */ + /** @description Reply-to address */ replyTo?: string; - /** - * Sender display name - */ + /** @description Sender display name */ senderDisplayName?: string; - /** - * Local part of From address - */ + /** @description Local part of From address */ senderLocalPart?: string; - /** - * Subject of the email - */ + /** @description Subject of the email */ subject?: string; }; - /** - * Indicates which credentials to pass to the registered Blocking Functions. - */ + /** @description Indicates which credentials to pass to the registered Blocking Functions. */ GoogleCloudIdentitytoolkitAdminV2ForwardInboundCredentials: { - /** - * Whether to pass the user's OAuth identity provider's access token. - */ + /** @description Whether to pass the user's OAuth identity provider's access token. */ accessToken?: boolean; - /** - * Whether to pass the user's OIDC identity provider's ID token. - */ + /** @description Whether to pass the user's OIDC identity provider's ID token. */ idToken?: boolean; - /** - * Whether to pass the user's OAuth identity provider's refresh token. - */ + /** @description Whether to pass the user's OAuth identity provider's refresh token. */ refreshToken?: boolean; }; - /** - * History information of the hash algorithm and key. Different accounts' passwords may be generated by different version. - */ + /** @description History information of the hash algorithm and key. Different accounts' passwords may be generated by different version. */ GoogleCloudIdentitytoolkitAdminV2HashConfig: { - /** - * Output only. Different password hash algorithms used in Identity Toolkit. - */ + /** @description Output only. Different password hash algorithms used in Identity Toolkit. */ algorithm?: | "HASH_ALGORITHM_UNSPECIFIED" | "HMAC_SHA256" @@ -2163,880 +3190,4147 @@ export interface components { | "SHA512" | "STANDARD_SCRYPT"; /** - * Output only. Memory cost for hash calculation. Used by scrypt and other similar password derivation algorithms. See https://tools.ietf.org/html/rfc7914 for explanation of field. + * Format: int32 + * @description Output only. Memory cost for hash calculation. Used by scrypt and other similar password derivation algorithms. See https://tools.ietf.org/html/rfc7914 for explanation of field. */ memoryCost?: number; /** - * Output only. How many rounds for hash calculation. Used by scrypt and other similar password derivation algorithms. + * Format: int32 + * @description Output only. How many rounds for hash calculation. Used by scrypt and other similar password derivation algorithms. */ rounds?: number; - /** - * Output only. Non-printable character to be inserted between the salt and plain text password in base64. - */ + /** @description Output only. Non-printable character to be inserted between the salt and plain text password in base64. */ saltSeparator?: string; - /** - * Output only. Signer key in base64. - */ + /** @description Output only. Signer key in base64. */ signerKey?: string; }; - /** - * The IDP's certificate data to verify the signature in the SAMLResponse issued by the IDP. - */ + /** @description The IDP's certificate data to verify the signature in the SAMLResponse issued by the IDP. */ GoogleCloudIdentitytoolkitAdminV2IdpCertificate: { - /** - * The x509 certificate - */ + /** @description The x509 certificate */ x509Certificate?: string; }; - /** - * The SAML IdP (Identity Provider) configuration when the project acts as the relying party. - */ + /** @description The SAML IdP (Identity Provider) configuration when the project acts as the relying party. */ GoogleCloudIdentitytoolkitAdminV2IdpConfig: { - /** - * IDP's public keys for verifying signature in the assertions. - */ + /** @description IDP's public keys for verifying signature in the assertions. */ idpCertificates?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2IdpCertificate"][]; - /** - * Unique identifier for all SAML entities. - */ + /** @description Unique identifier for all SAML entities. */ idpEntityId?: string; - /** - * Indicates if outbounding SAMLRequest should be signed. - */ + /** @description Indicates if outbounding SAMLRequest should be signed. */ signRequest?: boolean; - /** - * URL to send Authentication request to. - */ + /** @description URL to send Authentication request to. */ ssoUrl?: string; }; - /** - * A pair of SAML RP-IDP configurations when the project acts as the relying party. - */ + /** @description A pair of SAML RP-IDP configurations when the project acts as the relying party. */ GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig: { - /** - * The config's display name set by developers. - */ + /** @description The config's display name set by developers. */ displayName?: string; - /** - * True if allows the user to sign in with the provider. - */ + /** @description True if allows the user to sign in with the provider. */ enabled?: boolean; idpConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2IdpConfig"]; - /** - * The name of the InboundSamlConfig resource, for example: 'projects/my-awesome-project/inboundSamlConfigs/my-config-id'. Ignored during create requests. - */ + /** @description The name of the InboundSamlConfig resource, for example: 'projects/my-awesome-project/inboundSamlConfigs/my-config-id'. Ignored during create requests. */ name?: string; spConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SpConfig"]; }; - /** - * Settings that the tenants will inherit from project level. - */ + /** @description Settings that the tenants will inherit from project level. */ GoogleCloudIdentitytoolkitAdminV2Inheritance: { - /** - * Whether to allow the tenant to inherit custom domains, email templates, and custom SMTP settings. If true, email sent from tenant will follow the project level email sending configurations. If false (by default), emails will go with the default settings with no customizations. - */ + /** @description Whether to allow the tenant to inherit custom domains, email templates, and custom SMTP settings. If true, email sent from tenant will follow the project level email sending configurations. If false (by default), emails will go with the default settings with no customizations. */ emailSendingConfig?: boolean; }; - /** - * Request for InitializeIdentityPlatform. - */ - GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformRequest: { [key: string]: any }; - /** - * Response for InitializeIdentityPlatform. Empty for now. - */ - GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformResponse: { [key: string]: any }; - /** - * Response for DefaultSupportedIdpConfigs - */ + /** @description Request for InitializeIdentityPlatform. */ + GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformRequest: { [key: string]: unknown }; + /** @description Response for InitializeIdentityPlatform. Empty for now. */ + GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformResponse: { [key: string]: unknown }; + /** @description Response for DefaultSupportedIdpConfigs */ GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpConfigsResponse: { - /** - * The set of configs. - */ + /** @description The set of configs. */ defaultSupportedIdpConfigs?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"][]; - /** - * Token to retrieve the next page of results, or empty if there are no more results in the list. - */ + /** @description Token to retrieve the next page of results, or empty if there are no more results in the list. */ nextPageToken?: string; }; - /** - * Response for ListDefaultSupportedIdps - */ + /** @description Response for ListDefaultSupportedIdps */ GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpsResponse: { - /** - * The set of configs. - */ + /** @description The set of configs. */ defaultSupportedIdps?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdp"][]; - /** - * Token to retrieve the next page of results, or empty if there are no more results in the list. - */ + /** @description Token to retrieve the next page of results, or empty if there are no more results in the list. */ nextPageToken?: string; }; - /** - * Response for ListInboundSamlConfigs - */ + /** @description Response for ListInboundSamlConfigs */ GoogleCloudIdentitytoolkitAdminV2ListInboundSamlConfigsResponse: { - /** - * The set of configs. - */ + /** @description The set of configs. */ inboundSamlConfigs?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"][]; - /** - * Token to retrieve the next page of results, or empty if there are no more results in the list. - */ + /** @description Token to retrieve the next page of results, or empty if there are no more results in the list. */ nextPageToken?: string; }; - /** - * Response for ListOAuthIdpConfigs - */ + /** @description Response for ListOAuthIdpConfigs */ GoogleCloudIdentitytoolkitAdminV2ListOAuthIdpConfigsResponse: { - /** - * Token to retrieve the next page of results, or empty if there are no more results in the list. - */ + /** @description Token to retrieve the next page of results, or empty if there are no more results in the list. */ nextPageToken?: string; - /** - * The set of configs. - */ + /** @description The set of configs. */ oauthIdpConfigs?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"][]; }; - /** - * Response message for ListTenants. - */ + /** @description Response message for ListTenants. */ GoogleCloudIdentitytoolkitAdminV2ListTenantsResponse: { - /** - * The token to get the next page of results. - */ + /** @description The token to get the next page of results. */ nextPageToken?: string; - /** - * A list of tenants under the given agent project. - */ + /** @description A list of tenants under the given agent project. */ tenants?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Tenant"][]; }; - /** - * Configuration related to monitoring project activity. - */ + /** @description Configuration related to monitoring project activity. */ GoogleCloudIdentitytoolkitAdminV2MonitoringConfig: { requestLogging?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2RequestLogging"]; }; - /** - * Options related to MultiFactor Authentication for the project. - */ + /** @description Options related to MultiFactor Authentication for the project. */ GoogleCloudIdentitytoolkitAdminV2MultiFactorAuthConfig: { - /** - * A list of usable second factors for this project. - */ + /** @description A list of usable second factors for this project. */ enabledProviders?: ("PROVIDER_UNSPECIFIED" | "PHONE_SMS")[]; - /** - * Whether MultiFactor Authentication has been enabled for this project. - */ + /** @description Whether MultiFactor Authentication has been enabled for this project. */ state?: "STATE_UNSPECIFIED" | "DISABLED" | "ENABLED" | "MANDATORY"; }; - /** - * Configuration related to multi-tenant functionality. - */ + /** @description Configuration related to multi-tenant functionality. */ GoogleCloudIdentitytoolkitAdminV2MultiTenantConfig: { - /** - * Whether this project can have tenants or not. - */ + /** @description Whether this project can have tenants or not. */ allowTenants?: boolean; - /** - * The default cloud parent org or folder that the tenant project should be created under. The parent resource name should be in the format of "/", such as "folders/123" or "organizations/456". If the value is not set, the tenant will be created under the same organization or folder as the agent project. - */ + /** @description The default cloud parent org or folder that the tenant project should be created under. The parent resource name should be in the format of "/", such as "folders/123" or "organizations/456". If the value is not set, the tenant will be created under the same organization or folder as the agent project. */ defaultTenantLocation?: string; }; - /** - * Configuration related to sending notifications to users. - */ + /** @description Configuration related to sending notifications to users. */ GoogleCloudIdentitytoolkitAdminV2NotificationConfig: { - /** - * Default locale used for email and SMS in IETF BCP 47 format. - */ + /** @description Default locale used for email and SMS in IETF BCP 47 format. */ defaultLocale?: string; sendEmail?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SendEmail"]; sendSms?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SendSms"]; }; - /** - * Configuration options for authenticating with an OAuth IDP. - */ + /** @description Configuration options for authenticating with an OAuth IDP. */ GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig: { - /** - * The client id of an OAuth client. - */ + /** @description The client id of an OAuth client. */ clientId?: string; - /** - * The client secret of the OAuth client, to enable OIDC code flow. - */ + /** @description The client secret of the OAuth client, to enable OIDC code flow. */ clientSecret?: string; - /** - * The config's display name set by developers. - */ + /** @description The config's display name set by developers. */ displayName?: string; - /** - * True if allows the user to sign in with the provider. - */ + /** @description True if allows the user to sign in with the provider. */ enabled?: boolean; - /** - * For OIDC Idps, the issuer identifier. - */ + /** @description For OIDC Idps, the issuer identifier. */ issuer?: string; - /** - * The name of the OAuthIdpConfig resource, for example: 'projects/my-awesome-project/oauthIdpConfigs/oauth-config-id'. Ignored during create requests. - */ + /** @description The name of the OAuthIdpConfig resource, for example: 'projects/my-awesome-project/oauthIdpConfigs/oauth-config-id'. Ignored during create requests. */ name?: string; responseType?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthResponseType"]; }; - /** - * The response type to request for in the OAuth authorization flow. You can set either `id_token` or `code` to true, but not both. Setting both types to be simultaneously true (`{code: true, id_token: true}`) is not yet supported. See https://openid.net/specs/openid-connect-core-1_0.html#Authentication for a mapping of response type to OAuth 2.0 flow. - */ + /** @description The response type to request for in the OAuth authorization flow. You can set either `id_token` or `code` to true, but not both. Setting both types to be simultaneously true (`{code: true, id_token: true}`) is not yet supported. See https://openid.net/specs/openid-connect-core-1_0.html#Authentication for a mapping of response type to OAuth 2.0 flow. */ GoogleCloudIdentitytoolkitAdminV2OAuthResponseType: { - /** - * If true, authorization code is returned from IdP's authorization endpoint. - */ + /** @description If true, authorization code is returned from IdP's authorization endpoint. */ code?: boolean; - /** - * If true, ID token is returned from IdP's authorization endpoint. - */ + /** @description If true, ID token is returned from IdP's authorization endpoint. */ idToken?: boolean; - /** - * Do not use. The `token` response type is not supported at the moment. - */ + /** @description Do not use. The `token` response type is not supported at the moment. */ token?: boolean; }; - /** - * Configuration related to restricting a user's ability to affect their account. - */ + /** @description Configuration related to restricting a user's ability to affect their account. */ GoogleCloudIdentitytoolkitAdminV2Permissions: { - /** - * When true, end users cannot delete their account on the associated project through any of our API methods - */ + /** @description When true, end users cannot delete their account on the associated project through any of our API methods */ disabledUserDeletion?: boolean; - /** - * When true, end users cannot sign up for a new account on the associated project through any of our API methods - */ + /** @description When true, end users cannot sign up for a new account on the associated project through any of our API methods */ disabledUserSignup?: boolean; }; - /** - * Configuration options related to authenticated a user by their phone number. - */ + /** @description Configuration options related to authenticated a user by their phone number. */ GoogleCloudIdentitytoolkitAdminV2PhoneNumber: { - /** - * Whether phone number auth is enabled for the project or not. - */ + /** @description Whether phone number auth is enabled for the project or not. */ enabled?: boolean; - /** - * A map of that can be used for phone auth testing. - */ + /** @description A map of that can be used for phone auth testing. */ testPhoneNumbers?: { [key: string]: string }; }; - /** - * Configuration related to quotas. - */ + /** @description Configuration related to quotas. */ GoogleCloudIdentitytoolkitAdminV2QuotaConfig: { signUpQuotaConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2TemporaryQuota"]; }; - /** - * Configuration for logging requests made to this project to Stackdriver Logging - */ + /** @description Configuration for logging requests made to this project to Stackdriver Logging */ GoogleCloudIdentitytoolkitAdminV2RequestLogging: { - /** - * Whether logging is enabled for this project or not. - */ + /** @description Whether logging is enabled for this project or not. */ enabled?: boolean; }; - /** - * Options for email sending. - */ + /** @description Options for email sending. */ GoogleCloudIdentitytoolkitAdminV2SendEmail: { - /** - * action url in email template. - */ + /** @description action url in email template. */ callbackUri?: string; changeEmailTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2EmailTemplate"]; dnsInfo?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DnsInfo"]; legacyResetPasswordTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2EmailTemplate"]; - /** - * The method used for sending an email. - */ + /** @description The method used for sending an email. */ method?: "METHOD_UNSPECIFIED" | "DEFAULT" | "CUSTOM_SMTP"; resetPasswordTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2EmailTemplate"]; revertSecondFactorAdditionTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2EmailTemplate"]; smtp?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Smtp"]; verifyEmailTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2EmailTemplate"]; }; - /** - * Options for SMS sending. - */ + /** @description Options for SMS sending. */ GoogleCloudIdentitytoolkitAdminV2SendSms: { smsTemplate?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SmsTemplate"]; - /** - * Whether to use the accept_language header for SMS. - */ + /** @description Whether to use the accept_language header for SMS. */ useDeviceLocale?: boolean; }; - /** - * Configuration related to local sign in methods. - */ + /** @description Configuration related to local sign in methods. */ GoogleCloudIdentitytoolkitAdminV2SignInConfig: { - /** - * Whether to allow more than one account to have the same email. - */ + /** @description Whether to allow more than one account to have the same email. */ allowDuplicateEmails?: boolean; anonymous?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Anonymous"]; email?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Email"]; hashConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2HashConfig"]; phoneNumber?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2PhoneNumber"]; }; - /** - * Configures the regions where users are allowed to send verification SMS for the project or tenant. This is based on the calling code of the destination phone number. - */ + /** @description Configures the regions where users are allowed to send verification SMS for the project or tenant. This is based on the calling code of the destination phone number. */ GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig: { allowByDefault?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2AllowByDefault"]; allowlistOnly?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2AllowlistOnly"]; }; - /** - * The template to use when sending an SMS. - */ + /** @description The template to use when sending an SMS. */ GoogleCloudIdentitytoolkitAdminV2SmsTemplate: { - /** - * Output only. The SMS's content. Can contain the following placeholders which will be replaced with the appropriate values: %APP_NAME% - For Android or iOS apps, the app's display name. For web apps, the domain hosting the application. %LOGIN_CODE% - The OOB code being sent in the SMS. - */ + /** @description Output only. The SMS's content. Can contain the following placeholders which will be replaced with the appropriate values: %APP_NAME% - For Android or iOS apps, the app's display name. For web apps, the domain hosting the application. %LOGIN_CODE% - The OOB code being sent in the SMS. */ content?: string; }; - /** - * Configuration for SMTP relay - */ + /** @description Configuration for SMTP relay */ GoogleCloudIdentitytoolkitAdminV2Smtp: { - /** - * SMTP relay host - */ + /** @description SMTP relay host */ host?: string; - /** - * SMTP relay password - */ + /** @description SMTP relay password */ password?: string; /** - * SMTP relay port + * Format: int32 + * @description SMTP relay port */ port?: number; - /** - * SMTP security mode. - */ + /** @description SMTP security mode. */ securityMode?: "SECURITY_MODE_UNSPECIFIED" | "SSL" | "START_TLS"; - /** - * Sender email for the SMTP relay - */ + /** @description Sender email for the SMTP relay */ senderEmail?: string; - /** - * SMTP relay username - */ + /** @description SMTP relay username */ username?: string; }; - /** - * The SP's certificate data for IDP to verify the SAMLRequest generated by the SP. - */ + /** @description The SP's certificate data for IDP to verify the SAMLRequest generated by the SP. */ GoogleCloudIdentitytoolkitAdminV2SpCertificate: { /** - * Timestamp of the cert expiration instance. + * Format: google-datetime + * @description Timestamp of the cert expiration instance. */ expiresAt?: string; - /** - * Self-signed public certificate. - */ + /** @description Self-signed public certificate. */ x509Certificate?: string; }; - /** - * The SAML SP (Service Provider) configuration when the project acts as the relying party to receive and accept an authentication assertion issued by a SAML identity provider. - */ + /** @description The SAML SP (Service Provider) configuration when the project acts as the relying party to receive and accept an authentication assertion issued by a SAML identity provider. */ GoogleCloudIdentitytoolkitAdminV2SpConfig: { - /** - * Callback URI where responses from IDP are handled. - */ + /** @description Callback URI where responses from IDP are handled. */ callbackUri?: string; - /** - * Output only. Public certificates generated by the server to verify the signature in SAMLRequest in the SP-initiated flow. - */ + /** @description Output only. Public certificates generated by the server to verify the signature in SAMLRequest in the SP-initiated flow. */ spCertificates?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SpCertificate"][]; - /** - * Unique identifier for all SAML entities. - */ + /** @description Unique identifier for all SAML entities. */ spEntityId?: string; }; - /** - * Temporary quota increase / decrease - */ + /** @description Temporary quota increase / decrease */ GoogleCloudIdentitytoolkitAdminV2TemporaryQuota: { /** - * Corresponds to the 'refill_token_count' field in QuotaServer config + * Format: int64 + * @description Corresponds to the 'refill_token_count' field in QuotaServer config */ quota?: string; /** - * How long this quota will be active for + * Format: google-duration + * @description How long this quota will be active for */ quotaDuration?: string; /** - * When this quota will take affect + * Format: google-datetime + * @description When this quota will take affect */ startTime?: string; }; - /** - * A Tenant contains configuration for the tenant in a multi-tenant project. - */ + /** @description A Tenant contains configuration for the tenant in a multi-tenant project. */ GoogleCloudIdentitytoolkitAdminV2Tenant: { - /** - * Whether to allow email/password user authentication. - */ + /** @description Whether to allow email/password user authentication. */ allowPasswordSignup?: boolean; - /** - * Whether anonymous users will be auto-deleted after a period of 30 days. - */ + /** @description Whether anonymous users will be auto-deleted after a period of 30 days. */ autodeleteAnonymousUsers?: boolean; client?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ClientPermissionConfig"]; - /** - * Whether authentication is disabled for the tenant. If true, the users under the disabled tenant are not allowed to sign-in. Admins of the disabled tenant are not able to manage its users. - */ + /** @description Whether authentication is disabled for the tenant. If true, the users under the disabled tenant are not allowed to sign-in. Admins of the disabled tenant are not able to manage its users. */ disableAuth?: boolean; - /** - * Display name of the tenant. - */ + /** @description Display name of the tenant. */ displayName?: string; emailPrivacyConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig"]; - /** - * Whether to enable anonymous user authentication. - */ + /** @description Whether to enable anonymous user authentication. */ enableAnonymousUser?: boolean; - /** - * Whether to enable email link user authentication. - */ + /** @description Whether to enable email link user authentication. */ enableEmailLinkSignin?: boolean; hashConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2HashConfig"]; inheritance?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Inheritance"]; mfaConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MultiFactorAuthConfig"]; monitoring?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2MonitoringConfig"]; - /** - * Output only. Resource name of a tenant. For example: "projects/{project-id}/tenants/{tenant-id}" - */ + /** @description Output only. Resource name of a tenant. For example: "projects/{project-id}/tenants/{tenant-id}" */ name?: string; smsRegionConfig?: components["schemas"]["GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig"]; - /** - * A map of pairs that can be used for MFA. The phone number should be in E.164 format (https://www.itu.int/rec/T-REC-E.164/) and a maximum of 10 pairs can be added (error will be thrown once exceeded). - */ + /** @description A map of pairs that can be used for MFA. The phone number should be in E.164 format (https://www.itu.int/rec/T-REC-E.164/) and a maximum of 10 pairs can be added (error will be thrown once exceeded). */ testPhoneNumbers?: { [key: string]: string }; }; - /** - * Synchronous Cloud Function with HTTP Trigger - */ + /** @description Synchronous Cloud Function with HTTP Trigger */ GoogleCloudIdentitytoolkitAdminV2Trigger: { - /** - * HTTP URI trigger for the Cloud Function. - */ + /** @description HTTP URI trigger for the Cloud Function. */ functionUri?: string; /** - * When the trigger was changed. + * Format: google-datetime + * @description When the trigger was changed. */ updateTime?: string; }; - /** - * The information required to auto-retrieve an SMS. - */ + /** @description The information required to auto-retrieve an SMS. */ GoogleCloudIdentitytoolkitV2AutoRetrievalInfo: { - /** - * The Android app's signature hash for Google Play Service's SMS Retriever API. - */ + /** @description The Android app's signature hash for Google Play Service's SMS Retriever API. */ appSignatureHash?: string; }; - /** - * Finishes enrolling a second factor for the user. - */ + /** @description Finishes enrolling a second factor for the user. */ GoogleCloudIdentitytoolkitV2FinalizeMfaEnrollmentRequest: { - /** - * Display name which is entered by users to distinguish between different second factors with same type or different type. - */ + /** @description Display name which is entered by users to distinguish between different second factors with same type or different type. */ displayName?: string; - /** - * Required. ID token. - */ + /** @description Required. ID token. */ idToken?: string; phoneVerificationInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2FinalizeMfaPhoneRequestInfo"]; - /** - * The ID of the Identity Platform tenant that the user enrolling MFA belongs to. If not set, the user belongs to the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant that the user enrolling MFA belongs to. If not set, the user belongs to the default Identity Platform project. */ tenantId?: string; }; - /** - * FinalizeMfaEnrollment response. - */ + /** @description FinalizeMfaEnrollment response. */ GoogleCloudIdentitytoolkitV2FinalizeMfaEnrollmentResponse: { - /** - * ID token updated to reflect MFA enrollment. - */ + /** @description ID token updated to reflect MFA enrollment. */ idToken?: string; phoneAuthInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2FinalizeMfaPhoneResponseInfo"]; - /** - * Refresh token updated to reflect MFA enrollment. - */ + /** @description Refresh token updated to reflect MFA enrollment. */ refreshToken?: string; }; - /** - * Phone Verification info for a FinalizeMfa request. - */ + /** @description Phone Verification info for a FinalizeMfa request. */ GoogleCloudIdentitytoolkitV2FinalizeMfaPhoneRequestInfo: { - /** - * Android only. Uses for "instant" phone number verification though GmsCore. - */ + /** @description Android only. Uses for "instant" phone number verification though GmsCore. */ androidVerificationProof?: string; - /** - * User-entered verification code. - */ + /** @description User-entered verification code. */ code?: string; - /** - * Required if Android verification proof is presented. - */ + /** @description Required if Android verification proof is presented. */ phoneNumber?: string; - /** - * An opaque string that represents the enrollment session. - */ + /** @description An opaque string that represents the enrollment session. */ sessionInfo?: string; }; - /** - * Phone Verification info for a FinalizeMfa response. - */ + /** @description Phone Verification info for a FinalizeMfa response. */ GoogleCloudIdentitytoolkitV2FinalizeMfaPhoneResponseInfo: { - /** - * Android only. Long-lived replacement for valid code tied to android device. - */ + /** @description Android only. Long-lived replacement for valid code tied to android device. */ androidVerificationProof?: string; /** - * Android only. Expiration time of verification proof in seconds. + * Format: google-datetime + * @description Android only. Expiration time of verification proof in seconds. */ androidVerificationProofExpireTime?: string; - /** - * For Android verification proof. - */ + /** @description For Android verification proof. */ phoneNumber?: string; }; - /** - * Finalizes sign-in by verifying MFA challenge. - */ + /** @description Finalizes sign-in by verifying MFA challenge. */ GoogleCloudIdentitytoolkitV2FinalizeMfaSignInRequest: { - /** - * Required. Pending credential from first factor sign-in. - */ + /** @description Required. Pending credential from first factor sign-in. */ mfaPendingCredential?: string; phoneVerificationInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2FinalizeMfaPhoneRequestInfo"]; - /** - * The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. */ tenantId?: string; }; - /** - * FinalizeMfaSignIn response. - */ + /** @description FinalizeMfaSignIn response. */ GoogleCloudIdentitytoolkitV2FinalizeMfaSignInResponse: { - /** - * ID token for the authenticated user. - */ + /** @description ID token for the authenticated user. */ idToken?: string; phoneAuthInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2FinalizeMfaPhoneResponseInfo"]; - /** - * Refresh token for the authenticated user. - */ + /** @description Refresh token for the authenticated user. */ refreshToken?: string; }; - /** - * Sends MFA enrollment verification SMS for a user. - */ + /** @description Sends MFA enrollment verification SMS for a user. */ GoogleCloudIdentitytoolkitV2StartMfaEnrollmentRequest: { - /** - * Required. User's ID token. - */ + /** @description Required. User's ID token. */ idToken?: string; phoneEnrollmentInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2StartMfaPhoneRequestInfo"]; - /** - * The ID of the Identity Platform tenant that the user enrolling MFA belongs to. If not set, the user belongs to the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant that the user enrolling MFA belongs to. If not set, the user belongs to the default Identity Platform project. */ tenantId?: string; }; - /** - * StartMfaEnrollment response. - */ + /** @description StartMfaEnrollment response. */ GoogleCloudIdentitytoolkitV2StartMfaEnrollmentResponse: { phoneSessionInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2StartMfaPhoneResponseInfo"]; }; - /** - * App Verification info for a StartMfa request. - */ + /** @description App Verification info for a StartMfa request. */ GoogleCloudIdentitytoolkitV2StartMfaPhoneRequestInfo: { autoRetrievalInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2AutoRetrievalInfo"]; - /** - * iOS only. Receipt of successful app token validation with APNS. - */ + /** @description iOS only. Receipt of successful app token validation with APNS. */ iosReceipt?: string; - /** - * iOS only. Secret delivered to iOS app via APNS. - */ + /** @description iOS only. Secret delivered to iOS app via APNS. */ iosSecret?: string; - /** - * Required for enrollment. Phone number to be enrolled as MFA. - */ + /** @description Required for enrollment. Phone number to be enrolled as MFA. */ phoneNumber?: string; - /** - * Web only. Recaptcha solution. - */ + /** @description Web only. Recaptcha solution. */ recaptchaToken?: string; - /** - * Android only. Used to assert application identity in place of a recaptcha token. A SafetyNet Token can be generated via the [SafetyNet Android Attestation API](https://developer.android.com/training/safetynet/attestation.html), with the Base64 encoding of the `phone_number` field as the nonce. - */ + /** @description Android only. Used to assert application identity in place of a recaptcha token. A SafetyNet Token can be generated via the [SafetyNet Android Attestation API](https://developer.android.com/training/safetynet/attestation.html), with the Base64 encoding of the `phone_number` field as the nonce. */ safetyNetToken?: string; }; - /** - * Phone Verification info for a StartMfa response. - */ + /** @description Phone Verification info for a StartMfa response. */ GoogleCloudIdentitytoolkitV2StartMfaPhoneResponseInfo: { - /** - * An opaque string that represents the enrollment session. - */ + /** @description An opaque string that represents the enrollment session. */ sessionInfo?: string; }; - /** - * Starts multi-factor sign-in by sending the multi-factor auth challenge. - */ + /** @description Starts multi-factor sign-in by sending the multi-factor auth challenge. */ GoogleCloudIdentitytoolkitV2StartMfaSignInRequest: { - /** - * Required. MFA enrollment id from the user's list of current MFA enrollments. - */ + /** @description Required. MFA enrollment id from the user's list of current MFA enrollments. */ mfaEnrollmentId?: string; - /** - * Required. Pending credential from first factor sign-in. - */ + /** @description Required. Pending credential from first factor sign-in. */ mfaPendingCredential?: string; phoneSignInInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2StartMfaPhoneRequestInfo"]; - /** - * The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant the user is signing in to. If not set, the user will sign in to the default Identity Platform project. */ tenantId?: string; }; - /** - * StartMfaSignIn response. - */ + /** @description StartMfaSignIn response. */ GoogleCloudIdentitytoolkitV2StartMfaSignInResponse: { phoneResponseInfo?: components["schemas"]["GoogleCloudIdentitytoolkitV2StartMfaPhoneResponseInfo"]; }; - /** - * Withdraws MFA. - */ + /** @description Withdraws MFA. */ GoogleCloudIdentitytoolkitV2WithdrawMfaRequest: { - /** - * Required. User's ID token. - */ + /** @description Required. User's ID token. */ idToken?: string; - /** - * Required. MFA enrollment id from a current MFA enrollment. - */ + /** @description Required. MFA enrollment id from a current MFA enrollment. */ mfaEnrollmentId?: string; - /** - * The ID of the Identity Platform tenant that the user unenrolling MFA belongs to. If not set, the user belongs to the default Identity Platform project. - */ + /** @description The ID of the Identity Platform tenant that the user unenrolling MFA belongs to. If not set, the user belongs to the default Identity Platform project. */ tenantId?: string; }; - /** - * Withdraws MultiFactorAuth response. - */ + /** @description Withdraws MultiFactorAuth response. */ GoogleCloudIdentitytoolkitV2WithdrawMfaResponse: { - /** - * ID token updated to reflect removal of the second factor. - */ + /** @description ID token updated to reflect removal of the second factor. */ idToken?: string; - /** - * Refresh token updated to reflect removal of the second factor. - */ + /** @description Refresh token updated to reflect removal of the second factor. */ refreshToken?: string; }; - /** - * Specifies the audit configuration for a service. The configuration determines which permission types are logged, and what identities, if any, are exempted from logging. An AuditConfig must have one or more AuditLogConfigs. If there are AuditConfigs for both `allServices` and a specific service, the union of the two AuditConfigs is used for that service: the log_types specified in each AuditConfig are enabled, and the exempted_members in each AuditLogConfig are exempted. Example Policy with multiple AuditConfigs: { "audit_configs": [ { "service": "allServices", "audit_log_configs": [ { "log_type": "DATA_READ", "exempted_members": [ "user:jose@example.com" ] }, { "log_type": "DATA_WRITE" }, { "log_type": "ADMIN_READ" } ] }, { "service": "sampleservice.googleapis.com", "audit_log_configs": [ { "log_type": "DATA_READ" }, { "log_type": "DATA_WRITE", "exempted_members": [ "user:aliya@example.com" ] } ] } ] } For sampleservice, this policy enables DATA_READ, DATA_WRITE and ADMIN_READ logging. It also exempts `jose@example.com` from DATA_READ logging, and `aliya@example.com` from DATA_WRITE logging. - */ + /** @description Specifies the audit configuration for a service. The configuration determines which permission types are logged, and what identities, if any, are exempted from logging. An AuditConfig must have one or more AuditLogConfigs. If there are AuditConfigs for both `allServices` and a specific service, the union of the two AuditConfigs is used for that service: the log_types specified in each AuditConfig are enabled, and the exempted_members in each AuditLogConfig are exempted. Example Policy with multiple AuditConfigs: { "audit_configs": [ { "service": "allServices", "audit_log_configs": [ { "log_type": "DATA_READ", "exempted_members": [ "user:jose@example.com" ] }, { "log_type": "DATA_WRITE" }, { "log_type": "ADMIN_READ" } ] }, { "service": "sampleservice.googleapis.com", "audit_log_configs": [ { "log_type": "DATA_READ" }, { "log_type": "DATA_WRITE", "exempted_members": [ "user:aliya@example.com" ] } ] } ] } For sampleservice, this policy enables DATA_READ, DATA_WRITE and ADMIN_READ logging. It also exempts `jose@example.com` from DATA_READ logging, and `aliya@example.com` from DATA_WRITE logging. */ GoogleIamV1AuditConfig: { - /** - * The configuration for logging of each type of permission. - */ + /** @description The configuration for logging of each type of permission. */ auditLogConfigs?: components["schemas"]["GoogleIamV1AuditLogConfig"][]; - /** - * Specifies a service that will be enabled for audit logging. For example, `storage.googleapis.com`, `cloudsql.googleapis.com`. `allServices` is a special value that covers all services. - */ + /** @description Specifies a service that will be enabled for audit logging. For example, `storage.googleapis.com`, `cloudsql.googleapis.com`. `allServices` is a special value that covers all services. */ service?: string; }; - /** - * Provides the configuration for logging a type of permissions. Example: { "audit_log_configs": [ { "log_type": "DATA_READ", "exempted_members": [ "user:jose@example.com" ] }, { "log_type": "DATA_WRITE" } ] } This enables 'DATA_READ' and 'DATA_WRITE' logging, while exempting jose@example.com from DATA_READ logging. - */ + /** @description Provides the configuration for logging a type of permissions. Example: { "audit_log_configs": [ { "log_type": "DATA_READ", "exempted_members": [ "user:jose@example.com" ] }, { "log_type": "DATA_WRITE" } ] } This enables 'DATA_READ' and 'DATA_WRITE' logging, while exempting jose@example.com from DATA_READ logging. */ GoogleIamV1AuditLogConfig: { - /** - * Specifies the identities that do not cause logging for this type of permission. Follows the same format of Binding.members. - */ + /** @description Specifies the identities that do not cause logging for this type of permission. Follows the same format of Binding.members. */ exemptedMembers?: string[]; - /** - * The log type that this config enables. - */ + /** @description The log type that this config enables. */ logType?: "LOG_TYPE_UNSPECIFIED" | "ADMIN_READ" | "DATA_WRITE" | "DATA_READ"; }; - /** - * Associates `members`, or principals, with a `role`. - */ + /** @description Associates `members`, or principals, with a `role`. */ GoogleIamV1Binding: { condition?: components["schemas"]["GoogleTypeExpr"]; - /** - * Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. Does not include identities that come from external identity providers (IdPs) through identity federation. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a Google service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `serviceAccount:{projectid}.svc.id.goog[{namespace}/{kubernetes-sa}]`: An identifier for a [Kubernetes service account](https://cloud.google.com/kubernetes-engine/docs/how-to/kubernetes-service-accounts). For example, `my-project.svc.id.goog[my-namespace/my-kubernetes-sa]`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. - */ + /** @description Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. Does not include identities that come from external identity providers (IdPs) through identity federation. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a Google service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `serviceAccount:{projectid}.svc.id.goog[{namespace}/{kubernetes-sa}]`: An identifier for a [Kubernetes service account](https://cloud.google.com/kubernetes-engine/docs/how-to/kubernetes-service-accounts). For example, `my-project.svc.id.goog[my-namespace/my-kubernetes-sa]`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. */ members?: string[]; - /** - * Role that is assigned to the list of `members`, or principals. For example, `roles/viewer`, `roles/editor`, or `roles/owner`. - */ + /** @description Role that is assigned to the list of `members`, or principals. For example, `roles/viewer`, `roles/editor`, or `roles/owner`. */ role?: string; }; - /** - * Request message for `GetIamPolicy` method. - */ + /** @description Request message for `GetIamPolicy` method. */ GoogleIamV1GetIamPolicyRequest: { options?: components["schemas"]["GoogleIamV1GetPolicyOptions"]; }; - /** - * Encapsulates settings provided to GetIamPolicy. - */ + /** @description Encapsulates settings provided to GetIamPolicy. */ GoogleIamV1GetPolicyOptions: { /** - * Optional. The maximum policy version that will be used to format the policy. Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. Requests for policies with any conditional role bindings must specify version 3. Policies with no conditional role bindings may specify any valid value or leave the field unset. The policy in the response might use the policy version that you specified, or it might use a lower policy version. For example, if you specify version 3, but the policy has no conditional role bindings, the response uses version 1. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). + * Format: int32 + * @description Optional. The maximum policy version that will be used to format the policy. Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. Requests for policies with any conditional role bindings must specify version 3. Policies with no conditional role bindings may specify any valid value or leave the field unset. The policy in the response might use the policy version that you specified, or it might use a lower policy version. For example, if you specify version 3, but the policy has no conditional role bindings, the response uses version 1. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). */ requestedPolicyVersion?: number; }; - /** - * An Identity and Access Management (IAM) policy, which specifies access controls for Google Cloud resources. A `Policy` is a collection of `bindings`. A `binding` binds one or more `members`, or principals, to a single `role`. Principals can be user accounts, service accounts, Google groups, and domains (such as G Suite). A `role` is a named list of permissions; each `role` can be an IAM predefined role or a user-created custom role. For some types of Google Cloud resources, a `binding` can also specify a `condition`, which is a logical expression that allows access to a resource only if the expression evaluates to `true`. A condition can add constraints based on attributes of the request, the resource, or both. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). **JSON example:** { "bindings": [ { "role": "roles/resourcemanager.organizationAdmin", "members": [ "user:mike@example.com", "group:admins@example.com", "domain:google.com", "serviceAccount:my-project-id@appspot.gserviceaccount.com" ] }, { "role": "roles/resourcemanager.organizationViewer", "members": [ "user:eve@example.com" ], "condition": { "title": "expirable access", "description": "Does not grant access after Sep 2020", "expression": "request.time < timestamp('2020-10-01T00:00:00.000Z')", } } ], "etag": "BwWWja0YfJA=", "version": 3 } **YAML example:** bindings: - members: - user:mike@example.com - group:admins@example.com - domain:google.com - serviceAccount:my-project-id@appspot.gserviceaccount.com role: roles/resourcemanager.organizationAdmin - members: - user:eve@example.com role: roles/resourcemanager.organizationViewer condition: title: expirable access description: Does not grant access after Sep 2020 expression: request.time < timestamp('2020-10-01T00:00:00.000Z') etag: BwWWja0YfJA= version: 3 For a description of IAM and its features, see the [IAM documentation](https://cloud.google.com/iam/docs/). - */ + /** @description An Identity and Access Management (IAM) policy, which specifies access controls for Google Cloud resources. A `Policy` is a collection of `bindings`. A `binding` binds one or more `members`, or principals, to a single `role`. Principals can be user accounts, service accounts, Google groups, and domains (such as G Suite). A `role` is a named list of permissions; each `role` can be an IAM predefined role or a user-created custom role. For some types of Google Cloud resources, a `binding` can also specify a `condition`, which is a logical expression that allows access to a resource only if the expression evaluates to `true`. A condition can add constraints based on attributes of the request, the resource, or both. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). **JSON example:** { "bindings": [ { "role": "roles/resourcemanager.organizationAdmin", "members": [ "user:mike@example.com", "group:admins@example.com", "domain:google.com", "serviceAccount:my-project-id@appspot.gserviceaccount.com" ] }, { "role": "roles/resourcemanager.organizationViewer", "members": [ "user:eve@example.com" ], "condition": { "title": "expirable access", "description": "Does not grant access after Sep 2020", "expression": "request.time < timestamp('2020-10-01T00:00:00.000Z')", } } ], "etag": "BwWWja0YfJA=", "version": 3 } **YAML example:** bindings: - members: - user:mike@example.com - group:admins@example.com - domain:google.com - serviceAccount:my-project-id@appspot.gserviceaccount.com role: roles/resourcemanager.organizationAdmin - members: - user:eve@example.com role: roles/resourcemanager.organizationViewer condition: title: expirable access description: Does not grant access after Sep 2020 expression: request.time < timestamp('2020-10-01T00:00:00.000Z') etag: BwWWja0YfJA= version: 3 For a description of IAM and its features, see the [IAM documentation](https://cloud.google.com/iam/docs/). */ GoogleIamV1Policy: { - /** - * Specifies cloud audit logging configuration for this policy. - */ + /** @description Specifies cloud audit logging configuration for this policy. */ auditConfigs?: components["schemas"]["GoogleIamV1AuditConfig"][]; - /** - * Associates a list of `members`, or principals, with a `role`. Optionally, may specify a `condition` that determines how and when the `bindings` are applied. Each of the `bindings` must contain at least one principal. The `bindings` in a `Policy` can refer to up to 1,500 principals; up to 250 of these principals can be Google groups. Each occurrence of a principal counts towards these limits. For example, if the `bindings` grant 50 different roles to `user:alice@example.com`, and not to any other principal, then you can add another 1,450 principals to the `bindings` in the `Policy`. - */ + /** @description Associates a list of `members`, or principals, with a `role`. Optionally, may specify a `condition` that determines how and when the `bindings` are applied. Each of the `bindings` must contain at least one principal. The `bindings` in a `Policy` can refer to up to 1,500 principals; up to 250 of these principals can be Google groups. Each occurrence of a principal counts towards these limits. For example, if the `bindings` grant 50 different roles to `user:alice@example.com`, and not to any other principal, then you can add another 1,450 principals to the `bindings` in the `Policy`. */ bindings?: components["schemas"]["GoogleIamV1Binding"][]; /** - * `etag` is used for optimistic concurrency control as a way to help prevent simultaneous updates of a policy from overwriting each other. It is strongly suggested that systems make use of the `etag` in the read-modify-write cycle to perform policy updates in order to avoid race conditions: An `etag` is returned in the response to `getIamPolicy`, and systems are expected to put that etag in the request to `setIamPolicy` to ensure that their change will be applied to the same version of the policy. **Important:** If you use IAM Conditions, you must include the `etag` field whenever you call `setIamPolicy`. If you omit this field, then IAM allows you to overwrite a version `3` policy with a version `1` policy, and all of the conditions in the version `3` policy are lost. + * Format: byte + * @description `etag` is used for optimistic concurrency control as a way to help prevent simultaneous updates of a policy from overwriting each other. It is strongly suggested that systems make use of the `etag` in the read-modify-write cycle to perform policy updates in order to avoid race conditions: An `etag` is returned in the response to `getIamPolicy`, and systems are expected to put that etag in the request to `setIamPolicy` to ensure that their change will be applied to the same version of the policy. **Important:** If you use IAM Conditions, you must include the `etag` field whenever you call `setIamPolicy`. If you omit this field, then IAM allows you to overwrite a version `3` policy with a version `1` policy, and all of the conditions in the version `3` policy are lost. */ etag?: string; /** - * Specifies the format of the policy. Valid values are `0`, `1`, and `3`. Requests that specify an invalid value are rejected. Any operation that affects conditional role bindings must specify version `3`. This requirement applies to the following operations: * Getting a policy that includes a conditional role binding * Adding a conditional role binding to a policy * Changing a conditional role binding in a policy * Removing any role binding, with or without a condition, from a policy that includes conditions **Important:** If you use IAM Conditions, you must include the `etag` field whenever you call `setIamPolicy`. If you omit this field, then IAM allows you to overwrite a version `3` policy with a version `1` policy, and all of the conditions in the version `3` policy are lost. If a policy does not include any conditions, operations on that policy may specify any valid version or leave the field unset. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). + * Format: int32 + * @description Specifies the format of the policy. Valid values are `0`, `1`, and `3`. Requests that specify an invalid value are rejected. Any operation that affects conditional role bindings must specify version `3`. This requirement applies to the following operations: * Getting a policy that includes a conditional role binding * Adding a conditional role binding to a policy * Changing a conditional role binding in a policy * Removing any role binding, with or without a condition, from a policy that includes conditions **Important:** If you use IAM Conditions, you must include the `etag` field whenever you call `setIamPolicy`. If you omit this field, then IAM allows you to overwrite a version `3` policy with a version `1` policy, and all of the conditions in the version `3` policy are lost. If a policy does not include any conditions, operations on that policy may specify any valid version or leave the field unset. To learn which resources support conditions in their IAM policies, see the [IAM documentation](https://cloud.google.com/iam/help/conditions/resource-policies). */ version?: number; }; - /** - * Request message for `SetIamPolicy` method. - */ + /** @description Request message for `SetIamPolicy` method. */ GoogleIamV1SetIamPolicyRequest: { policy?: components["schemas"]["GoogleIamV1Policy"]; /** - * OPTIONAL: A FieldMask specifying which fields of the policy to modify. Only the fields in the mask will be modified. If no mask is provided, the following default mask is used: `paths: "bindings, etag"` + * Format: google-fieldmask + * @description OPTIONAL: A FieldMask specifying which fields of the policy to modify. Only the fields in the mask will be modified. If no mask is provided, the following default mask is used: `paths: "bindings, etag"` */ updateMask?: string; }; - /** - * Request message for `TestIamPermissions` method. - */ + /** @description Request message for `TestIamPermissions` method. */ GoogleIamV1TestIamPermissionsRequest: { - /** - * The set of permissions to check for the `resource`. Permissions with wildcards (such as `*` or `storage.*`) are not allowed. For more information see [IAM Overview](https://cloud.google.com/iam/docs/overview#permissions). - */ + /** @description The set of permissions to check for the `resource`. Permissions with wildcards (such as `*` or `storage.*`) are not allowed. For more information see [IAM Overview](https://cloud.google.com/iam/docs/overview#permissions). */ permissions?: string[]; }; - /** - * Response message for `TestIamPermissions` method. - */ + /** @description Response message for `TestIamPermissions` method. */ GoogleIamV1TestIamPermissionsResponse: { - /** - * A subset of `TestPermissionsRequest.permissions` that the caller is allowed. - */ + /** @description A subset of `TestPermissionsRequest.permissions` that the caller is allowed. */ permissions?: string[]; }; - /** - * A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. A typical example is to use it as the request or the response type of an API method. For instance: service Foo { rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); } - */ - GoogleProtobufEmpty: { [key: string]: any }; - /** - * Represents a textual expression in the Common Expression Language (CEL) syntax. CEL is a C-like expression language. The syntax and semantics of CEL are documented at https://github.com/google/cel-spec. Example (Comparison): title: "Summary size limit" description: "Determines if a summary is less than 100 chars" expression: "document.summary.size() < 100" Example (Equality): title: "Requestor is owner" description: "Determines if requestor is the document owner" expression: "document.owner == request.auth.claims.email" Example (Logic): title: "Public documents" description: "Determine whether the document should be publicly visible" expression: "document.type != 'private' && document.type != 'internal'" Example (Data Manipulation): title: "Notification string" description: "Create a notification string with a timestamp." expression: "'New message received at ' + string(document.create_time)" The exact variables and functions that may be referenced within an expression are determined by the service that evaluates it. See the service documentation for additional information. - */ + /** @description A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. A typical example is to use it as the request or the response type of an API method. For instance: service Foo { rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); } */ + GoogleProtobufEmpty: { [key: string]: unknown }; + /** @description Represents a textual expression in the Common Expression Language (CEL) syntax. CEL is a C-like expression language. The syntax and semantics of CEL are documented at https://github.com/google/cel-spec. Example (Comparison): title: "Summary size limit" description: "Determines if a summary is less than 100 chars" expression: "document.summary.size() < 100" Example (Equality): title: "Requestor is owner" description: "Determines if requestor is the document owner" expression: "document.owner == request.auth.claims.email" Example (Logic): title: "Public documents" description: "Determine whether the document should be publicly visible" expression: "document.type != 'private' && document.type != 'internal'" Example (Data Manipulation): title: "Notification string" description: "Create a notification string with a timestamp." expression: "'New message received at ' + string(document.create_time)" The exact variables and functions that may be referenced within an expression are determined by the service that evaluates it. See the service documentation for additional information. */ GoogleTypeExpr: { - /** - * Optional. Description of the expression. This is a longer text which describes the expression, e.g. when hovered over it in a UI. - */ + /** @description Optional. Description of the expression. This is a longer text which describes the expression, e.g. when hovered over it in a UI. */ description?: string; - /** - * Textual representation of an expression in Common Expression Language syntax. - */ + /** @description Textual representation of an expression in Common Expression Language syntax. */ expression?: string; - /** - * Optional. String indicating the location of the expression for error reporting, e.g. a file name and a position in the file. - */ + /** @description Optional. String indicating the location of the expression for error reporting, e.g. a file name and a position in the file. */ location?: string; - /** - * Optional. Title for the expression, i.e. a short string describing its purpose. This can be used e.g. in UIs which allow to enter the expression. - */ + /** @description Optional. Title for the expression, i.e. a short string describing its purpose. This can be used e.g. in UIs which allow to enter the expression. */ title?: string; }; GrantTokenRequest: { - /** - * ID token to exchange for an access token and a refresh token. This field is called `code` to conform with the OAuth 2.0 specification. This field is deprecated and is ignored. - */ + /** @description ID token to exchange for an access token and a refresh token. This field is called `code` to conform with the OAuth 2.0 specification. This field is deprecated and is ignored. */ code?: string; - /** - * The grant_types that are supported: - `refresh_token` to exchange a Identity Platform refresh_token for Identity Platform id_token/access_token and possibly a new Identity Platform refresh_token. - */ + /** @description The grant_types that are supported: - `refresh_token` to exchange a Identity Platform refresh_token for Identity Platform id_token/access_token and possibly a new Identity Platform refresh_token. */ grantType?: string; - /** - * Identity Platform refresh_token. This field is ignored if `grantType` isn't `refresh_token`. - */ + /** @description Identity Platform refresh_token. This field is ignored if `grantType` isn't `refresh_token`. */ refreshToken?: string; }; GrantTokenResponse: { - /** - * DEPRECATED The granted access token. - */ + /** @description DEPRECATED The granted access token. */ access_token?: string; /** - * Expiration time of `access_token` in seconds. + * Format: int64 + * @description Expiration time of `access_token` in seconds. */ expires_in?: string; - /** - * The granted ID token - */ + /** @description The granted ID token */ id_token?: string; /** - * The client's project number + * Format: int64 + * @description The client's project number */ project_id?: string; - /** - * The granted refresh token; might be the same as `refreshToken` in the request. - */ + /** @description The granted refresh token; might be the same as `refreshToken` in the request. */ refresh_token?: string; - /** - * The type of `access_token`. Included to conform with the OAuth 2.0 specification; always `Bearer`. - */ + /** @description The type of `access_token`. Included to conform with the OAuth 2.0 specification; always `Bearer`. */ token_type?: string; - /** - * The local user ID - */ + /** @description The local user ID */ user_id?: string; }; - /** - * Emulator-specific configuration. - */ - EmulatorV1ProjectsConfig: { signIn?: { allowDuplicateEmails?: boolean } }; - /** - * Details of all pending confirmation codes. - */ - EmulatorV1ProjectsOobCodes: { - oobCodes?: { email?: string; oobCode?: string; oobLink?: string; requestType?: string }[]; + /** @description Emulator-specific configuration. */ + EmulatorV1ProjectsConfig: { + signIn?: { + allowDuplicateEmails?: boolean; + }; }; - /** - * Details of all pending verification codes. - */ + /** @description Details of all pending confirmation codes. */ + EmulatorV1ProjectsOobCodes: { + oobCodes?: { + email?: string; + oobCode?: string; + oobLink?: string; + requestType?: string; + }[]; + }; + /** @description Details of all pending verification codes. */ EmulatorV1ProjectsVerificationCodes: { - verificationCodes?: { code?: string; phoneNumber?: string; sessionInfo?: string }[]; + verificationCodes?: { + code?: string; + phoneNumber?: string; + sessionInfo?: string; + }[]; + }; + }; + parameters: { + /** @description OAuth access token. */ + access_token: string; + /** @description Data format for response. */ + alt: "json" | "media" | "proto"; + /** @description JSONP */ + callback: string; + /** @description Selector specifying which fields to include in a partial response. */ + fields: string; + /** @description OAuth 2.0 token for the current user. */ + oauth_token: string; + /** @description Returns response with indentations and line breaks. */ + prettyPrint: boolean; + /** @description Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser: string; + /** @description Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType: string; + /** @description Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol: string; + }; + requestBodies: { + GoogleCloudIdentitytoolkitV1BatchDeleteAccountsRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1BatchDeleteAccountsRequest"]; + }; + }; + GoogleCloudIdentitytoolkitV1SignUpRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SignUpRequest"]; + }; + }; + GoogleCloudIdentitytoolkitV1DeleteAccountRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1DeleteAccountRequest"]; + }; + }; + GoogleCloudIdentitytoolkitV1UploadAccountRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1UploadAccountRequest"]; + }; + }; + GoogleCloudIdentitytoolkitV1CreateSessionCookieRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1CreateSessionCookieRequest"]; + }; + }; + GoogleCloudIdentitytoolkitV1GetAccountInfoRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1GetAccountInfoRequest"]; + }; + }; + GoogleCloudIdentitytoolkitV1GetOobCodeRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1GetOobCodeRequest"]; + }; + }; + GoogleCloudIdentitytoolkitV1SetAccountInfoRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SetAccountInfoRequest"]; + }; + }; + GoogleCloudIdentitytoolkitV1QueryUserInfoRequest: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1QueryUserInfoRequest"]; + }; + }; + GoogleCloudIdentitytoolkitAdminV2Tenant: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Tenant"]; + }; + }; + GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + }; + GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + }; + GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; }; }; } + +export interface operations { + /** If an email identifier is specified, checks and returns if any user account is registered with the email. If there is a registered account, fetches all providers associated with the account's email. If the provider ID of an Identity Provider (IdP) is specified, creates an authorization URI for the IdP. The user can be directed to this URI to sign in with the IdP. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.createAuthUri": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1CreateAuthUriResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1CreateAuthUriRequest"]; + }; + }; + }; + /** Deletes a user's account. */ + "identitytoolkit.accounts.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1DeleteAccountResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1DeleteAccountRequest"]; + }; + /** Experimental */ + "identitytoolkit.accounts.issueSamlResponse": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1IssueSamlResponseResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1IssueSamlResponseRequest"]; + }; + }; + }; + /** Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria. */ + "identitytoolkit.accounts.lookup": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetAccountInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1GetAccountInfoRequest"]; + }; + /** Resets the password of an account either using an out-of-band code generated by sendOobCode or by specifying the email and password of the account to be modified. Can also check the purpose of an out-of-band code without consuming it. */ + "identitytoolkit.accounts.resetPassword": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1ResetPasswordResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1ResetPasswordRequest"]; + }; + }; + }; + /** Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it. */ + "identitytoolkit.accounts.sendOobCode": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetOobCodeResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1GetOobCodeRequest"]; + }; + /** Sends a SMS verification code for phone number sign-in. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.sendVerificationCode": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SendVerificationCodeResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SendVerificationCodeRequest"]; + }; + }; + }; + /** Signs in or signs up a user by exchanging a custom Auth token. Upon a successful sign-in or sign-up, a new Identity Platform ID token and refresh token are issued for the user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.signInWithCustomToken": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithCustomTokenResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithCustomTokenRequest"]; + }; + }; + }; + /** Signs in or signs up a user with a out-of-band code from an email link. If a user does not exist with the given email address, a user record will be created. If the sign-in succeeds, an Identity Platform ID and refresh token are issued for the authenticated user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.signInWithEmailLink": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithEmailLinkResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithEmailLinkRequest"]; + }; + }; + }; + /** Signs in or signs up a user with iOS Game Center credentials. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. The bundle ID is required in the request header as `x-ios-bundle-identifier`. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.signInWithGameCenter": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithGameCenterResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithGameCenterRequest"]; + }; + }; + }; + /** Signs in or signs up a user using credentials from an Identity Provider (IdP). This is done by manually providing an IdP credential, or by providing the authorization response obtained via the authorization request from CreateAuthUri. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. A new Identity Platform user account will be created if the user has not previously signed in to the IdP with the same account. In addition, when the "One account per email address" setting is enabled, there should not be an existing Identity Platform user account with the same email address for a new user account to be created. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.signInWithIdp": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithIdpResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithIdpRequest"]; + }; + }; + }; + /** Signs in a user with email and password. If the sign-in succeeds, a new Identity Platform ID token and refresh token are issued for the authenticated user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.signInWithPassword": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithPasswordResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithPasswordRequest"]; + }; + }; + }; + /** Completes a phone number authentication attempt. If a user already exists with the given phone number, an ID token is minted for that user. Otherwise, a new user is created and associated with the phone number. This method may also be used to link a phone number to an existing user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.signInWithPhoneNumber": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithPhoneNumberResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1SignInWithPhoneNumberRequest"]; + }; + }; + }; + /** Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.signUp": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignUpResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1SignUpRequest"]; + }; + /** Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported. */ + "identitytoolkit.accounts.update": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SetAccountInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1SetAccountInfoRequest"]; + }; + /** Verifies an iOS client is a real iOS device. If the request is valid, a receipt will be sent in the response and a secret will be sent via Apple Push Notification Service. The client should send both of them back to certain Identity Platform APIs in a later call (for example, /accounts:sendVerificationCode), in order to verify the client. The bundle ID is required in the request header as `x-ios-bundle-identifier`. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.accounts.verifyIosClient": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1VerifyIosClientResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV1VerifyIosClientRequest"]; + }; + }; + }; + /** Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.projects.accounts": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The project ID of the project which the user should belong to. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). If this is not set, the target project is inferred from the scope associated to the Bearer access token. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignUpResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1SignUpRequest"]; + }; + /** Creates a session cookie for the given Identity Platform ID token. The session cookie is used by the client to preserve the user's login state. */ + "identitytoolkit.projects.createSessionCookie": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the project that the account belongs to. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1CreateSessionCookieResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1CreateSessionCookieRequest"]; + }; + /** Looks up user accounts within a project or a tenant based on conditions in the request. */ + "identitytoolkit.projects.queryAccounts": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the project to which the result is scoped. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1QueryUserInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1QueryUserInfoRequest"]; + }; + /** Uploads multiple accounts into the Google Cloud project. If there is a problem uploading one or more of the accounts, the rest will be uploaded, and a list of the errors will be returned. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ + "identitytoolkit.projects.accounts.batchCreate": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The Project ID of the Identity Platform project which the account belongs to. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1UploadAccountResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1UploadAccountRequest"]; + }; + /** Batch deletes multiple accounts. For accounts that fail to be deleted, error info is contained in the response. The method ignores accounts that do not exist or are duplicated in the request. This method requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). */ + "identitytoolkit.projects.accounts.batchDelete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** If `tenant_id` is specified, the ID of the Google Cloud project that the Identity Platform tenant belongs to. Otherwise, the ID of the Google Cloud project that accounts belong to. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1BatchDeleteAccountsResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1BatchDeleteAccountsRequest"]; + }; + /** Download account information for all accounts on the project in a paginated manner. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control).. Furthermore, additional permissions are needed to get password hash, password salt, and password version from accounts; otherwise these fields are redacted. */ + "identitytoolkit.projects.accounts.batchGet": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + delegatedProjectNumber?: string; + /** The maximum number of results to return. Must be at least 1 and no greater than 1000. By default, it is 20. */ + maxResults?: number; + /** The pagination token from the response of a previous request. */ + nextPageToken?: string; + /** The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned. */ + tenantId?: string; + }; + path: { + /** If `tenant_id` is specified, the ID of the Google Cloud project that the Identity Platform tenant belongs to. Otherwise, the ID of the Google Cloud project that the accounts belong to. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1DownloadAccountResponse"]; + }; + }; + }; + }; + /** Deletes a user's account. */ + "identitytoolkit.projects.accounts.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the project which the account belongs to. Should only be specified in authenticated requests that specify local_id of an account. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1DeleteAccountResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1DeleteAccountRequest"]; + }; + /** Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria. */ + "identitytoolkit.projects.accounts.lookup": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the Google Cloud project that the account or the Identity Platform tenant specified by `tenant_id` belongs to. Should only be specified by authenticated requests bearing a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetAccountInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1GetAccountInfoRequest"]; + }; + /** Looks up user accounts within a project or a tenant based on conditions in the request. */ + "identitytoolkit.projects.accounts.query": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the project to which the result is scoped. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1QueryUserInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1QueryUserInfoRequest"]; + }; + /** Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it. */ + "identitytoolkit.projects.accounts.sendOobCode": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The Project ID of the Identity Platform project which the account belongs to. To specify this field, it requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetOobCodeResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1GetOobCodeRequest"]; + }; + /** Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported. */ + "identitytoolkit.projects.accounts.update": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SetAccountInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1SetAccountInfoRequest"]; + }; + /** Signs up a new email and password user or anonymous user, or upgrades an anonymous user to email and password. For an admin request with a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control), creates a new anonymous, email and password, or phone number user. An [API key](https://cloud.google.com/docs/authentication/api-keys) is required in the request in order to identify the Google Cloud project. */ + "identitytoolkit.projects.tenants.accounts": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The project ID of the project which the user should belong to. Specifying this field requires a Google OAuth 2.0 credential with the proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). If this is not set, the target project is inferred from the scope associated to the Bearer access token. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant to create a user under. If not set, the user will be created under the default Identity Platform project. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SignUpResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1SignUpRequest"]; + }; + /** Creates a session cookie for the given Identity Platform ID token. The session cookie is used by the client to preserve the user's login state. */ + "identitytoolkit.projects.tenants.createSessionCookie": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the project that the account belongs to. */ + targetProjectId: string; + /** The tenant ID of the Identity Platform tenant the account belongs to. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1CreateSessionCookieResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1CreateSessionCookieRequest"]; + }; + /** Uploads multiple accounts into the Google Cloud project. If there is a problem uploading one or more of the accounts, the rest will be uploaded, and a list of the errors will be returned. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ + "identitytoolkit.projects.tenants.accounts.batchCreate": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The Project ID of the Identity Platform project which the account belongs to. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant the account belongs to. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1UploadAccountResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1UploadAccountRequest"]; + }; + /** Batch deletes multiple accounts. For accounts that fail to be deleted, error info is contained in the response. The method ignores accounts that do not exist or are duplicated in the request. This method requires a Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). */ + "identitytoolkit.projects.tenants.accounts.batchDelete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** If `tenant_id` is specified, the ID of the Google Cloud project that the Identity Platform tenant belongs to. Otherwise, the ID of the Google Cloud project that accounts belong to. */ + targetProjectId: string; + /** If the accounts belong to an Identity Platform tenant, the ID of the tenant. If the accounts belong to an default Identity Platform project, the field is not needed. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1BatchDeleteAccountsResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1BatchDeleteAccountsRequest"]; + }; + /** Download account information for all accounts on the project in a paginated manner. To use this method requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control).. Furthermore, additional permissions are needed to get password hash, password salt, and password version from accounts; otherwise these fields are redacted. */ + "identitytoolkit.projects.tenants.accounts.batchGet": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + delegatedProjectNumber?: string; + /** The maximum number of results to return. Must be at least 1 and no greater than 1000. By default, it is 20. */ + maxResults?: number; + /** The pagination token from the response of a previous request. */ + nextPageToken?: string; + }; + path: { + /** If `tenant_id` is specified, the ID of the Google Cloud project that the Identity Platform tenant belongs to. Otherwise, the ID of the Google Cloud project that the accounts belong to. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1DownloadAccountResponse"]; + }; + }; + }; + }; + /** Deletes a user's account. */ + "identitytoolkit.projects.tenants.accounts.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the project which the account belongs to. Should only be specified in authenticated requests that specify local_id of an account. */ + targetProjectId: string; + /** The ID of the tenant that the account belongs to, if applicable. Only require to be specified for authenticated requests bearing a Google OAuth 2.0 credential that specify local_id of an account that belongs to an Identity Platform tenant. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1DeleteAccountResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1DeleteAccountRequest"]; + }; + /** Gets account information for all matched accounts. For an end user request, retrieves the account of the end user. For an admin request with Google OAuth 2.0 credential, retrieves one or multiple account(s) with matching criteria. */ + "identitytoolkit.projects.tenants.accounts.lookup": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the Google Cloud project that the account or the Identity Platform tenant specified by `tenant_id` belongs to. Should only be specified by authenticated requests bearing a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ + targetProjectId: string; + /** The ID of the tenant that the account belongs to. Should only be specified by authenticated requests from a developer. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetAccountInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1GetAccountInfoRequest"]; + }; + /** Looks up user accounts within a project or a tenant based on conditions in the request. */ + "identitytoolkit.projects.tenants.accounts.query": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The ID of the project to which the result is scoped. */ + targetProjectId: string; + /** The ID of the tenant to which the result is scoped. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1QueryUserInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1QueryUserInfoRequest"]; + }; + /** Sends an out-of-band confirmation code for an account. Requests from a authenticated request can optionally return a link including the OOB code instead of sending it. */ + "identitytoolkit.projects.tenants.accounts.sendOobCode": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The Project ID of the Identity Platform project which the account belongs to. To specify this field, it requires a Google OAuth 2.0 credential with proper [permissions](https://cloud.google.com/identity-platform/docs/access-control). */ + targetProjectId: string; + /** The tenant ID of the Identity Platform tenant the account belongs to. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetOobCodeResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1GetOobCodeRequest"]; + }; + /** Updates account-related information for the specified user by setting specific fields or applying action codes. Requests from administrators and end users are supported. */ + "identitytoolkit.projects.tenants.accounts.update": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + /** The project ID for the project that the account belongs to. Specifying this field requires Google OAuth 2.0 credential with proper [permissions] (https://cloud.google.com/identity-platform/docs/access-control). Requests from end users should pass an Identity Platform ID token instead. */ + targetProjectId: string; + /** The tenant ID of the Identity Platform tenant that the account belongs to. Requests from end users should pass an Identity Platform ID token rather than setting this field. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1SetAccountInfoResponse"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitV1SetAccountInfoRequest"]; + }; + /** Gets a project's public Identity Toolkit configuration. (Legacy) This method also supports authenticated calls from a developer to retrieve non-public configuration. */ + "identitytoolkit.getProjects": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** Android package name to check against the real android package name. If this field is provided, and sha1_cert_hash is not provided, the action will throw an error if this does not match the real android package name. */ + androidPackageName?: string; + /** The RP OAuth client ID. If set, a check will be performed to ensure that the OAuth client is valid for the retrieved project and the request rejected with a client error if not valid. */ + clientId?: string; + /** Project Number of the delegated project request. This field should only be used as part of the Firebase V1 migration. */ + delegatedProjectNumber?: string; + /** The Firebase app ID, for applications that use Firebase. This can be found in the Firebase console for your project. If set, a check will be performed to ensure that the app ID is valid for the retrieved project. If not valid, the request will be rejected with a client error. */ + firebaseAppId?: string; + /** iOS bundle id to check against the real ios bundle id. If this field is provided, the action will throw an error if this does not match the real iOS bundle id. */ + iosBundleId?: string; + /** Project number of the configuration to retrieve. This field is deprecated and should not be used by new integrations. */ + projectNumber?: string; + /** Whether dynamic link should be returned. */ + returnDynamicLink?: boolean; + /** SHA-1 Android application cert hash. If set, a check will be performed to ensure that the cert hash is valid for the retrieved project and android_package_name. */ + sha1Cert?: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetProjectConfigResponse"]; + }; + }; + }; + }; + /** Gets parameters needed for generating a reCAPTCHA challenge. */ + "identitytoolkit.getRecaptchaParams": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetRecaptchaParamResponse"]; + }; + }; + }; + }; + /** Retrieves the set of public keys of the session cookie JSON Web Token (JWT) signer that can be used to validate the session cookie created through createSessionCookie. */ + "identitytoolkit.getSessionCookiePublicKeys": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV1GetSessionCookiePublicKeysResponse"]; + }; + }; + }; + }; + /** Finishes enrolling a second factor for the user. */ + "identitytoolkit.accounts.mfaEnrollment.finalize": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV2FinalizeMfaEnrollmentResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV2FinalizeMfaEnrollmentRequest"]; + }; + }; + }; + /** Step one of the MFA enrollment process. In SMS case, this sends an SMS verification code to the user. */ + "identitytoolkit.accounts.mfaEnrollment.start": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV2StartMfaEnrollmentResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV2StartMfaEnrollmentRequest"]; + }; + }; + }; + /** Revokes one second factor from the enrolled second factors for an account. */ + "identitytoolkit.accounts.mfaEnrollment.withdraw": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV2WithdrawMfaResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV2WithdrawMfaRequest"]; + }; + }; + }; + /** Verifies the MFA challenge and performs sign-in */ + "identitytoolkit.accounts.mfaSignIn.finalize": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV2FinalizeMfaSignInResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV2FinalizeMfaSignInRequest"]; + }; + }; + }; + /** Sends the MFA challenge */ + "identitytoolkit.accounts.mfaSignIn.start": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitV2StartMfaSignInResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitV2StartMfaSignInRequest"]; + }; + }; + }; + /** List all default supported Idps. */ + "identitytoolkit.defaultSupportedIdps.list": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The maximum number of items to return. */ + pageSize?: number; + /** The next_page_token value returned from a previous List request, if any. */ + pageToken?: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpsResponse"]; + }; + }; + }; + }; + /** Retrieve an Identity Toolkit project configuration. */ + "identitytoolkit.projects.getConfig": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Config"]; + }; + }; + }; + }; + /** Update an Identity Toolkit project configuration. */ + "identitytoolkit.projects.updateConfig": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The update mask applies to the resource. Fields set in the config but not included in this update mask will be ignored. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask */ + updateMask?: string; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Config"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Config"]; + }; + }; + }; + /** List all default supported Idp configurations for an Identity Toolkit project. */ + "identitytoolkit.projects.defaultSupportedIdpConfigs.list": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The maximum number of items to return. */ + pageSize?: number; + /** The next_page_token value returned from a previous List request, if any. */ + pageToken?: string; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpConfigsResponse"]; + }; + }; + }; + }; + /** Create a default supported Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.defaultSupportedIdpConfigs.create": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The id of the Idp to create a config for. Call ListDefaultSupportedIdps for list of all default supported Idps. */ + idpId?: string; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + /** Retrieve a default supported Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.defaultSupportedIdpConfigs.get": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + defaultSupportedIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + }; + }; + }; + /** Delete a default supported Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.defaultSupportedIdpConfigs.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + defaultSupportedIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleProtobufEmpty"]; + }; + }; + }; + }; + /** Update a default supported Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.defaultSupportedIdpConfigs.patch": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The update mask applies to the resource. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask */ + updateMask?: string; + }; + path: { + targetProjectId: string; + defaultSupportedIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + /** Initialize Identity Platform for a Cloud project. Identity Platform is an end-to-end authentication system for third-party users to access your apps and services. These could include mobile/web apps, games, APIs and beyond. This is the publicly available variant of EnableIdentityPlatform that is only available to billing-enabled projects. */ + "identitytoolkit.projects.identityPlatform.initializeAuth": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InitializeIdentityPlatformRequest"]; + }; + }; + }; + /** List all inbound SAML configurations for an Identity Toolkit project. */ + "identitytoolkit.projects.inboundSamlConfigs.list": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The maximum number of items to return. */ + pageSize?: number; + /** The next_page_token value returned from a previous List request, if any. */ + pageToken?: string; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ListInboundSamlConfigsResponse"]; + }; + }; + }; + }; + /** Create an inbound SAML configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.inboundSamlConfigs.create": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The id to use for this config. */ + inboundSamlConfigId?: string; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + /** Retrieve an inbound SAML configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.inboundSamlConfigs.get": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + inboundSamlConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + }; + }; + }; + /** Delete an inbound SAML configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.inboundSamlConfigs.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + inboundSamlConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleProtobufEmpty"]; + }; + }; + }; + }; + /** Update an inbound SAML configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.inboundSamlConfigs.patch": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The update mask applies to the resource. Empty update mask will result in updating nothing. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask */ + updateMask?: string; + }; + path: { + targetProjectId: string; + inboundSamlConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + /** List all Oidc Idp configurations for an Identity Toolkit project. */ + "identitytoolkit.projects.oauthIdpConfigs.list": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The maximum number of items to return. */ + pageSize?: number; + /** The next_page_token value returned from a previous List request, if any. */ + pageToken?: string; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ListOAuthIdpConfigsResponse"]; + }; + }; + }; + }; + /** Create an Oidc Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.oauthIdpConfigs.create": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The id to use for this config. */ + oauthIdpConfigId?: string; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + /** Retrieve an Oidc Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.oauthIdpConfigs.get": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + oauthIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + }; + }; + }; + /** Delete an Oidc Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.oauthIdpConfigs.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + oauthIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleProtobufEmpty"]; + }; + }; + }; + }; + /** Update an Oidc Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.oauthIdpConfigs.patch": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The update mask applies to the resource. Empty update mask will result in updating nothing. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask */ + updateMask?: string; + }; + path: { + targetProjectId: string; + oauthIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + /** List tenants under the given agent project. Requires read permission on the Agent project. */ + "identitytoolkit.projects.tenants.list": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The maximum number of results to return, capped at 1000. If not specified, the default value is 20. */ + pageSize?: number; + /** The pagination token from the response of a previous request. */ + pageToken?: string; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ListTenantsResponse"]; + }; + }; + }; + }; + /** Create a tenant. Requires write permission on the Agent project. */ + "identitytoolkit.projects.tenants.create": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Tenant"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2Tenant"]; + }; + /** Get a tenant. Requires read permission on the Tenant resource. */ + "identitytoolkit.projects.tenants.get": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Tenant"]; + }; + }; + }; + }; + /** Delete a tenant. Requires write permission on the Agent project. */ + "identitytoolkit.projects.tenants.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleProtobufEmpty"]; + }; + }; + }; + }; + /** Update a tenant. Requires write permission on the Tenant resource. */ + "identitytoolkit.projects.tenants.patch": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** If provided, only update fields set in the update mask. Otherwise, all settable fields will be updated. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask */ + updateMask?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2Tenant"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2Tenant"]; + }; + /** Gets the access control policy for a resource. An error is returned if the resource does not exist. An empty policy is returned if the resource exists but does not have a policy set on it. Caller must have the right Google IAM permission on the resource. */ + "identitytoolkit.projects.tenants.getIamPolicy": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleIamV1Policy"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleIamV1GetIamPolicyRequest"]; + }; + }; + }; + /** Sets the access control policy for a resource. If the policy exists, it is replaced. Caller must have the right Google IAM permission on the resource. */ + "identitytoolkit.projects.tenants.setIamPolicy": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleIamV1Policy"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleIamV1SetIamPolicyRequest"]; + }; + }; + }; + /** Returns the caller's permissions on a resource. An error is returned if the resource does not exist. A caller is not required to have Google IAM permission to make this request. */ + "identitytoolkit.projects.tenants.testIamPermissions": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleIamV1TestIamPermissionsResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GoogleIamV1TestIamPermissionsRequest"]; + }; + }; + }; + /** List all default supported Idp configurations for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.list": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The maximum number of items to return. */ + pageSize?: number; + /** The next_page_token value returned from a previous List request, if any. */ + pageToken?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ListDefaultSupportedIdpConfigsResponse"]; + }; + }; + }; + }; + /** Create a default supported Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.create": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The id of the Idp to create a config for. Call ListDefaultSupportedIdps for list of all default supported Idps. */ + idpId?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + /** Retrieve a default supported Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.get": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + defaultSupportedIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + }; + }; + }; + /** Delete a default supported Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + defaultSupportedIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleProtobufEmpty"]; + }; + }; + }; + }; + /** Update a default supported Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.defaultSupportedIdpConfigs.patch": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The update mask applies to the resource. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask */ + updateMask?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + defaultSupportedIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2DefaultSupportedIdpConfig"]; + }; + /** List all inbound SAML configurations for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.inboundSamlConfigs.list": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The maximum number of items to return. */ + pageSize?: number; + /** The next_page_token value returned from a previous List request, if any. */ + pageToken?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ListInboundSamlConfigsResponse"]; + }; + }; + }; + }; + /** Create an inbound SAML configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.inboundSamlConfigs.create": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The id to use for this config. */ + inboundSamlConfigId?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + /** Retrieve an inbound SAML configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.inboundSamlConfigs.get": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + inboundSamlConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + }; + }; + }; + /** Delete an inbound SAML configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.inboundSamlConfigs.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + inboundSamlConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleProtobufEmpty"]; + }; + }; + }; + }; + /** Update an inbound SAML configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.inboundSamlConfigs.patch": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The update mask applies to the resource. Empty update mask will result in updating nothing. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask */ + updateMask?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + inboundSamlConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2InboundSamlConfig"]; + }; + /** List all Oidc Idp configurations for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.oauthIdpConfigs.list": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The maximum number of items to return. */ + pageSize?: number; + /** The next_page_token value returned from a previous List request, if any. */ + pageToken?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2ListOAuthIdpConfigsResponse"]; + }; + }; + }; + }; + /** Create an Oidc Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.oauthIdpConfigs.create": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The id to use for this config. */ + oauthIdpConfigId?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + /** Retrieve an Oidc Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.oauthIdpConfigs.get": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + oauthIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + }; + }; + }; + /** Delete an Oidc Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.oauthIdpConfigs.delete": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + path: { + targetProjectId: string; + tenantId: string; + oauthIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleProtobufEmpty"]; + }; + }; + }; + }; + /** Update an Oidc Idp configuration for an Identity Toolkit project. */ + "identitytoolkit.projects.tenants.oauthIdpConfigs.patch": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + /** The update mask applies to the resource. Empty update mask will result in updating nothing. For the `FieldMask` definition, see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask */ + updateMask?: string; + }; + path: { + targetProjectId: string; + tenantId: string; + oauthIdpConfigsId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + }; + }; + requestBody: components["requestBodies"]["GoogleCloudIdentitytoolkitAdminV2OAuthIdpConfig"]; + }; + /** The Token Service API lets you exchange either an ID token or a refresh token for an access token and a new refresh token. You can use the access token to securely call APIs that require user authorization. */ + "securetoken.token": { + parameters: { + query: { + /** OAuth access token. */ + access_token?: components["parameters"]["access_token"]; + /** Data format for response. */ + alt?: components["parameters"]["alt"]; + /** JSONP */ + callback?: components["parameters"]["callback"]; + /** Selector specifying which fields to include in a partial response. */ + fields?: components["parameters"]["fields"]; + /** OAuth 2.0 token for the current user. */ + oauth_token?: components["parameters"]["oauth_token"]; + /** Returns response with indentations and line breaks. */ + prettyPrint?: components["parameters"]["prettyPrint"]; + /** Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. */ + quotaUser?: components["parameters"]["quotaUser"]; + /** Legacy upload protocol for media (e.g. "media", "multipart"). */ + uploadType?: components["parameters"]["uploadType"]; + /** Upload protocol for media (e.g. "raw", "multipart"). */ + upload_protocol?: components["parameters"]["upload_protocol"]; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "*/*": components["schemas"]["GrantTokenResponse"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GrantTokenRequest"]; + "application/x-www-form-urlencoded": components["schemas"]["GrantTokenRequest"]; + }; + }; + }; + /** Remove all accounts in the project, regardless of state. */ + "emulator.projects.accounts.delete": { + parameters: { + path: { + /** The ID of the Google Cloud project that the accounts belong to. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "application/json": { [key: string]: unknown }; + }; + }; + }; + }; + /** Get emulator-specific configuration for the project. */ + "emulator.projects.config.get": { + parameters: { + path: { + /** The ID of the Google Cloud project that the config belongs to. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "application/json": components["schemas"]["EmulatorV1ProjectsConfig"]; + }; + }; + }; + }; + /** Update emulator-specific configuration for the project. */ + "emulator.projects.config.update": { + parameters: { + path: { + /** The ID of the Google Cloud project that the config belongs to. */ + targetProjectId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "application/json": components["schemas"]["EmulatorV1ProjectsConfig"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EmulatorV1ProjectsConfig"]; + }; + }; + }; + /** List all pending confirmation codes for the project. */ + "emulator.projects.oobCodes.list": { + parameters: { + path: { + /** The ID of the Google Cloud project that the confirmation codes belongs to. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "application/json": components["schemas"]["EmulatorV1ProjectsOobCodes"]; + }; + }; + }; + }; + /** List all pending phone verification codes for the project. */ + "emulator.projects.verificationCodes.list": { + parameters: { + path: { + /** The ID of the Google Cloud project that the verification codes belongs to. */ + targetProjectId: string; + /** The ID of the Identity Platform tenant the accounts belongs to. If not specified, accounts on the Identity Platform project are returned. */ + tenantId: string; + }; + }; + responses: { + /** Successful response */ + 200: { + content: { + "application/json": components["schemas"]["EmulatorV1ProjectsOobCodes"]; + }; + }; + }; + }; +} + +export interface external {} From 2d2345faf8458470d2c679e51fb47020cb46365e Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 6 Feb 2023 15:54:11 -0800 Subject: [PATCH 0790/1699] marked dependency update (#5487) * import marked instead of requiring it * upgrade marked and types --- npm-shrinkwrap.json | 24 ++++++++++++------------ src/commands/ext-configure.ts | 3 +-- src/commands/ext-dev-init.ts | 3 +-- src/commands/ext-dev-publish.ts | 3 +-- src/commands/ext-dev-register.ts | 3 +-- src/commands/ext-info.ts | 3 +-- src/commands/ext-install.ts | 3 +-- src/commands/ext-uninstall.ts | 3 +-- src/commands/ext-update.ts | 3 +-- src/commands/hosting-channel-create.ts | 3 +-- src/commands/hosting-channel-delete.ts | 3 +-- src/commands/hosting-channel-deploy.ts | 3 +-- src/commands/hosting-clone.ts | 3 +-- src/extensions/askUserForConsent.ts | 3 +-- src/extensions/askUserForEventsConfig.ts | 2 +- src/extensions/askUserForParam.ts | 3 +-- src/extensions/billingMigrationHelper.ts | 3 +-- src/extensions/change-log.ts | 3 +-- src/extensions/displayExtensionInfo.ts | 3 +-- src/extensions/extensionsApi.ts | 3 +-- src/extensions/extensionsHelper.ts | 3 +-- src/extensions/provisioningHelper.ts | 3 +-- src/extensions/updateHelper.ts | 3 +-- src/extensions/warnings.ts | 3 +-- src/projectUtils.ts | 3 +-- 25 files changed, 36 insertions(+), 59 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 040f82d12c0..d2b169e4240 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2949,9 +2949,9 @@ } }, "node_modules/@types/marked": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz", - "integrity": "sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.8.tgz", + "integrity": "sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==", "dev": true }, "node_modules/@types/marked-terminal": { @@ -10510,9 +10510,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/marked": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", - "integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", "bin": { "marked": "bin/marked.js" }, @@ -18086,9 +18086,9 @@ } }, "@types/marked": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz", - "integrity": "sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.8.tgz", + "integrity": "sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==", "dev": true }, "@types/marked-terminal": { @@ -23877,9 +23877,9 @@ "requires": {} }, "marked": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", - "integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==" + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==" }, "marked-terminal": { "version": "5.1.1", diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 59acf297db8..13ac7acf4b6 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index 285e8462ecb..c5e3ac62142 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -1,7 +1,6 @@ import * as fs from "fs"; import * as path from "path"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; diff --git a/src/commands/ext-dev-publish.ts b/src/commands/ext-dev-publish.ts index 4de993f3f71..7a876921bbc 100644 --- a/src/commands/ext-dev-publish.ts +++ b/src/commands/ext-dev-publish.ts @@ -1,6 +1,5 @@ import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import { Command } from "../command"; diff --git a/src/commands/ext-dev-register.ts b/src/commands/ext-dev-register.ts index d6b1ec19e61..d8689648071 100644 --- a/src/commands/ext-dev-register.ts +++ b/src/commands/ext-dev-register.ts @@ -1,6 +1,5 @@ import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { Command } from "../command"; import { registerPublisherProfile } from "../extensions/extensionsApi"; diff --git a/src/commands/ext-info.ts b/src/commands/ext-info.ts index 4e46416bc77..157eb60285e 100644 --- a/src/commands/ext-info.ts +++ b/src/commands/ext-info.ts @@ -9,8 +9,7 @@ import { logger } from "../logger"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); const FUNCTION_TYPE_REGEX = /\..+\.function/; diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index c0cbc298d2b..096aeaca30b 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -1,6 +1,5 @@ import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import { displayExtInfo } from "../extensions/displayExtensionInfo"; diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 828da221969..2ec8134c9fd 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 011d3f42dc4..42e52072f9c 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -1,6 +1,5 @@ import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; diff --git a/src/commands/hosting-channel-create.ts b/src/commands/hosting-channel-create.ts index ae0bfd6645d..b8b5a7ba3b1 100644 --- a/src/commands/hosting-channel-create.ts +++ b/src/commands/hosting-channel-create.ts @@ -10,8 +10,7 @@ import { requirePermissions } from "../requirePermissions"; import { needProjectId } from "../projectUtils"; import { logger } from "../logger"; import { requireConfig } from "../requireConfig"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { requireHostingSite } from "../requireHostingSite"; const LOG_TAG = "hosting:channel"; diff --git a/src/commands/hosting-channel-delete.ts b/src/commands/hosting-channel-delete.ts index 04ce3dd12af..66a3b42516d 100644 --- a/src/commands/hosting-channel-delete.ts +++ b/src/commands/hosting-channel-delete.ts @@ -1,6 +1,5 @@ import { bold, underline } from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { Command } from "../command"; import { consoleUrl, logLabeledSuccess, logLabeledWarning } from "../utils"; diff --git a/src/commands/hosting-channel-deploy.ts b/src/commands/hosting-channel-deploy.ts index 78471c55c76..00819986c6b 100644 --- a/src/commands/hosting-channel-deploy.ts +++ b/src/commands/hosting-channel-deploy.ts @@ -19,8 +19,7 @@ import { requireConfig } from "../requireConfig"; import { DEFAULT_DURATION, calculateChannelExpireTTL } from "../hosting/expireUtils"; import { logLabeledSuccess, datetimeString, logLabeledWarning, consoleUrl } from "../utils"; import { hostingConfig } from "../hosting/config"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { requireHostingSite } from "../requireHostingSite"; import { HostingOptions } from "../hosting/options"; import { Options } from "../options"; diff --git a/src/commands/hosting-clone.ts b/src/commands/hosting-clone.ts index a1b994b5498..96cfc16ace2 100644 --- a/src/commands/hosting-clone.ts +++ b/src/commands/hosting-clone.ts @@ -13,8 +13,7 @@ import { } from "../hosting/api"; import * as utils from "../utils"; import { requireAuth } from "../requireAuth"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { logger } from "../logger"; export const command = new Command("hosting:clone ") diff --git a/src/extensions/askUserForConsent.ts b/src/extensions/askUserForConsent.ts index 82bc88b823a..9e6525b2b0d 100644 --- a/src/extensions/askUserForConsent.ts +++ b/src/extensions/askUserForConsent.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import { FirebaseError } from "../error"; diff --git a/src/extensions/askUserForEventsConfig.ts b/src/extensions/askUserForEventsConfig.ts index 99599fd255e..2707c305c24 100644 --- a/src/extensions/askUserForEventsConfig.ts +++ b/src/extensions/askUserForEventsConfig.ts @@ -4,7 +4,7 @@ import { EventDescriptor, ExtensionInstance } from "./types"; import * as utils from "../utils"; import * as clc from "colorette"; import { logger } from "../logger"; -const { marked } = require("marked"); +import { marked } from "marked"; export interface InstanceEventsConfig { channel: string; diff --git a/src/extensions/askUserForParam.ts b/src/extensions/askUserForParam.ts index ed5472b746f..94b1723a4b2 100644 --- a/src/extensions/askUserForParam.ts +++ b/src/extensions/askUserForParam.ts @@ -1,7 +1,6 @@ import * as _ from "lodash"; import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { Param, ParamOption, ParamType } from "./types"; import * as secretManagerApi from "../gcp/secretManager"; diff --git a/src/extensions/billingMigrationHelper.ts b/src/extensions/billingMigrationHelper.ts index 6152861e1b2..169c98ac6a6 100644 --- a/src/extensions/billingMigrationHelper.ts +++ b/src/extensions/billingMigrationHelper.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import { FirebaseError } from "../error"; diff --git a/src/extensions/change-log.ts b/src/extensions/change-log.ts index 8900658f267..ea6a21e0e73 100644 --- a/src/extensions/change-log.ts +++ b/src/extensions/change-log.ts @@ -1,6 +1,5 @@ import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import * as path from "path"; import * as semver from "semver"; import TerminalRenderer = require("marked-terminal"); diff --git a/src/extensions/displayExtensionInfo.ts b/src/extensions/displayExtensionInfo.ts index 381d03b548b..5be6b21d0f3 100644 --- a/src/extensions/displayExtensionInfo.ts +++ b/src/extensions/displayExtensionInfo.ts @@ -1,6 +1,5 @@ import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import TerminalRenderer = require("marked-terminal"); import * as utils from "../utils"; diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index 9c8f224e8dd..b0afd26cd20 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -1,7 +1,6 @@ import * as yaml from "js-yaml"; import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { Client } from "../apiv2"; import { extensionsOrigin } from "../api"; diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 51f66fe0359..f08b3c7506c 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -1,8 +1,7 @@ import * as clc from "colorette"; import * as ora from "ora"; import * as semver from "semver"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; const TerminalRenderer = require("marked-terminal"); marked.setOptions({ diff --git a/src/extensions/provisioningHelper.ts b/src/extensions/provisioningHelper.ts index ac4f91e3184..7cba701c250 100644 --- a/src/extensions/provisioningHelper.ts +++ b/src/extensions/provisioningHelper.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { ExtensionSpec } from "./types"; import { firebaseStorageOrigin, firedataOrigin } from "../api"; diff --git a/src/extensions/updateHelper.ts b/src/extensions/updateHelper.ts index 2e22a92f65a..521d91cb013 100644 --- a/src/extensions/updateHelper.ts +++ b/src/extensions/updateHelper.ts @@ -1,7 +1,6 @@ import * as clc from "colorette"; import * as semver from "semver"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import { FirebaseError } from "../error"; import { logger } from "../logger"; diff --git a/src/extensions/warnings.ts b/src/extensions/warnings.ts index e8e7da45084..4561e90852b 100644 --- a/src/extensions/warnings.ts +++ b/src/extensions/warnings.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; import * as clc from "colorette"; import { ExtensionVersion, RegistryLaunchStage } from "./types"; diff --git a/src/projectUtils.ts b/src/projectUtils.ts index 5cf1702b849..81ce5865e6e 100644 --- a/src/projectUtils.ts +++ b/src/projectUtils.ts @@ -2,8 +2,7 @@ import { getFirebaseProject } from "./management/projects"; import { RC } from "./rc"; import * as clc from "colorette"; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires -const { marked } = require("marked"); +import { marked } from "marked"; const { FirebaseError } = require("./error"); From ee1da271a98fcaa2207ff0b0bd750853383ee021 Mon Sep 17 00:00:00 2001 From: aalej Date: Tue, 7 Feb 2023 08:21:18 +0800 Subject: [PATCH 0791/1699] Added checker to see if the project ID is valid (#5483) * Added checker to see if the project ID is valid * Updated CHANGELOG.md * Removed period after the description in CHANGELOG.md --------- Co-authored-by: Bryan Kendall --- CHANGELOG.md | 1 + src/getDefaultHostingSite.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 882e2bcec1c..ed0bb0e654b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,4 +3,5 @@ - Fix bug where cloudevent emitted by various emulators didn't conform to spec (#5466) - Upgrade the emulator suite UI to 1.11.3 to capture some bug fixes (#5479) - Web frameworks deploys can once again bundle local NPM dependencies (#5440) +- Catches error when attempting to deploy without a project (#5415) - Fixes a number of issues and outdated dependencies in templates for `init --only functions` and `ext:dev:init` diff --git a/src/getDefaultHostingSite.ts b/src/getDefaultHostingSite.ts index aa931c0e029..cffc7b98f6a 100644 --- a/src/getDefaultHostingSite.ts +++ b/src/getDefaultHostingSite.ts @@ -1,5 +1,6 @@ import { logger } from "./logger"; import { getFirebaseProject } from "./management/projects"; +import { needProjectId } from "./projectUtils"; /** * Tries to determine the default hosting site for a project, else falls back to projectId. @@ -7,7 +8,8 @@ import { getFirebaseProject } from "./management/projects"; * @return The hosting site ID */ export async function getDefaultHostingSite(options: any): Promise { - const project = await getFirebaseProject(options.project); + const projectId = needProjectId(options); + const project = await getFirebaseProject(projectId); const site = project.resources?.hostingSite; if (!site) { logger.debug( From 50d9c1de7889d0cd13af8eb03c4dcb9fbbf9b452 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Tue, 7 Feb 2023 18:55:05 -0800 Subject: [PATCH 0792/1699] Release Emulator UI v1.11.4. (#5494) --- CHANGELOG.md | 2 ++ src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed0bb0e654b..84d3302ef6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,3 +5,5 @@ - Web frameworks deploys can once again bundle local NPM dependencies (#5440) - Catches error when attempting to deploy without a project (#5415) - Fixes a number of issues and outdated dependencies in templates for `init --only functions` and `ext:dev:init` +- Fix some edge cases where Emulator UI cannot reach the emulators (#912) +- Fix various accessibility and usability issues in Emulator UI. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 8be85567fbd..6be0d8d52bd 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -45,9 +45,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet ui: experiments.isEnabled("emulatoruisnapshot") ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" } : { - version: "1.11.3", - expectedSize: 3062910, - expectedChecksum: "641b375b67f0d9aee11a5b769b074118", + version: "1.11.4", + expectedSize: 3062916, + expectedChecksum: "1773926323b07fdb9602d882a7682882", }, pubsub: { version: "0.7.1", From 1880944b552796af11a83e22854f7f3f52398f10 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Wed, 8 Feb 2023 09:52:55 -0800 Subject: [PATCH 0793/1699] Release Cloud Firestore Emulator v1.16.0. (#5493) --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84d3302ef6e..5859272c508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,5 +5,6 @@ - Web frameworks deploys can once again bundle local NPM dependencies (#5440) - Catches error when attempting to deploy without a project (#5415) - Fixes a number of issues and outdated dependencies in templates for `init --only functions` and `ext:dev:init` +- Support private network access (CORS-RFC1918) in Firestore Emulator (#4227) - Fix some edge cases where Emulator UI cannot reach the emulators (#912) - Fix various accessibility and usability issues in Emulator UI. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 6be0d8d52bd..db217e292ee 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -33,9 +33,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet expectedChecksum: "311609538bd65666eb724ef47c2e6466", }, firestore: { - version: "1.15.1", - expectedSize: 61475851, - expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee", + version: "1.16.0", + expectedSize: 63422812, + expectedChecksum: "6c1a43c1b327d534f83f7386c595d7ff", }, storage: { version: "1.1.3", From bc87d152b78b3f5e381b1d2e23fa27a039d921e7 Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Wed, 8 Feb 2023 13:34:50 -0800 Subject: [PATCH 0794/1699] audit, couple dependency updates (#5495) * fix audit issue * update and reformat using linters * upgrade typescript and node types * add conditions for merge_group --- .github/workflows/node-test.yml | 7 +- npm-shrinkwrap.json | 1065 ++++++++++---------- src/deploy/functions/release/fabricator.ts | 8 +- src/deploy/functions/runtimes/index.ts | 4 +- src/emulator/functionsEmulatorRuntime.ts | 26 +- src/functional.ts | 5 - src/functions/constants.ts | 4 +- src/functions/events/v1.ts | 2 +- src/functions/events/v2.ts | 4 +- src/gcp/cloudfunctions.ts | 2 +- src/gcp/cloudfunctionsv2.ts | 2 +- src/prompt.ts | 2 +- 12 files changed, 589 insertions(+), 542 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 9783bd01ec9..d953555bcc3 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -3,6 +3,7 @@ name: CI Tests on: - pull_request - push + - merge_group env: CI: true @@ -19,7 +20,7 @@ concurrency: jobs: lint: runs-on: ubuntu-latest - if: github.event_name == 'pull_request' + if: contains(fromJSON('["pull_request", "merge_group"]'), github.event_name) strategy: matrix: node-version: @@ -63,7 +64,7 @@ jobs: integration: needs: unit - if: github.event_name == 'push' + if: contains(fromJSON('["push", "merge_group"]'), github.event_name) runs-on: ubuntu-latest env: @@ -119,7 +120,7 @@ jobs: integration-windows: needs: unit - if: github.event_name == 'push' + if: contains(fromJSON('["push", "merge_group"]'), github.event_name) runs-on: windows-latest env: diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d2b169e4240..243498922f5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -648,39 +648,40 @@ "node": ">=0.1.90" } }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { "node": ">=12" } }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.29.0.tgz", - "integrity": "sha512-4yKy5t+/joLihG+ei6CCU6sc08sjUdEdXCQ2U+9h9VP13EiqHQ4YMgDC18ys/AsLdJDBX3KRx/AWY6PR7hn52Q==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", + "integrity": "sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==", "dev": true, "dependencies": { "comment-parser": "1.3.1", "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~3.0.1" + "jsdoc-type-pratt-parser": "~3.1.0" }, "engines": { - "node": "^14 || ^16 || ^17 || ^18" + "node": "^14 || ^16 || ^17 || ^18 || ^19" } }, "node_modules/@esbuild/android-arm": { @@ -716,23 +717,26 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", - "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.2.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/argparse": { @@ -742,9 +746,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -759,9 +763,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -773,15 +777,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2012,23 +2007,23 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", - "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2048,6 +2043,19 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -2887,13 +2895,12 @@ } }, "node_modules/@types/inquirer": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.1.3.tgz", - "integrity": "sha512-AayK4ZL5ssPzR1OtnOLGAwpT0Dda3Xi/h1G0l1oJDNrowp7T1423q4Zb8/emr7tzRlCy4ssEri0LWVexAqHyKQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QXlzybid60YtAwfgG3cpykptRYUx2KomzNutMlWsQC64J/WG/gQSl+P4w7A21sGN0VIxRVava4rgnT7FQmFCdg==", "dev": true, "dependencies": { - "@types/through": "*", - "rxjs": "^7.2.0" + "@types/through": "*" } }, "node_modules/@types/js-yaml": { @@ -3018,9 +3025,9 @@ } }, "node_modules/@types/node": { - "version": "14.18.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.18.tgz", - "integrity": "sha512-B9EoJFjhqcQ9OmQrNorItO+OwEOORNn3S31WuiHvZY/dm9ajkB7AKD/8toessEtHHNL+58jofbq7hMMY9v4yig==" + "version": "14.18.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz", + "integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==" }, "node_modules/@types/node-fetch": { "version": "2.5.12", @@ -3351,19 +3358,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.0.tgz", - "integrity": "sha512-qT4lr2jysDQBQOPsCCvpPUZHjbABoTJW8V9ZzIYKHMfppJtpdtzszDYsldwhFxlhvrp7aCHeXD1Lb9M1zhwWwQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", + "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "5.9.0", - "@typescript-eslint/scope-manager": "5.9.0", - "@typescript-eslint/type-utils": "5.9.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/type-utils": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -3384,9 +3392,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -3407,9 +3415,9 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3421,40 +3429,16 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.0.tgz", - "integrity": "sha512-ZnLVjBrf26dn7ElyaSKa6uDhqwvAi4jBBmHK1VxuFGPRAxhdi18ubQYSGA7SRiFiES3q9JiBOBHEBStOFkwD2g==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.9.0", - "@typescript-eslint/types": "5.9.0", - "@typescript-eslint/typescript-estree": "5.9.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, "node_modules/@typescript-eslint/parser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.9.0.tgz", - "integrity": "sha512-/6pOPz8yAxEt4PLzgbFRDpZmHnXCeZgPDrh/1DaVKOjvn/UPMlWhbx/gA96xRi2JxY1kBl2AmwVbyROUqys5xQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", + "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.9.0", - "@typescript-eslint/types": "5.9.0", - "@typescript-eslint/typescript-estree": "5.9.0", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "debug": "^4.3.4" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3473,9 +3457,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -3496,13 +3480,13 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz", - "integrity": "sha512-DKtdIL49Qxk2a8icF6whRk7uThuVz4A6TCXfjdJSwOsf+9ree7vgQWcx0KOyCdk0i9ETX666p4aMhrRhxhUkyg==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", + "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.9.0", - "@typescript-eslint/visitor-keys": "5.9.0" + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3513,13 +3497,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.0.tgz", - "integrity": "sha512-uVCb9dJXpBrK1071ri5aEW7ZHdDHAiqEjYznF3HSSvAJXyrkxGOw2Ejibz/q6BXdT8lea8CMI0CzKNFTNI6TEQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", + "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "5.9.0", - "debug": "^4.3.2", + "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", "tsutils": "^3.21.0" }, "engines": { @@ -3539,9 +3524,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -3562,9 +3547,9 @@ "dev": true }, "node_modules/@typescript-eslint/types": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz", - "integrity": "sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", + "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3575,17 +3560,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz", - "integrity": "sha512-kxo3xL2mB7XmiVZcECbaDwYCt3qFXz99tBSuVJR4L/sR7CJ+UNAPrYILILktGj1ppfZ/jNt/cWYbziJUlHl1Pw==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", + "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.9.0", - "@typescript-eslint/visitor-keys": "5.9.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -3602,9 +3587,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -3625,9 +3610,56 @@ "dev": true }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", + "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3640,13 +3672,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz", - "integrity": "sha512-6zq0mb7LV0ThExKlecvpfepiB+XEtFv/bzx7/jKSgyXTFD7qjmSu1FoiS0x3OZaiS+UIXpH2vd9O89f02RCtgw==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", + "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.9.0", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "5.51.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -5787,18 +5819,6 @@ "once": "^1.4.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", @@ -6263,49 +6283,50 @@ } }, "node_modules/eslint": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.6.0.tgz", - "integrity": "sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", + "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.0.5", - "@humanwhocodes/config-array": "^0.9.2", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", + "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.3.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", "regexpp": "^3.2.0", - "semver": "^7.2.1", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -6330,9 +6351,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -6342,21 +6363,21 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "39.2.9", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.2.9.tgz", - "integrity": "sha512-gaPYJT94rWlWyQcisQyyEJHtLaaJqN4baFlLCEr/LcXVibS9wzQTL2dskqk327ggwqQopR+Xecu2Lng1IJ9Ypw==", + "version": "39.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.8.0.tgz", + "integrity": "sha512-ZwGmk0jJoJD/NILeDRBKrpq/PCgddUdATjeU5JGTqTzKsOWfeaHOnaAwZjuOh7T8EB4hSoZ/9pR4+Qns2ldQVg==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.29.0", + "@es-joy/jsdoccomment": "~0.36.1", "comment-parser": "1.3.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.4.0", - "semver": "^7.3.7", + "semver": "^7.3.8", "spdx-expression-parse": "^3.0.1" }, "engines": { - "node": "^14 || ^16 || ^17 || ^18" + "node": "^14 || ^16 || ^17 || ^18 || ^19" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" @@ -6398,9 +6419,9 @@ "dev": true }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6423,15 +6444,15 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", - "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=12.0.0" }, "peerDependencies": { "eslint": ">=7.28.0", @@ -6484,9 +6505,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -6576,9 +6597,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -6610,9 +6631,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -6633,15 +6654,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/eslint/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -6699,21 +6711,6 @@ "node": ">= 0.8.0" } }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6763,16 +6760,19 @@ } }, "node_modules/espree": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", - "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", "dependencies": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -7200,9 +7200,9 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -7682,7 +7682,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true + "dev": true, + "optional": true }, "node_modules/gauge": { "version": "4.0.4", @@ -7950,16 +7951,16 @@ "dev": true }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -8638,6 +8639,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -8807,9 +8814,9 @@ "dev": true }, "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "optional": true }, "node_modules/http-errors": { @@ -9050,9 +9057,9 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inquirer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz", - "integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -9064,13 +9071,14 @@ "mute-stream": "0.0.8", "ora": "^5.4.1", "run-async": "^2.4.0", - "rxjs": "^7.2.0", + "rxjs": "^7.5.5", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", - "through": "^2.3.6" + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" } }, "node_modules/inquirer/node_modules/ansi-escapes": { @@ -9317,9 +9325,9 @@ } }, "node_modules/is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "engines": { "node": ">=8" } @@ -9605,6 +9613,16 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9665,9 +9683,9 @@ } }, "node_modules/jsdoc-type-pratt-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.0.1.tgz", - "integrity": "sha512-vqMCdAFVIiFhVgBYE/X8naf3L/7qiJsaYWTfUJZZZ124dR3OUz9HrmaMUGpYIYAN4VSuodf6gIZY0e8ktPw9cg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", + "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", "dev": true, "engines": { "node": ">=12.0.0" @@ -10586,13 +10604,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -11026,6 +11044,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -12427,15 +12451,18 @@ } }, "node_modules/prettier": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", "dev": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/prettier-linter-helpers": { @@ -14694,12 +14721,12 @@ "dev": true }, "node_modules/ts-node": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", - "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, "dependencies": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -14710,11 +14737,13 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { "ts-node": "dist/bin.js", "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" @@ -14830,9 +14859,9 @@ } }, "node_modules/typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -15282,10 +15311,10 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", - "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, "node_modules/valid-url": { @@ -16175,30 +16204,36 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "optional": true }, - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true - }, "@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "requires": { - "@cspotcode/source-map-consumer": "0.8.0" + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } } }, "@es-joy/jsdoccomment": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.29.0.tgz", - "integrity": "sha512-4yKy5t+/joLihG+ei6CCU6sc08sjUdEdXCQ2U+9h9VP13EiqHQ4YMgDC18ys/AsLdJDBX3KRx/AWY6PR7hn52Q==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", + "integrity": "sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==", "dev": true, "requires": { "comment-parser": "1.3.1", "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~3.0.1" + "jsdoc-type-pratt-parser": "~3.1.0" } }, "@esbuild/android-arm": { @@ -16216,19 +16251,19 @@ "optional": true }, "@eslint/eslintrc": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", - "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.2.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "dependencies": { @@ -16239,29 +16274,23 @@ "dev": true }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" } }, "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -17359,20 +17388,20 @@ } }, "@humanwhocodes/config-array": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", - "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -17386,6 +17415,12 @@ } } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -18024,13 +18059,12 @@ } }, "@types/inquirer": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.1.3.tgz", - "integrity": "sha512-AayK4ZL5ssPzR1OtnOLGAwpT0Dda3Xi/h1G0l1oJDNrowp7T1423q4Zb8/emr7tzRlCy4ssEri0LWVexAqHyKQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QXlzybid60YtAwfgG3cpykptRYUx2KomzNutMlWsQC64J/WG/gQSl+P4w7A21sGN0VIxRVava4rgnT7FQmFCdg==", "dev": true, "requires": { - "@types/through": "*", - "rxjs": "^7.2.0" + "@types/through": "*" } }, "@types/js-yaml": { @@ -18157,9 +18191,9 @@ } }, "@types/node": { - "version": "14.18.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.18.tgz", - "integrity": "sha512-B9EoJFjhqcQ9OmQrNorItO+OwEOORNn3S31WuiHvZY/dm9ajkB7AKD/8toessEtHHNL+58jofbq7hMMY9v4yig==" + "version": "14.18.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz", + "integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==" }, "@types/node-fetch": { "version": "2.5.12", @@ -18488,26 +18522,27 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.0.tgz", - "integrity": "sha512-qT4lr2jysDQBQOPsCCvpPUZHjbABoTJW8V9ZzIYKHMfppJtpdtzszDYsldwhFxlhvrp7aCHeXD1Lb9M1zhwWwQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", + "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "5.9.0", - "@typescript-eslint/scope-manager": "5.9.0", - "@typescript-eslint/type-utils": "5.9.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/type-utils": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -18520,9 +18555,9 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -18530,36 +18565,22 @@ } } }, - "@typescript-eslint/experimental-utils": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.0.tgz", - "integrity": "sha512-ZnLVjBrf26dn7ElyaSKa6uDhqwvAi4jBBmHK1VxuFGPRAxhdi18ubQYSGA7SRiFiES3q9JiBOBHEBStOFkwD2g==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.9.0", - "@typescript-eslint/types": "5.9.0", - "@typescript-eslint/typescript-estree": "5.9.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, "@typescript-eslint/parser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.9.0.tgz", - "integrity": "sha512-/6pOPz8yAxEt4PLzgbFRDpZmHnXCeZgPDrh/1DaVKOjvn/UPMlWhbx/gA96xRi2JxY1kBl2AmwVbyROUqys5xQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", + "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.9.0", - "@typescript-eslint/types": "5.9.0", - "@typescript-eslint/typescript-estree": "5.9.0", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "debug": "^4.3.4" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -18574,30 +18595,31 @@ } }, "@typescript-eslint/scope-manager": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz", - "integrity": "sha512-DKtdIL49Qxk2a8icF6whRk7uThuVz4A6TCXfjdJSwOsf+9ree7vgQWcx0KOyCdk0i9ETX666p4aMhrRhxhUkyg==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", + "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.9.0", - "@typescript-eslint/visitor-keys": "5.9.0" + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0" } }, "@typescript-eslint/type-utils": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.0.tgz", - "integrity": "sha512-uVCb9dJXpBrK1071ri5aEW7ZHdDHAiqEjYznF3HSSvAJXyrkxGOw2Ejibz/q6BXdT8lea8CMI0CzKNFTNI6TEQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", + "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "5.9.0", - "debug": "^4.3.2", + "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", "tsutils": "^3.21.0" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -18612,30 +18634,30 @@ } }, "@typescript-eslint/types": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz", - "integrity": "sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", + "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz", - "integrity": "sha512-kxo3xL2mB7XmiVZcECbaDwYCt3qFXz99tBSuVJR4L/sR7CJ+UNAPrYILILktGj1ppfZ/jNt/cWYbziJUlHl1Pw==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", + "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.9.0", - "@typescript-eslint/visitor-keys": "5.9.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "dependencies": { "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -18648,9 +18670,42 @@ "dev": true }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", + "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "dependencies": { + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -18659,13 +18714,13 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz", - "integrity": "sha512-6zq0mb7LV0ThExKlecvpfepiB+XEtFv/bzx7/jKSgyXTFD7qjmSu1FoiS0x3OZaiS+UIXpH2vd9O89f02RCtgw==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", + "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.9.0", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "5.51.0", + "eslint-visitor-keys": "^3.3.0" } }, "@ungap/promise-all-settled": { @@ -20325,15 +20380,6 @@ "once": "^1.4.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", @@ -20587,49 +20633,50 @@ } }, "eslint": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.6.0.tgz", - "integrity": "sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", + "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.0.5", - "@humanwhocodes/config-array": "^0.9.2", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", + "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.3.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", "regexpp": "^3.2.0", - "semver": "^7.2.1", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "dependencies": { "ansi-styles": { @@ -20688,9 +20735,9 @@ "dev": true }, "eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -20713,9 +20760,9 @@ } }, "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -20727,12 +20774,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -20778,15 +20819,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -20827,24 +20859,24 @@ "requires": {} }, "eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", "dev": true, "requires": {} }, "eslint-plugin-jsdoc": { - "version": "39.2.9", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.2.9.tgz", - "integrity": "sha512-gaPYJT94rWlWyQcisQyyEJHtLaaJqN4baFlLCEr/LcXVibS9wzQTL2dskqk327ggwqQopR+Xecu2Lng1IJ9Ypw==", + "version": "39.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.8.0.tgz", + "integrity": "sha512-ZwGmk0jJoJD/NILeDRBKrpq/PCgddUdATjeU5JGTqTzKsOWfeaHOnaAwZjuOh7T8EB4hSoZ/9pR4+Qns2ldQVg==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "~0.29.0", + "@es-joy/jsdoccomment": "~0.36.1", "comment-parser": "1.3.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.4.0", - "semver": "^7.3.7", + "semver": "^7.3.8", "spdx-expression-parse": "^3.0.1" }, "dependencies": { @@ -20870,9 +20902,9 @@ "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -20891,9 +20923,9 @@ } }, "eslint-plugin-prettier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", - "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" @@ -20927,18 +20959,18 @@ } }, "eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" }, "espree": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", - "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", "requires": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" } }, "esprima": { @@ -21268,9 +21300,9 @@ } }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -21642,7 +21674,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true + "dev": true, + "optional": true }, "gauge": { "version": "4.0.4", @@ -21850,16 +21883,16 @@ "dev": true }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, @@ -22388,6 +22421,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -22518,9 +22557,9 @@ "dev": true }, "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "optional": true }, "http-errors": { @@ -22704,9 +22743,9 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz", - "integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", "requires": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -22718,10 +22757,11 @@ "mute-stream": "0.0.8", "ora": "^5.4.1", "run-async": "^2.4.0", - "rxjs": "^7.2.0", + "rxjs": "^7.5.5", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", - "through": "^2.3.6" + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" }, "dependencies": { "ansi-escapes": { @@ -22888,9 +22928,9 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" }, "is-plain-obj": { "version": "1.1.0", @@ -23124,6 +23164,12 @@ "integrity": "sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A==", "dev": true }, + "js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -23197,9 +23243,9 @@ } }, "jsdoc-type-pratt-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.0.1.tgz", - "integrity": "sha512-vqMCdAFVIiFhVgBYE/X8naf3L/7qiJsaYWTfUJZZZ124dR3OUz9HrmaMUGpYIYAN4VSuodf6gIZY0e8ktPw9cg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", + "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", "dev": true }, "jsesc": { @@ -23928,13 +23974,13 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime": { @@ -24253,6 +24299,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -25303,9 +25355,9 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prettier": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", "dev": true }, "prettier-linter-helpers": { @@ -27072,12 +27124,12 @@ "dev": true }, "ts-node": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", - "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, "requires": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -27088,6 +27140,7 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "dependencies": { @@ -27165,9 +27218,9 @@ } }, "typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "typescript-json-schema": { @@ -27489,10 +27542,10 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, - "v8-compile-cache": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", - "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, "valid-url": { diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 68f0cf47ec5..f3346d2b3a5 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -230,7 +230,7 @@ export class Fabricator { onPoll: scraper.poller, }); }) - .catch(rethrowAs(endpoint, "create")); + .catch(rethrowAs(endpoint, "create")); endpoint.uri = resultFunction?.httpsTrigger?.url; if (backend.isHttpsTriggered(endpoint)) { @@ -351,7 +351,7 @@ export class Fabricator { operationResourceName: op.name, }); }) - .catch(rethrowAs(endpoint, "create")); + .catch(rethrowAs(endpoint, "create")); endpoint.uri = resultFunction.serviceConfig.uri; const serviceName = resultFunction.serviceConfig.service!; @@ -433,7 +433,7 @@ export class Fabricator { onPoll: scraper.poller, }); }) - .catch(rethrowAs(endpoint, "update")); + .catch(rethrowAs(endpoint, "update")); endpoint.uri = resultFunction?.httpsTrigger?.url; let invoker: string[] | undefined; @@ -479,7 +479,7 @@ export class Fabricator { operationResourceName: op.name, }); }) - .catch(rethrowAs(endpoint, "update")); + .catch(rethrowAs(endpoint, "update")); endpoint.uri = resultFunction.serviceConfig.uri; const serviceName = resultFunction.serviceConfig.service!; diff --git a/src/deploy/functions/runtimes/index.ts b/src/deploy/functions/runtimes/index.ts index 8864b127f44..90cb9c51db8 100644 --- a/src/deploy/functions/runtimes/index.ts +++ b/src/deploy/functions/runtimes/index.ts @@ -11,11 +11,11 @@ const RUNTIMES: string[] = ["nodejs10", "nodejs12", "nodejs14", "nodejs16", "nod // different list to help guard against some day accidentally iterating over // and printing a hidden runtime to the user. const EXPERIMENTAL_RUNTIMES: string[] = ["python310", "python311"]; -export type Runtime = typeof RUNTIMES[number] | typeof EXPERIMENTAL_RUNTIMES[number]; +export type Runtime = (typeof RUNTIMES)[number] | (typeof EXPERIMENTAL_RUNTIMES)[number]; /** Runtimes that can be found in existing backends but not used for new functions. */ const DEPRECATED_RUNTIMES = ["nodejs6", "nodejs8"]; -export type DeprecatedRuntime = typeof DEPRECATED_RUNTIMES[number]; +export type DeprecatedRuntime = (typeof DEPRECATED_RUNTIMES)[number]; /** Type deduction helper for a runtime string */ export function isDeprecatedRuntime(runtime: string): runtime is DeprecatedRuntime { diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 5e3b20686a3..7484bd0861e 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -628,13 +628,12 @@ async function initializeFirebaseAdminStubs(): Promise { .finalize(); // Stub the admin module in the require cache - require.cache[adminResolution.resolution] = Object.assign( - require.cache[adminResolution.resolution], - { - exports: proxiedAdminModule, - path: path.dirname(adminResolution.resolution), - } - ); + const v = require.cache[adminResolution.resolution]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- this is not precedent. + require.cache[adminResolution.resolution] = Object.assign(v!, { + exports: proxiedAdminModule, + path: path.dirname(adminResolution.resolution), + }); logDebug("firebase-admin has been stubbed.", { adminResolution, @@ -741,13 +740,12 @@ async function initializeFunctionsConfigHelper(): Promise { .finalize(); // Stub the functions module in the require cache - require.cache[functionsResolution.resolution] = Object.assign( - require.cache[functionsResolution.resolution], - { - exports: proxiedFunctionsModule, - path: path.dirname(functionsResolution.resolution), - } - ); + const v = require.cache[functionsResolution.resolution]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- this is not precedent. + require.cache[functionsResolution.resolution] = Object.assign(v!, { + exports: proxiedFunctionsModule, + path: path.dirname(functionsResolution.resolution), + }); logDebug("firebase-functions has been stubbed.", { functionsResolution, diff --git a/src/functional.ts b/src/functional.ts index b6c638816cd..bafeb5d5b2d 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -52,11 +52,6 @@ export function flatten(objOrArr: T): unknown { } } -type RecursiveElems = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - [Key in keyof T]: T[Key] extends unknown[] ? T[Key] | RecursiveElems : T[Key]; -}[number]; - /** * Used with reduce to flatten in place. * Due to the quirks of TypeScript, callers must pass [] as the diff --git a/src/functions/constants.ts b/src/functions/constants.ts index a06083f8b42..99bd2bf716b 100644 --- a/src/functions/constants.ts +++ b/src/functions/constants.ts @@ -3,11 +3,11 @@ import { AUTH_BLOCKING_EVENTS } from "./events/v1"; export const CODEBASE_LABEL = "firebase-functions-codebase"; export const HASH_LABEL = "firebase-functions-hash"; export const BLOCKING_LABEL = "deployment-blocking"; -export const BLOCKING_LABEL_KEY_TO_EVENT: Record = { +export const BLOCKING_LABEL_KEY_TO_EVENT: Record = { "before-create": "providers/cloud.auth/eventTypes/user.beforeCreate", "before-sign-in": "providers/cloud.auth/eventTypes/user.beforeSignIn", }; -export const BLOCKING_EVENT_TO_LABEL_KEY: Record = { +export const BLOCKING_EVENT_TO_LABEL_KEY: Record<(typeof AUTH_BLOCKING_EVENTS)[number], string> = { "providers/cloud.auth/eventTypes/user.beforeCreate": "before-create", "providers/cloud.auth/eventTypes/user.beforeSignIn": "before-sign-in", }; diff --git a/src/functions/events/v1.ts b/src/functions/events/v1.ts index f8936ac68e9..0be24c12487 100644 --- a/src/functions/events/v1.ts +++ b/src/functions/events/v1.ts @@ -4,4 +4,4 @@ export const BEFORE_SIGN_IN_EVENT = "providers/cloud.auth/eventTypes/user.before export const AUTH_BLOCKING_EVENTS = [BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT] as const; -export type Event = typeof AUTH_BLOCKING_EVENTS[number]; +export type Event = (typeof AUTH_BLOCKING_EVENTS)[number]; diff --git a/src/functions/events/v2.ts b/src/functions/events/v2.ts index 01b9fc491c6..0298210e261 100644 --- a/src/functions/events/v2.ts +++ b/src/functions/events/v2.ts @@ -22,8 +22,8 @@ export const TEST_LAB_EVENT = "google.firebase.testlab.testMatrix.v1.completed"; export type Event = | typeof PUBSUB_PUBLISH_EVENT - | typeof STORAGE_EVENTS[number] + | (typeof STORAGE_EVENTS)[number] | typeof FIREBASE_ALERTS_PUBLISH_EVENT - | typeof DATABASE_EVENTS[number] + | (typeof DATABASE_EVENTS)[number] | typeof REMOTE_CONFIG_EVENT | typeof TEST_LAB_EVENT; diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 78f283173c7..06b1f389696 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -624,7 +624,7 @@ export function functionFromEndpoint( ...gcfFunction.labels, [BLOCKING_LABEL]: BLOCKING_EVENT_TO_LABEL_KEY[ - endpoint.blockingTrigger.eventType as typeof AUTH_BLOCKING_EVENTS[number] + endpoint.blockingTrigger.eventType as (typeof AUTH_BLOCKING_EVENTS)[number] ], }; } else { diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index f57aad0e91f..e50aeca522c 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -547,7 +547,7 @@ export function functionFromEndpoint( ...gcfFunction.labels, [BLOCKING_LABEL]: BLOCKING_EVENT_TO_LABEL_KEY[ - endpoint.blockingTrigger.eventType as typeof AUTH_BLOCKING_EVENTS[number] + endpoint.blockingTrigger.eventType as (typeof AUTH_BLOCKING_EVENTS)[number] ], }; } diff --git a/src/prompt.ts b/src/prompt.ts index 01433a71519..5658d2a08fd 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -8,7 +8,7 @@ import { FirebaseError } from "./error"; */ export type Question = inquirer.DistinctQuestion; -type QuestionsThatReturnAString = +type QuestionsThatReturnAString = | inquirer.RawListQuestion | inquirer.ExpandQuestion | inquirer.InputQuestion From 17de2ab9efc85a9ff1fc637fa673ed58e06be874 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 9 Feb 2023 21:29:57 -0500 Subject: [PATCH 0795/1699] fix ng discover, relative init, and nextjs javascript flavor (#5500) * Fix the second discover in init using a relative dir, ng for example doesn't like "." * Allow nextjs javascript init, turns out typescript is the default now * Name a new ng app the projectId, rather the the dir (helps the "." case) * Init for all frameworks now should appropriately handle relative directories --- CHANGELOG.md | 1 + src/frameworks/angular/index.ts | 14 +++++++++----- src/frameworks/index.ts | 2 +- src/frameworks/next/index.ts | 8 ++++---- src/frameworks/vite/index.ts | 9 +++++---- src/init/features/hosting/index.ts | 5 +++-- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5859272c508..ff8899ba8f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,4 @@ - Support private network access (CORS-RFC1918) in Firestore Emulator (#4227) - Fix some edge cases where Emulator UI cannot reach the emulators (#912) - Fix various accessibility and usability issues in Emulator UI. +- Fix various issues with "init hosting" and web frameworks (#5500) diff --git a/src/frameworks/angular/index.ts b/src/frameworks/angular/index.ts index 3d24a8f6023..13f52e1086b 100644 --- a/src/frameworks/angular/index.ts +++ b/src/frameworks/angular/index.ts @@ -31,10 +31,14 @@ export async function discover(dir: string): Promise { return { mayWantBackend: !!serverTarget, publicDirectory: join(dir, "src", "assets") }; } -export async function init(setup: any) { - execSync(`npx --yes -p @angular/cli@latest ng new ${setup.hosting.source} --skip-git`, { - stdio: "inherit", - }); +export async function init(setup: any, config: any) { + execSync( + `npx --yes -p @angular/cli@latest ng new ${setup.projectId} --directory ${setup.hosting.source} --skip-git`, + { + stdio: "inherit", + cwd: config.projectDir, + } + ); const useAngularUniversal = await promptOnce({ name: "useAngularUniversal", type: "confirm", @@ -44,7 +48,7 @@ export async function init(setup: any) { if (useAngularUniversal) { execSync("ng add @nguniversal/express-engine --skip-confirmation", { stdio: "inherit", - cwd: setup.hosting.source, + cwd: join(config.projectDir, setup.hosting.source), }); } } diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 645ba9ed239..1079ddad6e6 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -50,7 +50,7 @@ export interface Framework { name: string; build: (dir: string) => Promise; support: SupportLevel; - init?: (setup: any) => Promise; + init?: (setup: any, config: any) => Promise; getDevModeHandle?: ( dir: string, hostingEmulatorInfo?: EmulatorInfo diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index 0a4db4994ff..8a5e126b4d1 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -235,18 +235,18 @@ export async function build(dir: string): Promise { /** * Utility method used during project initialization. */ -export async function init(setup: any) { +export async function init(setup: any, config: any) { const language = await promptOnce({ type: "list", - default: "JavaScript", + default: "TypeScript", message: "What language would you like to use?", choices: ["JavaScript", "TypeScript"], }); execSync( `npx --yes create-next-app@latest -e hello-world ${setup.hosting.source} --use-npm ${ - language === "TypeScript" ? "--ts" : "" + language === "TypeScript" ? "--ts" : "--js" }`, - { stdio: "inherit" } + { stdio: "inherit", cwd: config.projectDir } ); } diff --git a/src/frameworks/vite/index.ts b/src/frameworks/vite/index.ts index 6586c1ff588..21f0f6f5ee7 100644 --- a/src/frameworks/vite/index.ts +++ b/src/frameworks/vite/index.ts @@ -19,10 +19,10 @@ const CLI_COMMAND = join( export const DEFAULT_BUILD_SCRIPT = ["vite build", "tsc && vite build"]; -export const initViteTemplate = (template: string) => async (setup: any) => - await init(setup, template); +export const initViteTemplate = (template: string) => async (setup: any, config: any) => + await init(setup, config, template); -export async function init(setup: any, baseTemplate: string = "vanilla") { +export async function init(setup: any, config: any, baseTemplate: string = "vanilla") { const template = await promptOnce({ type: "list", default: "JavaScript", @@ -34,8 +34,9 @@ export async function init(setup: any, baseTemplate: string = "vanilla") { }); execSync(`npm create vite@latest ${setup.hosting.source} --yes -- --template ${template}`, { stdio: "inherit", + cwd: config.projectDir, }); - execSync(`npm install`, { stdio: "inherit", cwd: setup.hosting.source }); + execSync(`npm install`, { stdio: "inherit", cwd: join(config.projectDir, setup.hosting.source) }); } export const viteDiscoverWithNpmDependency = (dep: string) => async (dir: string) => diff --git a/src/init/features/hosting/index.ts b/src/init/features/hosting/index.ts index 341e6cc10ee..02641a47f8f 100644 --- a/src/init/features/hosting/index.ts +++ b/src/init/features/hosting/index.ts @@ -8,6 +8,7 @@ import { prompt, promptOnce } from "../../../prompt"; import { logger } from "../../../logger"; import { discover, WebFrameworks } from "../../../frameworks"; import * as experiments from "../../../experiments"; +import { join } from "path"; const INDEX_TEMPLATE = fs.readFileSync( __dirname + "/../../../../templates/init/hosting/index.html", @@ -70,7 +71,7 @@ export async function doSetup(setup: any, config: any): Promise { ); if (setup.hosting.source !== ".") delete setup.hosting.useDiscoveredFramework; - discoveredFramework = await discover(setup.hosting.source); + discoveredFramework = await discover(join(config.projectDir, setup.hosting.source)); if (discoveredFramework) { const name = WebFrameworks[discoveredFramework.framework].name; @@ -112,7 +113,7 @@ export async function doSetup(setup: any, config: any): Promise { ); if (discoveredFramework) rimraf(setup.hosting.source); - await WebFrameworks[setup.hosting.whichFramework].init!(setup); + await WebFrameworks[setup.hosting.whichFramework].init!(setup, config); } setup.config.hosting = { From 16e5e431ac8811b3acb5796b6c9caec50e255fe6 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Fri, 10 Feb 2023 12:04:09 -0500 Subject: [PATCH 0796/1699] Support .env files in web frameworks (#5501) --- CHANGELOG.md | 1 + src/frameworks/index.ts | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff8899ba8f8..d8a1390e7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,4 +8,5 @@ - Support private network access (CORS-RFC1918) in Firestore Emulator (#4227) - Fix some edge cases where Emulator UI cannot reach the emulators (#912) - Fix various accessibility and usability issues in Emulator UI. +- Support .env when deploying a web framework (#5501) - Fix various issues with "init hosting" and web frameworks (#5500) diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 1079ddad6e6..e575ab65ebc 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -4,11 +4,12 @@ import { execSync, spawnSync } from "child_process"; import { readdirSync, statSync } from "fs"; import { pathToFileURL } from "url"; import { IncomingMessage, ServerResponse } from "http"; -import { copyFile, readdir, rm, writeFile } from "fs/promises"; +import { copyFile, readdir, readFile, rm, writeFile } from "fs/promises"; import { mkdirp, pathExists, stat } from "fs-extra"; import * as clc from "colorette"; import * as process from "node:process"; import * as semver from "semver"; +import * as glob from "glob"; import { needProjectId } from "../projectUtils"; import { hostingConfig } from "../hosting/config"; @@ -534,13 +535,6 @@ export async function prepareFrameworks( } await writeFile(join(functionsDist, "package.json"), JSON.stringify(packageJson, null, 2)); - // TODO do we add the append the local .env? - await writeFile( - join(functionsDist, ".env"), - `__FIREBASE_FRAMEWORKS_ENTRY__=${frameworksEntry} -${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\n` : ""}` - ); - await copyFile( getProjectPath("package-lock.json"), join(functionsDist, "package-lock.json") @@ -552,6 +546,27 @@ ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\ await copyFile(getProjectPath(".npmrc"), join(functionsDist, ".npmrc")); } + let existingDotEnvContents = ""; + if (await pathExists(getProjectPath(".env"))) { + existingDotEnvContents = (await readFile(getProjectPath(".env"))).toString(); + } + + await writeFile( + join(functionsDist, ".env"), + `${existingDotEnvContents} +__FIREBASE_FRAMEWORKS_ENTRY__=${frameworksEntry} +${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\n` : ""}` + ); + + const envs = await new Promise((resolve, reject) => + glob(getProjectPath(".env.*"), (err, matches) => { + if (err) reject(err); + resolve(matches); + }) + ); + + await Promise.all(envs.map((path) => copyFile(path, join(functionsDist, basename(path))))); + execSync(`${NPM_COMMAND} i --omit dev --no-audit`, { cwd: functionsDist, stdio: "inherit", From 7258fca2687e17d19cf8f7104bae2dbce30669d8 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 10 Feb 2023 10:20:25 -0800 Subject: [PATCH 0797/1699] Overhaul `ext:dev:init` (#5498) * Overhaul ext:dev:init * Lint and changelog * Typo fix --- CHANGELOG.md | 1 + src/commands/ext-dev-init.ts | 45 +++++++++++++++++++ templates/extensions/integration-test.env | 2 + templates/extensions/integration-test.json | 14 ++++++ templates/extensions/javascript/WELCOME.md | 19 +++++--- .../extensions/javascript/integration-test.js | 13 ++++++ .../extensions/javascript/package.lint.json | 10 ++++- .../extensions/javascript/package.nolint.json | 9 ++++ templates/extensions/typescript/WELCOME.md | 23 +++++++--- templates/extensions/typescript/_mocharc | 10 +++++ .../extensions/typescript/integration-test.ts | 13 ++++++ .../extensions/typescript/package.lint.json | 13 +++++- .../extensions/typescript/package.nolint.json | 12 ++++- templates/init/functions/javascript/_eslintrc | 5 ++- 14 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 templates/extensions/integration-test.env create mode 100644 templates/extensions/integration-test.json create mode 100644 templates/extensions/javascript/integration-test.js create mode 100644 templates/extensions/typescript/_mocharc create mode 100644 templates/extensions/typescript/integration-test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d8a1390e7d3..316c2d80cbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Web frameworks deploys can once again bundle local NPM dependencies (#5440) - Catches error when attempting to deploy without a project (#5415) - Fixes a number of issues and outdated dependencies in templates for `init --only functions` and `ext:dev:init` +- Adds integration tests and useful scripts to the extension directory created by `ext:dev:init`. - Support private network access (CORS-RFC1918) in Firestore Emulator (#4227) - Fix some edge cases where Emulator UI cannot reach the emulators (#912) - Fix various accessibility and usability issues in Emulator UI. diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index c5e3ac62142..00a650c3415 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -19,6 +19,14 @@ const FUNCTIONS_ROOT = path.resolve(__dirname, "../../templates/init/functions/" function readCommonTemplates() { return { + integrationTestFirebaseJsonTemplate: fs.readFileSync( + path.join(TEMPLATE_ROOT, "integration-test.json"), + "utf8" + ), + integrationTestEnvTemplate: fs.readFileSync( + path.join(TEMPLATE_ROOT, "integration-test.env"), + "utf8" + ), extSpecTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "extension.yaml"), "utf8"), preinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "PREINSTALL.md"), "utf8"), postinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "POSTINSTALL.md"), "utf8"), @@ -106,10 +114,18 @@ async function typescriptSelected(config: Config): Promise { "utf8" ); const indexTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "index.ts"), "utf8"); + const integrationTestTemplate = fs.readFileSync( + path.join(TEMPLATE_ROOT, "typescript", "integration-test.ts"), + "utf8" + ); const gitignoreTemplate = fs.readFileSync( path.join(TEMPLATE_ROOT, "typescript", "_gitignore"), "utf8" ); + const mocharcTemplate = fs.readFileSync( + path.join(TEMPLATE_ROOT, "typescript", "_mocharc"), + "utf8" + ); const eslintTemplate = fs.readFileSync( path.join(FUNCTIONS_ROOT, "typescript", "_eslintrc"), "utf8" @@ -126,7 +142,20 @@ async function typescriptSelected(config: Config): Promise { await config.askWriteProjectFile("PREINSTALL.md", templates.preinstallTemplate); await config.askWriteProjectFile("POSTINSTALL.md", templates.postinstallTemplate); await config.askWriteProjectFile("CHANGELOG.md", templates.changelogTemplate); + await config.askWriteProjectFile("functions/.mocharc.json", mocharcTemplate); await config.askWriteProjectFile("functions/src/index.ts", indexTemplate); + await config.askWriteProjectFile( + "functions/integration-tests/integration-test.spec.ts", + integrationTestTemplate + ); + await config.askWriteProjectFile( + "functions/integration-tests/firebase.json", + templates.integrationTestFirebaseJsonTemplate + ); + await config.askWriteProjectFile( + "functions/integration-tests/extensions/greet-the-world.env", + templates.integrationTestEnvTemplate + ); if (lint) { await config.askWriteProjectFile("functions/package.json", packageLintingTemplate); await config.askWriteProjectFile("functions/.eslintrc.js", eslintTemplate); @@ -146,6 +175,10 @@ async function typescriptSelected(config: Config): Promise { */ async function javascriptSelected(config: Config): Promise { const indexTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "javascript", "index.js"), "utf8"); + const integrationTestTemplate = fs.readFileSync( + path.join(TEMPLATE_ROOT, "javascript", "integration-test.js"), + "utf8" + ); const packageLintingTemplate = fs.readFileSync( path.join(TEMPLATE_ROOT, "javascript", "package.lint.json"), "utf8" @@ -176,6 +209,18 @@ async function javascriptSelected(config: Config): Promise { await config.askWriteProjectFile("POSTINSTALL.md", templates.postinstallTemplate); await config.askWriteProjectFile("CHANGELOG.md", templates.changelogTemplate); await config.askWriteProjectFile("functions/index.js", indexTemplate); + await config.askWriteProjectFile( + "functions/integration-tests/integration-test.spec.js", + integrationTestTemplate + ); + await config.askWriteProjectFile( + "functions/integration-tests/firebase.json", + templates.integrationTestFirebaseJsonTemplate + ); + await config.askWriteProjectFile( + "functions/integration-tests/extensions/greet-the-world.env", + templates.integrationTestEnvTemplate + ); if (lint) { await config.askWriteProjectFile("functions/package.json", packageLintingTemplate); await config.askWriteProjectFile("functions/.eslintrc.js", eslintTemplate); diff --git a/templates/extensions/integration-test.env b/templates/extensions/integration-test.env new file mode 100644 index 00000000000..a90ed76eb87 --- /dev/null +++ b/templates/extensions/integration-test.env @@ -0,0 +1,2 @@ +GREETING=Hello +LOCATION=us-central1 \ No newline at end of file diff --git a/templates/extensions/integration-test.json b/templates/extensions/integration-test.json new file mode 100644 index 00000000000..81dfc0b04c3 --- /dev/null +++ b/templates/extensions/integration-test.json @@ -0,0 +1,14 @@ +{ + "emulators": { + "functions": { + "port": 5001 + }, + "ui": { + "enabled": true + }, + "singleProjectMode": true + }, + "extensions": { + "greet-the-world": "../.." + } +} diff --git a/templates/extensions/javascript/WELCOME.md b/templates/extensions/javascript/WELCOME.md index c4ec21c9f08..04423133bb0 100644 --- a/templates/extensions/javascript/WELCOME.md +++ b/templates/extensions/javascript/WELCOME.md @@ -1,9 +1,18 @@ -This directory now contains the source files for a simple extension called **greet-the-world**. To try out this extension right away, install it in an existing Firebase project by running: +This directory now contains the source files for a simple extension called **greet-the-world**. You can try it out right away in the Firebase Emulator suite - just naviagte to the integration-test directory and run: -`firebase ext:install . --project=` +`firebase emulators:start --project=` -If you want to jump into the code to customize your extension, then modify **index.js** and **extension.yaml** in your favorite editor. When you're ready to try out your fancy new extension, run: +If you don't have a project to use, you can instead use '--project=demo-test' to run against a fake project. -`firebase ext:install . --project=` +The `integration-test` directory also includes an end to end test (in the file integration-test.spec.js) that verifies that the extension responds back with the expected greeting. You can see it in action by running: -As always, in the docs, you can find detailed instructions for creating and testing your extension (including using the emulator!). +`npm run test` + +If you want to jump into the code to customize your extension, then modify **index.js** and **extension.yaml** in your favorite editor. + +If you want to deploy your extension to test on a real project, go to a Firebase project directory (or create a new one with `firebase init`) and run: + +`firebase ext:install ./path/to/extension/directory --project=` +`firebase deploy --only extensions` + +You can find more information about building extensions in the publisher docs: https://firebase.google.com/docs/extensions/alpha/overview-build-extensions diff --git a/templates/extensions/javascript/integration-test.js b/templates/extensions/javascript/integration-test.js new file mode 100644 index 00000000000..959bc3a82cb --- /dev/null +++ b/templates/extensions/javascript/integration-test.js @@ -0,0 +1,13 @@ +const axios = require("axios"); +const chai = require("chai"); + +describe("greet-the-world", () => { + it("should respond with the configured greeting", async () => { + const expected = "Hello World from greet-the-world"; + + const httpFunctionUri = "http://localhost:5001/demo-test/us-central1/ext-greet-the-world-greetTheWorld/"; + const res = await axios.get(httpFunctionUri); + + return chai.expect(res.data).to.eql(expected); + }).timeout(10000); +}); diff --git a/templates/extensions/javascript/package.lint.json b/templates/extensions/javascript/package.lint.json index 33de627354b..75eaa09a938 100644 --- a/templates/extensions/javascript/package.lint.json +++ b/templates/extensions/javascript/package.lint.json @@ -10,10 +10,16 @@ "eslint": "^8.15.1", "eslint-plugin-promise": "^6.0.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-import": "^2.25.4" + "eslint-plugin-import": "^2.25.4", + "axios": "^1.3.2", + "chai": "^4.3.7", + "mocha": "^10.2.0" }, "scripts": { - "lint": "./node_modules/.bin/eslint --max-warnings=0 .." + "lint": "./node_modules/.bin/eslint --max-warnings=0 ..", + "lint:fix": "./node_modules/.bin/eslint --max-warnings=0 --fix ..", + "mocha": "mocha '**/*.spec.js'", + "test": "(cd integration-tests && firebase emulators:exec 'npm run mocha' -P demo-test)" }, "private": true } \ No newline at end of file diff --git a/templates/extensions/javascript/package.nolint.json b/templates/extensions/javascript/package.nolint.json index 67867620a36..a42c894a265 100644 --- a/templates/extensions/javascript/package.nolint.json +++ b/templates/extensions/javascript/package.nolint.json @@ -6,5 +6,14 @@ "firebase-admin": "^11.5.0", "firebase-functions": "^4.2.0" }, + "devDependencies": { + "axios": "^1.3.2", + "chai": "^4.3.7", + "mocha": "^10.2.0" + }, + "scripts": { + "mocha": "mocha '**/*.spec.js'", + "test": "(cd integration-tests && firebase emulators:exec 'npm run mocha' -P demo-test)" + }, "private": true } \ No newline at end of file diff --git a/templates/extensions/typescript/WELCOME.md b/templates/extensions/typescript/WELCOME.md index a0635c860a2..eae620830ea 100644 --- a/templates/extensions/typescript/WELCOME.md +++ b/templates/extensions/typescript/WELCOME.md @@ -1,9 +1,22 @@ -This directory now contains the source files for a simple extension called **greet-the-world**. To try out this extension right away, install it in an existing Firebase project by running: +This directory now contains the source files for a simple extension called **greet-the-world**. You can try it out right away in the Firebase Emulator suite: first, compile your code by running: -`npm run build --prefix=functions && firebase ext:install . --project=` +`npm run build --prefix=functions` -If you want to jump into the code to customize your extension, then modify **index.ts** and **extension.yaml** in your favorite editor. When you're ready to try out your fancy new extension, run: +Then, navigate to the `functions/integration-test` directory and run: -`npm run build --prefix=functions && firebase ext:install . --project=` +`firebase emulators:start --project=` -As always, in the docs, you can find detailed instructions for creating and testing your extension (including using the emulator!). +If you don't have a project to use, you can instead use '--project=demo-test' to run against a fake project. + +The `integration-test` directory also includes an end to end test (in the file **integration-test.spec.ts**) that verifies that the extension responds back with the expected greeting. You can see it in action by running: + +`npm run test` + +If you want to jump into the code to customize your extension, then modify **index.ts** and **extension.yaml** in your favorite editor. + +If you want to deploy your extension to test on a real project, go to a Firebase project directory (or create a new one with `firebase init`) and run: + +`firebase ext:install ./path/to/extension/directory --project=` +`firebase deploy --only extensions` + +You can find more information about building extensions in the publisher docs: https://firebase.google.com/docs/extensions/alpha/overview-build-extensions diff --git a/templates/extensions/typescript/_mocharc b/templates/extensions/typescript/_mocharc new file mode 100644 index 00000000000..4d837556b90 --- /dev/null +++ b/templates/extensions/typescript/_mocharc @@ -0,0 +1,10 @@ +{ + "require": "ts-node/register", + "extensions": ["ts", "tsx"], + "spec": [ + "integration-tests/**/*.spec.*" + ], + "watch-files": [ + "src" + ] +} \ No newline at end of file diff --git a/templates/extensions/typescript/integration-test.ts b/templates/extensions/typescript/integration-test.ts new file mode 100644 index 00000000000..ca06f775a02 --- /dev/null +++ b/templates/extensions/typescript/integration-test.ts @@ -0,0 +1,13 @@ +import axios from "axios"; +import { expect } from "chai"; + +describe("greet-the-world", () => { + it("should respond with the configured greeting", async () => { + const expected = "Hello World from greet-the-world"; + + const httpFunctionUri = "http://localhost:5001/demo-test/us-central1/ext-greet-the-world-greetTheWorld/"; + const res = await axios.get(httpFunctionUri); + + return expect(res.data).to.eql(expected); + }).timeout(10000); +}); diff --git a/templates/extensions/typescript/package.lint.json b/templates/extensions/typescript/package.lint.json index 5142ed474f7..5e8da3e6001 100644 --- a/templates/extensions/typescript/package.lint.json +++ b/templates/extensions/typescript/package.lint.json @@ -2,8 +2,11 @@ "name": "functions", "scripts": { "lint": "eslint \"src/**/*\"", + "lint:fix": "eslint \"src/**/*\" --fix", "build": "tsc", - "build:watch": "tsc --watch" + "build:watch": "tsc --watch", + "mocha": "mocha '**/*.spec.ts'", + "test": "(cd integration-tests && firebase emulators:exec 'npm run mocha' -P demo-test)" }, "main": "lib/index.js", "dependencies": { @@ -11,12 +14,18 @@ "firebase-functions": "^4.2.0" }, "devDependencies": { + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", "@typescript-eslint/eslint-plugin": "^5.12.0", "@typescript-eslint/parser": "^5.12.0", "eslint": "^8.15.1", "eslint-plugin-import": "^2.26.0", "eslint-config-google": "^0.14.0", - "typescript": "^4.9.0" + "typescript": "^4.9.0", + "axios": "^1.3.2", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "ts-node": "^10.4.0" }, "private": true } \ No newline at end of file diff --git a/templates/extensions/typescript/package.nolint.json b/templates/extensions/typescript/package.nolint.json index 2d524dd3b17..f6914b638dc 100644 --- a/templates/extensions/typescript/package.nolint.json +++ b/templates/extensions/typescript/package.nolint.json @@ -2,7 +2,9 @@ "name": "functions", "scripts": { "build": "tsc", - "build:watch": "tsc --watch" + "build:watch": "tsc --watch", + "mocha": "mocha '**/*.spec.ts'", + "test": "(cd integration-tests && firebase emulators:exec 'npm run mocha' -P demo-test)" }, "main": "lib/index.js", "dependencies": { @@ -10,7 +12,13 @@ "firebase-functions": "^4.2.0" }, "devDependencies": { - "typescript": "^4.9.0" + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", + "typescript": "^4.9.0", + "axios": "^1.3.2", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "ts-node": "^10.4.0" }, "private": true } diff --git a/templates/init/functions/javascript/_eslintrc b/templates/init/functions/javascript/_eslintrc index e8a4eed9395..f4cb76caae2 100644 --- a/templates/init/functions/javascript/_eslintrc +++ b/templates/init/functions/javascript/_eslintrc @@ -3,6 +3,9 @@ module.exports = { es6: true, node: true, }, + parserOptions: { + "ecmaVersion": 2018, + }, extends: [ "eslint:recommended", "google", @@ -14,7 +17,7 @@ module.exports = { }, overrides: [ { - files: ["*.spec.*"], + files: ["**/*.spec.*"], env: { mocha: true, }, From d46ae1fbc6d9ba6f5f4694d8308ca172fc476db4 Mon Sep 17 00:00:00 2001 From: Leonardo Ortiz Date: Mon, 13 Feb 2023 14:01:38 -0300 Subject: [PATCH 0798/1699] Fix Next.js deployments on Windows (#5499) * Stream npm ls when bundling Next.js * replace node spawn with cross-spawn --------- Co-authored-by: James Daniels --- CHANGELOG.md | 1 + src/frameworks/angular/index.ts | 3 +- src/frameworks/index.ts | 3 +- src/frameworks/next/index.ts | 76 +++++++++++++++++--------- src/frameworks/next/utils.ts | 3 +- src/frameworks/vite/index.ts | 3 +- src/test/frameworks/next/utils.spec.ts | 6 ++ 7 files changed, 64 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 316c2d80cbd..371875394ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,3 +11,4 @@ - Fix various accessibility and usability issues in Emulator UI. - Support .env when deploying a web framework (#5501) - Fix various issues with "init hosting" and web frameworks (#5500) +- Fix Next.js deployments on Windows (#5499) diff --git a/src/frameworks/angular/index.ts b/src/frameworks/angular/index.ts index 13f52e1086b..4f6dcbb8feb 100644 --- a/src/frameworks/angular/index.ts +++ b/src/frameworks/angular/index.ts @@ -1,6 +1,7 @@ import type { Target } from "@angular-devkit/architect"; import { join } from "path"; -import { execSync, spawn } from "child_process"; +import { execSync } from "child_process"; +import { spawn } from "cross-spawn"; import { copy, pathExists } from "fs-extra"; import { mkdir } from "fs/promises"; diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index e575ab65ebc..a132536f229 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -1,6 +1,7 @@ import { join, relative, extname, basename } from "path"; import { exit } from "process"; -import { execSync, spawnSync } from "child_process"; +import { execSync } from "child_process"; +import { sync as spawnSync } from "cross-spawn"; import { readdirSync, statSync } from "fs"; import { pathToFileURL } from "url"; import { IncomingMessage, ServerResponse } from "http"; diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index 8a5e126b4d1..f03dd4f5f2c 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -1,4 +1,5 @@ -import { execSync, spawnSync } from "child_process"; +import { execSync } from "child_process"; +import { spawn, sync as spawnSync } from "cross-spawn"; import { mkdir, copyFile } from "fs/promises"; import { dirname, join } from "path"; import type { NextConfig } from "next"; @@ -11,6 +12,10 @@ import { existsSync } from "fs"; import { gte } from "semver"; import { IncomingMessage, ServerResponse } from "http"; import * as clc from "colorette"; +import { chain } from "stream-chain"; +import { parser } from "stream-json"; +import { pick } from "stream-json/filters/Pick"; +import { streamObject } from "stream-json/streamers/StreamObject"; import { BuildResult, @@ -34,7 +39,7 @@ import { isUsingMiddleware, allDependencyNames, } from "./utils"; -import type { Manifest, NpmLsReturn } from "./interfaces"; +import type { Manifest, NpmLsDepdendency } from "./interfaces"; import { readJSON } from "../utils"; import { warnIfCustomBuildScript } from "../utils"; import type { EmulatorInfo } from "../../emulator/types"; @@ -345,33 +350,52 @@ export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: strin export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) { const { distDir } = await getConfig(sourceDir); const packageJson = await readJSON(join(sourceDir, "package.json")); + // Bundle their next.config.js with esbuild via NPX, pinned version was having troubles on m1 + // macs and older Node versions; either way, we should avoid taking on any deps in firebase-tools + // Alternatively I tried using @swc/spack and the webpack bundled into Next.js but was + // encountering difficulties with both of those if (existsSync(join(sourceDir, "next.config.js"))) { - // Bundle their next.config.js with esbuild via NPX, pinned version was having troubles on m1 - // macs and older Node versions; either way, we should avoid taking on any deps in firebase-tools - // Alternatively I tried using @swc/spack and the webpack bundled into Next.js but was - // encountering difficulties with both of those - const dependencyTree: NpmLsReturn = JSON.parse( - spawnSync("npm", ["ls", "--omit=dev", "--all", "--json"], { + try { + const productionDeps = await new Promise((resolve) => { + const dependencies: string[] = []; + const pipeline = chain([ + spawn("npm", ["ls", "--omit=dev", "--all", "--json"], { cwd: sourceDir }).stdout, + parser({ packValues: false, packKeys: true, streamValues: false }), + pick({ filter: "dependencies" }), + streamObject(), + ({ key, value }: { key: string; value: NpmLsDepdendency }) => [ + key, + ...allDependencyNames(value), + ], + ]); + pipeline.on("data", (it: string) => dependencies.push(it)); + pipeline.on("end", () => { + resolve([...new Set(dependencies)]); + }); + }); + // Mark all production deps as externals, so they aren't bundled + // DevDeps won't be included in the Cloud Function, so they should be bundled + const esbuildArgs = productionDeps + .map((it) => `--external:${it}`) + .concat( + "--bundle", + "--platform=node", + `--target=node${NODE_VERSION}`, + `--outdir=${destDir}`, + "--log-level=error" + ); + const bundle = spawnSync("npx", ["--yes", "esbuild", "next.config.js", ...esbuildArgs], { cwd: sourceDir, - }).stdout.toString() - ); - // Mark all production deps as externals, so they aren't bundled - // DevDeps won't be included in the Cloud Function, so they should be bundled - const esbuildArgs = allDependencyNames(dependencyTree) - .map((it) => `--external:${it}`) - .concat( - "--bundle", - "--platform=node", - `--target=node${NODE_VERSION}`, - `--outdir=${destDir}`, - "--log-level=error" + }); + if (bundle.status) { + throw new FirebaseError(bundle.stderr.toString()); + } + } catch (e: any) { + console.warn( + "Unable to bundle next.config.js for use in Cloud Functions, proceeding with deploy but problems may be enountered." ); - const bundle = spawnSync("npx", ["--yes", "esbuild", "next.config.js", ...esbuildArgs], { - cwd: sourceDir, - }); - if (bundle.status) { - console.error(bundle.stderr.toString()); - throw new FirebaseError("Unable to bundle next.config.js for use in Cloud Functions"); + console.error(e.message); + copy(join(sourceDir, "next.config.js"), join(destDir, "next.config.js")); } } if (await pathExists(join(sourceDir, "public"))) { diff --git a/src/frameworks/next/utils.ts b/src/frameworks/next/utils.ts index 793a8cd1245..7ba2faeb601 100644 --- a/src/frameworks/next/utils.ts +++ b/src/frameworks/next/utils.ts @@ -229,6 +229,5 @@ export function allDependencyNames(mod: NpmLsDepdendency): string[] { (acc, it) => [...acc, it, ...allDependencyNames(mod.dependencies![it])], [] as string[] ); - // deduplicate the names - return [...new Set(dependencyNames)]; + return dependencyNames; } diff --git a/src/frameworks/vite/index.ts b/src/frameworks/vite/index.ts index 21f0f6f5ee7..9979c201484 100644 --- a/src/frameworks/vite/index.ts +++ b/src/frameworks/vite/index.ts @@ -1,4 +1,5 @@ -import { execSync, spawn } from "child_process"; +import { execSync } from "child_process"; +import { spawn } from "cross-spawn"; import { existsSync } from "fs"; import { copy, pathExists } from "fs-extra"; import { join } from "path"; diff --git a/src/test/frameworks/next/utils.spec.ts b/src/test/frameworks/next/utils.spec.ts index 31d5877dc49..df19ef17141 100644 --- a/src/test/frameworks/next/utils.spec.ts +++ b/src/test/frameworks/next/utils.spec.ts @@ -353,9 +353,15 @@ describe("Next.js utils", () => { "sass", "styled-jsx", "client-only", + "react", + "react-dom", "loose-envify", "js-tokens", + "react", "scheduler", + "loose-envify", + "react", + "loose-envify", ]); }); }); From 51ebb551b2d4ea7400d01b56c57a6bb2b51a8f63 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Mon, 13 Feb 2023 13:16:17 -0500 Subject: [PATCH 0799/1699] Bumping superstatic dep (#5512) --- npm-shrinkwrap.json | 51 ++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 243498922f5..179a2529579 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -57,7 +57,7 @@ "stream-chain": "^2.2.4", "stream-json": "^1.7.3", "strip-ansi": "^6.0.1", - "superstatic": "^9.0.2", + "superstatic": "^9.0.3", "tar": "^6.1.11", "tcp-port-used": "^1.0.2", "tmp": "^0.2.1", @@ -14155,12 +14155,12 @@ } }, "node_modules/superstatic": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-9.0.2.tgz", - "integrity": "sha512-eKX9qubOaJbtdxn4gWhVVMXuno8cn0WPKOYgLAmLwYiHafrASXAIXHzL3Jx7w06yXiaM5e1DA2Ezeb08iV28+A==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-9.0.3.tgz", + "integrity": "sha512-e/tmW0bsnQ/33ivK6y3CapJT0Ovy4pk/ohNPGhIAGU2oasoNLRQ1cv6enua09NU9w6Y0H/fBu07cjzuiWvLXxw==", "dependencies": { "basic-auth-connect": "^1.0.0", - "commander": "^9.4.0", + "commander": "^10.0.0", "compression": "^1.7.0", "connect": "^3.7.0", "destroy": "^1.0.4", @@ -14170,7 +14170,7 @@ "join-path": "^1.1.1", "lodash": "^4.17.19", "mime-types": "^2.1.35", - "minimatch": "^5.1.0", + "minimatch": "^6.1.6", "morgan": "^1.8.2", "on-finished": "^2.2.0", "on-headers": "^1.0.0", @@ -14197,11 +14197,11 @@ } }, "node_modules/superstatic/node_modules/commander": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", - "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", + "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", "engines": { - "node": "^12.20.0 || >=14" + "node": ">=14" } }, "node_modules/superstatic/node_modules/isarray": { @@ -14210,14 +14210,17 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "node_modules/superstatic/node_modules/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", + "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/superstatic/node_modules/path-to-regexp": { @@ -26678,12 +26681,12 @@ } }, "superstatic": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-9.0.2.tgz", - "integrity": "sha512-eKX9qubOaJbtdxn4gWhVVMXuno8cn0WPKOYgLAmLwYiHafrASXAIXHzL3Jx7w06yXiaM5e1DA2Ezeb08iV28+A==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/superstatic/-/superstatic-9.0.3.tgz", + "integrity": "sha512-e/tmW0bsnQ/33ivK6y3CapJT0Ovy4pk/ohNPGhIAGU2oasoNLRQ1cv6enua09NU9w6Y0H/fBu07cjzuiWvLXxw==", "requires": { "basic-auth-connect": "^1.0.0", - "commander": "^9.4.0", + "commander": "^10.0.0", "compression": "^1.7.0", "connect": "^3.7.0", "destroy": "^1.0.4", @@ -26693,7 +26696,7 @@ "join-path": "^1.1.1", "lodash": "^4.17.19", "mime-types": "^2.1.35", - "minimatch": "^5.1.0", + "minimatch": "^6.1.6", "morgan": "^1.8.2", "on-finished": "^2.2.0", "on-headers": "^1.0.0", @@ -26712,9 +26715,9 @@ } }, "commander": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", - "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==" + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", + "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==" }, "isarray": { "version": "0.0.1", @@ -26722,9 +26725,9 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", + "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", "requires": { "brace-expansion": "^2.0.1" } diff --git a/package.json b/package.json index 0bf207754d4..6243b0908c1 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "stream-chain": "^2.2.4", "stream-json": "^1.7.3", "strip-ansi": "^6.0.1", - "superstatic": "^9.0.2", + "superstatic": "^9.0.3", "tar": "^6.1.11", "tcp-port-used": "^1.0.2", "tmp": "^0.2.1", From 993850f088c1caf9fb40ac4710d4a015ec36f284 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 13 Feb 2023 11:56:50 -0800 Subject: [PATCH 0800/1699] Make Hosting's Run rewrite for v2 functions use API responses (#5491) * Make Hosting's Run rewrite for v2 functions use API responses * Hide log line in debug logs --- src/deploy/functions/backend.ts | 5 +++++ src/deploy/hosting/convertConfig.ts | 2 +- src/gcp/cloudfunctionsv2.ts | 9 +++++++++ src/test/deploy/hosting/convertConfig.spec.ts | 17 ++++++++++++----- src/test/gcp/cloudfunctionsv2.spec.ts | 17 +++++++++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index cb7d0d65b10..464e06b8571 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -366,6 +366,11 @@ export type Endpoint = TargetIds & // Marked as true if a user specifically called this function or codebase with the --only flag. targetedByOnly?: boolean; + + // Output only. For v2 functions, this is the run service ID. + // This may eventually be different than id because GCF is going to start + // doing name translations + runServiceId?: string; }; export interface RequiredAPI { diff --git a/src/deploy/hosting/convertConfig.ts b/src/deploy/hosting/convertConfig.ts index f6669343bae..58bffa53bab 100644 --- a/src/deploy/hosting/convertConfig.ts +++ b/src/deploy/hosting/convertConfig.ts @@ -213,7 +213,7 @@ export async function convertConfig( const apiRewrite: api.Rewrite = { ...target, run: { - serviceId: endpoint.id, + serviceId: endpoint.runServiceId ?? endpoint.id, region: endpoint.region, }, }; diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index e50aeca522c..cf924e5715c 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -691,5 +691,14 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi if (gcfFunction.labels?.[HASH_LABEL]) { endpoint.hash = gcfFunction.labels[HASH_LABEL]; } + const serviceName = gcfFunction.serviceConfig.service; + if (!serviceName) { + logger.debug( + "Got a v2 function without a service name." + + "Maybe we've migrated to using the v2 API everywhere and missed this code" + ); + } else { + endpoint.runServiceId = utils.last(serviceName.split("/")); + } return endpoint; } diff --git a/src/test/deploy/hosting/convertConfig.spec.ts b/src/test/deploy/hosting/convertConfig.spec.ts index 665656bf371..317b0924acc 100644 --- a/src/test/deploy/hosting/convertConfig.spec.ts +++ b/src/test/deploy/hosting/convertConfig.spec.ts @@ -9,7 +9,8 @@ import * as api from "../../../hosting/api"; import { FirebaseError } from "../../../error"; import { Payload } from "../../../deploy/functions/args"; -const FUNCTION_ID = "function"; +const FUNCTION_ID = "functionId"; +const SERVICE_ID = "function-id"; const PROJECT_ID = "project"; const REGION = "region"; @@ -36,6 +37,9 @@ function endpoint(opts?: Partial): backend.Endpoint { ) { ret.httpsTrigger = {}; } + if (opts?.platform === "gcfv2") { + ret.runServiceId = opts?.id ?? SERVICE_ID; + } return ret as backend.Endpoint; } @@ -178,7 +182,7 @@ describe("convertConfig", () => { name: "defaults to a us-central1 rewrite if one is avaiable, v2 edition", input: { rewrites: [{ glob: "/foo", function: { functionId: FUNCTION_ID } }] }, want: { - rewrites: [{ glob: "/foo", run: { region: "us-central1", serviceId: FUNCTION_ID } }], + rewrites: [{ glob: "/foo", run: { region: "us-central1", serviceId: SERVICE_ID } }], }, functionsPayload: { functions: { @@ -192,6 +196,7 @@ describe("convertConfig", () => { region: "europe-west2", platform: "gcfv2", httpsTrigger: {}, + runServiceId: SERVICE_ID, }, { id: FUNCTION_ID, @@ -201,6 +206,7 @@ describe("convertConfig", () => { region: "us-central1", platform: "gcfv2", httpsTrigger: {}, + runServiceId: SERVICE_ID, } ), haveBackend: backend.empty(), @@ -236,7 +242,7 @@ describe("convertConfig", () => { { name: "rewrites referencing CF3v2 functions being deployed are changed to Cloud Run (during release)", input: { rewrites: [{ regex: "/foo$", function: { functionId: FUNCTION_ID } }] }, - want: { rewrites: [{ regex: "/foo$", run: { serviceId: FUNCTION_ID, region: REGION } }] }, + want: { rewrites: [{ regex: "/foo$", run: { serviceId: SERVICE_ID, region: REGION } }] }, functionsPayload: { functions: { default: { @@ -248,6 +254,7 @@ describe("convertConfig", () => { region: REGION, platform: "gcfv2", httpsTrigger: {}, + runServiceId: SERVICE_ID, }), haveBackend: backend.empty(), }, @@ -262,7 +269,7 @@ describe("convertConfig", () => { ], }, want: { - rewrites: [{ regex: "/foo$", run: { serviceId: FUNCTION_ID, region: "us-central1" } }], + rewrites: [{ regex: "/foo$", run: { serviceId: SERVICE_ID, region: "us-central1" } }], }, existingBackend: backend.of(endpoint({ platform: "gcfv2", region: "us-central1" })), }, @@ -275,7 +282,7 @@ describe("convertConfig", () => { }, existingBackend: backend.of(endpoint({ platform: "gcfv2", region: "us-central1" })), want: { - rewrites: [{ regex: "/foo$", run: { serviceId: FUNCTION_ID, region: "us-central1" } }], + rewrites: [{ regex: "/foo$", run: { serviceId: SERVICE_ID, region: "us-central1" } }], }, }, { diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 54c4f623638..16e05e77844 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -382,6 +382,23 @@ describe("cloudfunctionsv2", () => { }); }); + it("should copy run service IDs", () => { + const fn: cloudfunctionsv2.CloudFunction = { + ...HAVE_CLOUD_FUNCTION_V2, + serviceConfig: { + ...HAVE_CLOUD_FUNCTION_V2.serviceConfig, + service: "projects/p/locations/l/services/service-id", + }, + }; + expect(cloudfunctionsv2.endpointFromFunction(fn)).to.deep.equal({ + ...ENDPOINT, + httpsTrigger: {}, + platform: "gcfv2", + uri: RUN_URI, + runServiceId: "service-id", + }); + }); + it("should translate event triggers", () => { let want: backend.Endpoint = { ...ENDPOINT, From b361ddfd125efd8e4be7aa79e3f60aa42626bb78 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 13 Feb 2023 14:57:25 -0800 Subject: [PATCH 0801/1699] Improve ignore files for python functions. (#5513) --- src/init/features/functions/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/init/features/functions/index.ts b/src/init/features/functions/index.ts index 1740104e467..9bc0bb266b0 100644 --- a/src/init/features/functions/index.ts +++ b/src/init/features/functions/index.ts @@ -188,6 +188,9 @@ async function languageSetup(setup: any, config: Config): Promise { case "typescript": cbconfig.ignore = ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"]; break; + case "python": + cbconfig.ignore = ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log"]; + break; } return require("./" + language).setup(setup, config); } From 5869f915696e4df397293fdba76619d87ace1f62 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 13 Feb 2023 23:16:46 +0000 Subject: [PATCH 0802/1699] 11.23.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 179a2529579..9fcc9a191f1 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.22.0", + "version": "11.23.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.22.0", + "version": "11.23.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 6243b0908c1..5ca7b845cb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.22.0", + "version": "11.23.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 50340fcc1130c521d95d9c0e1ad43c855ad112e4 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 13 Feb 2023 23:16:57 +0000 Subject: [PATCH 0803/1699] [firebase-release] Removed change log and reset repo after 11.23.0 release --- CHANGELOG.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371875394ec..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +0,0 @@ -- Fix storage download name issue #5478 -- Refactor the way timeouts are enforced by the Functions Emulator (#5464) -- Fix bug where cloudevent emitted by various emulators didn't conform to spec (#5466) -- Upgrade the emulator suite UI to 1.11.3 to capture some bug fixes (#5479) -- Web frameworks deploys can once again bundle local NPM dependencies (#5440) -- Catches error when attempting to deploy without a project (#5415) -- Fixes a number of issues and outdated dependencies in templates for `init --only functions` and `ext:dev:init` -- Adds integration tests and useful scripts to the extension directory created by `ext:dev:init`. -- Support private network access (CORS-RFC1918) in Firestore Emulator (#4227) -- Fix some edge cases where Emulator UI cannot reach the emulators (#912) -- Fix various accessibility and usability issues in Emulator UI. -- Support .env when deploying a web framework (#5501) -- Fix various issues with "init hosting" and web frameworks (#5500) -- Fix Next.js deployments on Windows (#5499) From dc3e7b7c783863600f3f8958d64a99572b3c73b4 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 14 Feb 2023 10:16:57 -0800 Subject: [PATCH 0804/1699] Fix bug where --inspect-functions flag always fails. (#5516) Fixes https://github.com/firebase/firebase-tools/issues/5509 --- CHANGELOG.md | 1 + src/emulator/functionsEmulator.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..065824bc2c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +Fix bug where --inspect-functions flag always fails. #5516 diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 6965429405d..88556e87536 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -690,7 +690,7 @@ export class FunctionsEmulator implements EmulatorInstance { // In debug mode, we eagerly start the runtime processes to allow debuggers to attach // before invoking a function. if (this.args.debugPort) { - if (!emulatableBackend.bin?.startsWith("node")) { + if (!emulatableBackend.runtime?.startsWith("node")) { this.logger.log("WARN", "--inspect-functions only supported for Node.js runtimes."); } else { // Since we're about to start a runtime to be shared by all the functions in this codebase, From 9aef0f4f0bfcdbc13a3478d90fb25123822efdd7 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 14 Feb 2023 11:09:09 -0800 Subject: [PATCH 0805/1699] Lookup more places to find Firebase Functions SDK in developer's workspace. (#5518) Fixes #5517. --- CHANGELOG.md | 3 ++- src/deploy/functions/runtimes/node/index.ts | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 065824bc2c7..1d09c52432a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ -Fix bug where --inspect-functions flag always fails. #5516 +- Fix bug where CLI couldn't discover functions for monorepo setups. (#5518) +- Fix bug where --inspect-functions flag always fails. #5516 diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index b4f22bcefcd..3eb9ef5602d 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -176,14 +176,25 @@ export class Delegate { // We'll try few routes in the following order: // // 1. $SOURCE_DIR/node_modules/.bin/firebase-functions - // 2. node_modules closest to the resolved path ${require.resolve("firebase-functions")} + // 2. $PROJECT_DIR/node_modules/.bin/firebase-functions + // 3. node_modules closest to the resolved path ${require.resolve("firebase-functions")} + // 4. (2) but ignore .pnpm directory // - // (1) works for most package managers (npm, yarn[no-hoist],pnpm). - // (2) handles cases where developer prefers monorepo setup or bundled function code. + // (1) works for most package managers (npm, yarn[no-hoist]). + // (2) works for some monorepo setup. + // (3) handles cases where developer prefers monorepo setup or bundled function code. + // (4) handles issue with some .pnpm setup (see https://github.com/firebase/firebase-tools/issues/5517) const sourceNodeModulesPath = path.join(this.sourceDir, "node_modules"); + const projectNodeModulesPath = path.join(this.projectDir, "node_modules"); const sdkPath = require.resolve("firebase-functions", { paths: [this.sourceDir] }); const sdkNodeModulesPath = sdkPath.substring(0, sdkPath.lastIndexOf("node_modules") + 12); - for (const nodeModulesPath of [sourceNodeModulesPath, sdkNodeModulesPath]) { + const ignorePnpmModulesPath = sdkNodeModulesPath.replace(/\/\.pnpm\/.*/, ""); + for (const nodeModulesPath of [ + sourceNodeModulesPath, + projectNodeModulesPath, + sdkNodeModulesPath, + ignorePnpmModulesPath, + ]) { const binPath = path.join(nodeModulesPath, ".bin", "firebase-functions"); if (fileExistsSync(binPath)) { logger.debug(`Found firebase-functions binary at '${binPath}'`); From e770a85cdee0f9f10f8c2c60f5147a3403e7ccc5 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 14 Feb 2023 20:44:14 +0000 Subject: [PATCH 0806/1699] 11.23.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 9fcc9a191f1..6c791c2a581 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.23.0", + "version": "11.23.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.23.0", + "version": "11.23.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 5ca7b845cb0..7864561d863 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.23.0", + "version": "11.23.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From d6d396a62e334ae8dd03105761c2399f94dc35dd Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 14 Feb 2023 20:44:27 +0000 Subject: [PATCH 0807/1699] [firebase-release] Removed change log and reset repo after 11.23.1 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d09c52432a..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Fix bug where CLI couldn't discover functions for monorepo setups. (#5518) -- Fix bug where --inspect-functions flag always fails. #5516 From d6826ee9e9c2dc5b8e320953aa794be7ba2fdcdb Mon Sep 17 00:00:00 2001 From: Chalo Salvador Date: Tue, 14 Feb 2023 22:37:50 +0100 Subject: [PATCH 0808/1699] Allow configuration of web frameworks backend (#5504) * Allow a subset of HttpsOptions to be specified in firebase.json (hosting.frameworksBackend) * Don't build the functions.yaml in firebase-tools, instead lean on firebase-functions to encode * Prompt for SSR region during frameworks "hosting init", defaulting to us-central1 --------- Co-authored-by: James Daniels --- CHANGELOG.md | 1 + schema/firebase-config.json | 129 ++++++++++++++++++++ src/deploy/functions/runtimes/node/index.ts | 2 + src/firebaseConfig.ts | 25 ++++ src/frameworks/index.ts | 50 ++++---- src/init/features/hosting/index.ts | 16 ++- 6 files changed, 196 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..89c36052247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Allow configuration of the Cloud Function generated for full-stack web frameworks (#5504) diff --git a/schema/firebase-config.json b/schema/firebase-config.json index 839508468e6..e2536dd4f15 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -5,6 +5,126 @@ "ExtensionsConfig": { "additionalProperties": false, "type": "object" + }, + "FrameworksBackendOptions": { + "additionalProperties": false, + "properties": { + "concurrency": { + "description": "Number of requests a function can serve at once.", + "type": "number" + }, + "cors": { + "description": "If true, allows CORS on requests to this function.\nIf this is a `string` or `RegExp`, allows requests from domains that match the provided value.\nIf this is an `Array`, allows requests from domains matching at least one entry of the array.\nDefaults to true for {@link https.CallableFunction} and false otherwise.", + "type": [ + "string", + "boolean" + ] + }, + "cpu": { + "anyOf": [ + { + "enum": [ + "gcf_gen1" + ], + "type": "string" + }, + { + "type": "number" + } + ], + "description": "Fractional number of CPUs to allocate to a function." + }, + "enforceAppCheck": { + "description": "Determines whether Firebase AppCheck is enforced. Defaults to false.", + "type": "boolean" + }, + "ingressSettings": { + "description": "Ingress settings which control where this function can be called from.", + "enum": [ + "ALLOW_ALL", + "ALLOW_INTERNAL_AND_GCLB", + "ALLOW_INTERNAL_ONLY" + ], + "type": "string" + }, + "invoker": { + "description": "Invoker to set access control on https functions.", + "enum": [ + "public" + ], + "type": "string" + }, + "labels": { + "$ref": "#/definitions/Record", + "description": "User labels to set on the function." + }, + "maxInstances": { + "description": "Max number of instances to be running in parallel.", + "type": "number" + }, + "memory": { + "description": "Amount of memory to allocate to a function.", + "enum": [ + "128MiB", + "16GiB", + "1GiB", + "256MiB", + "2GiB", + "32GiB", + "4GiB", + "512MiB", + "8GiB" + ], + "type": "string" + }, + "minInstances": { + "description": "Min number of actual instances to be running at a given time.", + "type": "number" + }, + "omit": { + "description": "If true, do not deploy or emulate this function.", + "type": "boolean" + }, + "preserveExternalChanges": { + "description": "Controls whether function configuration modified outside of function source is preserved. Defaults to false.", + "type": "boolean" + }, + "region": { + "description": "HTTP functions can override global options and can specify multiple regions to deploy to.", + "type": "string" + }, + "secrets": { + "items": { + "type": "string" + }, + "type": "array" + }, + "serviceAccount": { + "description": "Specific service account for the function to run as.", + "type": "string" + }, + "timeoutSeconds": { + "description": "Timeout for the function in sections, possible values are 0 to 540.\nHTTPS functions can specify a higher timeout.", + "type": "number" + }, + "vpcConnector": { + "description": "Connect cloud function to specified VPC connector.", + "type": "string" + }, + "vpcConnectorEgressSettings": { + "description": "Egress settings for VPC connector.", + "enum": [ + "ALL_TRAFFIC", + "PRIVATE_RANGES_ONLY" + ], + "type": "string" + } + }, + "type": "object" + }, + "Record": { + "additionalProperties": false, + "type": "object" } }, "properties": { @@ -473,6 +593,9 @@ "cleanUrls": { "type": "boolean" }, + "frameworksBackend": { + "$ref": "#/definitions/FrameworksBackendOptions" + }, "headers": { "items": { "anyOf": [ @@ -1064,6 +1187,9 @@ "cleanUrls": { "type": "boolean" }, + "frameworksBackend": { + "$ref": "#/definitions/FrameworksBackendOptions" + }, "headers": { "items": { "anyOf": [ @@ -1655,6 +1781,9 @@ "cleanUrls": { "type": "boolean" }, + "frameworksBackend": { + "$ref": "#/definitions/FrameworksBackendOptions" + }, "headers": { "items": { "anyOf": [ diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 3eb9ef5602d..92ac00b6d99 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -166,6 +166,8 @@ export class Delegate { HOME: process.env.HOME, PATH: process.env.PATH, NODE_ENV: process.env.NODE_ENV, + // Web Frameworks fails without this environment variable + __FIREBASE_FRAMEWORKS_ENTRY__: process.env.__FIREBASE_FRAMEWORKS_ENTRY__, }; if (Object.keys(config || {}).length) { env.CLOUD_RUNTIME_CONFIG = JSON.stringify(config); diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index ad1351aa8f9..32583495261 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -6,6 +6,8 @@ // import { RequireAtLeastOne } from "./metaprogramming"; +import type { HttpsOptions } from "firebase-functions/v2/https"; +import { IngressSetting, MemoryOption, VpcEgressSetting } from "firebase-functions/v2/options"; // should be sourced from - https://github.com/firebase/firebase-tools/blob/master/src/deploy/functions/runtimes/index.ts#L15 type CloudFunctionRuntimes = "nodejs10" | "nodejs12" | "nodejs14" | "nodejs16" | "nodejs18"; @@ -67,6 +69,28 @@ export type HostingHeaders = HostingSource & { }[]; }; +// Allow only serializable options, since this is in firebase.json +// TODO(jamesdaniels) look into allowing serialized CEL expressions, params, and regexp +// and if we can build this interface automatically via Typescript silliness +interface FrameworksBackendOptions extends HttpsOptions { + omit?: boolean; + cors?: string | boolean; + memory?: MemoryOption; + timeoutSeconds?: number; + minInstances?: number; + maxInstances?: number; + concurrency?: number; + vpcConnector?: string; + vpcConnectorEgressSettings?: VpcEgressSetting; + serviceAccount?: string; + ingressSettings?: IngressSetting; + secrets?: string[]; + // Only allow a single region to be specified + region?: string; + // Invoker can only be public + invoker?: "public"; +} + export type HostingBase = { public?: string; source?: string; @@ -80,6 +104,7 @@ export type HostingBase = { i18n?: { root: string; }; + frameworksBackend?: FrameworksBackendOptions; }; export type HostingSingle = HostingBase & { diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index a132536f229..f123a8943a4 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -109,8 +109,15 @@ const SupportLevelWarnings = { export const FIREBASE_FRAMEWORKS_VERSION = "^0.6.0"; export const FIREBASE_FUNCTIONS_VERSION = "^3.23.0"; export const FIREBASE_ADMIN_VERSION = "^11.0.1"; -export const DEFAULT_REGION = "us-central1"; export const NODE_VERSION = parseInt(process.versions.node, 10).toString(); +export const DEFAULT_REGION = "us-central1"; +export const ALLOWED_SSR_REGIONS = [ + { name: "us-central1 (Iowa)", value: "us-central1" }, + { name: "us-west1 (Oregon)", value: "us-west1" }, + { name: "us-east1 (South Carolina)", value: "us-east1" }, + { name: "europe-west1 (Belgium)", value: "europe-west1" }, + { name: "asia-east1 (Taiwan)", value: "asia-east1" }, +]; const DEFAULT_FIND_DEP_OPTIONS: FindDepOptions = { cwd: process.cwd(), @@ -294,8 +301,9 @@ export async function prepareFrameworks( if (configs.length === 0) { return; } + const allowedRegionsValues = ALLOWED_SSR_REGIONS.map((r) => r.value); for (const config of configs) { - const { source, site, public: publicDir } = config; + const { source, site, public: publicDir, frameworksBackend } = config; if (!source) { continue; } @@ -309,8 +317,15 @@ export async function prepareFrameworks( if (publicDir) { throw new Error(`hosting.public and hosting.source cannot both be set in firebase.json`); } + const ssrRegion = frameworksBackend?.region ?? DEFAULT_REGION; + if (!allowedRegionsValues.includes(ssrRegion)) { + const validRegions = allowedRegionsValues.join(", "); + throw new FirebaseError( + `Hosting config for site ${site} places server-side content in region ${ssrRegion} which is not known. Valid regions are ${validRegions}` + ); + } const getProjectPath = (...args: string[]) => join(projectRoot, source, ...args); - const functionName = `ssr${site.toLowerCase().replace(/-/g, "")}`; + const functionId = `ssr${site.toLowerCase().replace(/-/g, "")}`; const usesFirebaseAdminSdk = !!findDependency("firebase-admin", { cwd: getProjectPath() }); const usesFirebaseJsSdk = !!findDependency("@firebase/app", { cwd: getProjectPath() }); if (usesFirebaseAdminSdk) { @@ -432,7 +447,7 @@ export async function prepareFrameworks( const rewrite: HostingRewrites = { source: "**", function: { - functionId: functionName, + functionId, }, }; if (experiments.isEnabled("pintags")) { @@ -482,27 +497,8 @@ export async function prepareFrameworks( frameworksEntry = framework, } = await codegenFunctionsDirectory(getProjectPath(), functionsDist); - await writeFile( - join(functionsDist, "functions.yaml"), - JSON.stringify( - { - endpoints: { - [functionName]: { - platform: "gcfv2", - // TODO allow this to be configurable - region: [DEFAULT_REGION], - labels: {}, - httpsTrigger: {}, - entryPoint: "ssr", - }, - }, - specVersion: "v1alpha1", - requiredAPIs: [], - }, - null, - 2 - ) - ); + // Set the framework entry in the env variables to handle generation of the functions.yaml + process.env.__FIREBASE_FRAMEWORKS_ENTRY__ = frameworksEntry; packageJson.main = "server.js"; delete packageJson.devDependencies; @@ -580,7 +576,9 @@ ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\ join(functionsDist, "server.js"), `const { onRequest } = require('firebase-functions/v2/https'); const server = import('firebase-frameworks'); -exports.ssr = onRequest((req, res) => server.then(it => it.handle(req, res))); +exports.${functionId} = onRequest(${JSON.stringify( + frameworksBackend || {} + )}, (req, res) => server.then(it => it.handle(req, res))); ` ); } else { diff --git a/src/init/features/hosting/index.ts b/src/init/features/hosting/index.ts index 02641a47f8f..8c58d9acc66 100644 --- a/src/init/features/hosting/index.ts +++ b/src/init/features/hosting/index.ts @@ -6,7 +6,7 @@ import { Client } from "../../../apiv2"; import { initGitHub } from "./github"; import { prompt, promptOnce } from "../../../prompt"; import { logger } from "../../../logger"; -import { discover, WebFrameworks } from "../../../frameworks"; +import { ALLOWED_SSR_REGIONS, DEFAULT_REGION, discover, WebFrameworks } from "../../../frameworks"; import * as experiments from "../../../experiments"; import { join } from "path"; @@ -116,10 +116,24 @@ export async function doSetup(setup: any, config: any): Promise { await WebFrameworks[setup.hosting.whichFramework].init!(setup, config); } + await promptOnce( + { + name: "region", + type: "list", + message: "In which region would you like to host server-side content, if applicable?", + default: DEFAULT_REGION, + choices: ALLOWED_SSR_REGIONS, + }, + setup.hosting + ); + setup.config.hosting = { source: setup.hosting.source, // TODO swap out for framework ignores ignore: DEFAULT_IGNORES, + frameworksBackend: { + region: setup.hosting.region, + }, }; } else { logger.info(); From 05f4f50c271021e7425e80e38350c06e1a0a4ec8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 17 Feb 2023 16:15:01 -0800 Subject: [PATCH 0809/1699] Improve error message when given invalid hosting configuration. (#5533) Given invalid hosting configuration, Firebase CLI fails in an unhelpful way. e.g. ``` { "hosting": { "public": "public", "rewrites": [ { // missing "destination", "function", or "run" "source": "**" } ] } } ``` **Before**: ``` $ firebase deploy --only hosting ... Error: An unexpected error has occurred. $ firebase deploy --only hosting --debug ... Error: Never has a value ([object Object]). This should be impossible ``` **After** ``` $ firebase deploy --only hosting ... Error: Invalid hosting rewrite config in firebase.json. A rewrite config must specify 'destination', 'function', 'dynamicLinks', or 'run' ``` Fixes https://groups.google.com/a/google.com/g/firebase-discuss/c/VoQ0XkNnZFs --- CHANGELOG.md | 1 + src/deploy/hosting/convertConfig.ts | 9 ++++++++- src/functional.ts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c36052247..46e3a171c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Allow configuration of the Cloud Function generated for full-stack web frameworks (#5504) +- Improve error message during deploy when given invalid hosting rewrite rule (#5533) diff --git a/src/deploy/hosting/convertConfig.ts b/src/deploy/hosting/convertConfig.ts index 58bffa53bab..ae4f706fe73 100644 --- a/src/deploy/hosting/convertConfig.ts +++ b/src/deploy/hosting/convertConfig.ts @@ -247,7 +247,14 @@ export async function convertConfig( // This line makes sure this function breaks if there is ever added a new // kind of rewrite and we haven't yet handled it. - assertExhaustive(rewrite); + try { + assertExhaustive(rewrite); + } catch (e: any) { + throw new FirebaseError( + "Invalid hosting rewrite config in firebase.json. " + + "A rewrite config must specify 'destination', 'function', 'dynamicLinks', or 'run'" + ); + } }); if (config.rewrites) { diff --git a/src/functional.ts b/src/functional.ts index bafeb5d5b2d..3e166061a0e 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -88,7 +88,7 @@ export const zipIn = /** Used with type guards to guarantee that all cases have been covered. */ export function assertExhaustive(val: never): never { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - throw new Error(`Never has a value (${val}). This should be impossible`); + throw new Error(`Never has a value (${val}).`); } /** From 4755cb70aa8121776856d98d5d9415704b480249 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 17 Feb 2023 16:46:26 -0800 Subject: [PATCH 0810/1699] Fix typo in function init template. (#5535) --- templates/init/functions/javascript/package.lint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/init/functions/javascript/package.lint.json b/templates/init/functions/javascript/package.lint.json index 240104ba573..1e606aec3be 100644 --- a/templates/init/functions/javascript/package.lint.json +++ b/templates/init/functions/javascript/package.lint.json @@ -14,7 +14,7 @@ }, "main": "index.js", "dependencies": { - "firebase-admin": "^111.5.0", + "firebase-admin": "^11.5.0", "firebase-functions": "^4.2.0" }, "devDependencies": { From 74f82cf59be8e14c6aa134f04fdaac69e99cd630 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 21 Feb 2023 11:49:19 -0800 Subject: [PATCH 0811/1699] Fixes bugs when saving system params to manifest (#5532) * Fixes bugs when saving system params to manifest * Linting * ReplaceAll is not defined in node14 :( * Fixing tests * Removes .only --- src/extensions/manifest.ts | 2 +- src/prompt.ts | 4 +++- src/test/prompt.spec.ts | 8 ++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index 6fd47d62322..cb3f3c2222b 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -201,7 +201,7 @@ async function writeEnvFiles( ): Promise { for (const spec of specs) { const content = Object.entries(spec.params) - .filter((r) => r[1].baseValue !== "") // Don't write empty values + .filter((r) => r[1].baseValue !== "" && r[1].baseValue !== undefined) // Don't write empty values .sort((a, b) => { return a[0].localeCompare(b[0]); }) diff --git a/src/prompt.ts b/src/prompt.ts index 5658d2a08fd..174688d4b1e 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -91,7 +91,9 @@ export async function promptOnce( * @return The value as returned by `inquirer` for that quesiton. */ export async function promptOnce(question: Question, options: Options = {}): Promise { - question.name = question.name || "question"; + // Need to replace any .'s in the question name - otherwise, Inquirer puts the answer + // in a nested object like so: `"a.b.c" => {a: {b: {c: "my-answer"}}}` + question.name = question.name?.replace(/\./g, "/") || "question"; await prompt(options, [question]); return options[question.name]; } diff --git a/src/test/prompt.spec.ts b/src/test/prompt.spec.ts index 0adce547099..4b6e271db9b 100644 --- a/src/test/prompt.spec.ts +++ b/src/test/prompt.spec.ts @@ -9,6 +9,7 @@ describe("prompt", () => { let inquirerStub: sinon.SinonStub; const PROMPT_RESPONSES = { lint: true, + "lint/dint/mint": true, project: "the-best-project-ever", }; @@ -73,5 +74,12 @@ describe("prompt", () => { expect(r).to.equal(true); expect(inquirerStub).calledOnce; }); + + it("should handle names with .'s", async () => { + const r = await prompt.promptOnce({ name: "lint.dint.mint" }); + + expect(r).to.equal(true); + expect(inquirerStub).calledOnce; + }); }); }); From c05b9cd702bf15858cf4e202eee51efbb4a70e90 Mon Sep 17 00:00:00 2001 From: Austin Crim Date: Tue, 21 Feb 2023 18:14:23 -0600 Subject: [PATCH 0812/1699] Generate ESM-compatible web framework SSR function (#5540) --- Co-authored-by: Simon Wagner --- CHANGELOG.md | 1 + src/frameworks/index.ts | 31 ++++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46e3a171c55..e8ec64aa51f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Allow configuration of the Cloud Function generated for full-stack web frameworks (#5504) - Improve error message during deploy when given invalid hosting rewrite rule (#5533) +- Generate ESM-compatible SSR function for web frameworks (#5540) diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index f123a8943a4..ce6bd18838b 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -572,15 +572,28 @@ ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\ if (bootstrapScript) await writeFile(join(functionsDist, "bootstrap.js"), bootstrapScript); // TODO move to templates - await writeFile( - join(functionsDist, "server.js"), - `const { onRequest } = require('firebase-functions/v2/https'); -const server = import('firebase-frameworks'); -exports.${functionId} = onRequest(${JSON.stringify( - frameworksBackend || {} - )}, (req, res) => server.then(it => it.handle(req, res))); -` - ); + + if (packageJson.type === "module") { + await writeFile( + join(functionsDist, "server.js"), + `import { onRequest } from 'firebase-functions/v2/https'; + const server = import('firebase-frameworks'); + export const ${functionId} = onRequest(${JSON.stringify( + frameworksBackend || {} + )}, (req, res) => server.then(it => it.handle(req, res))); + ` + ); + } else { + await writeFile( + join(functionsDist, "server.js"), + `const { onRequest } = require('firebase-functions/v2/https'); + const server = import('firebase-frameworks'); + exports.${functionId} = onRequest(${JSON.stringify( + frameworksBackend || {} + )}, (req, res) => server.then(it => it.handle(req, res))); + ` + ); + } } else { // No function, treat as an SPA // TODO(jamesdaniels) be smarter about this, leave it to the framework? From 07cc578b09b2f6117cf4a8b0104a7d319b7f83da Mon Sep 17 00:00:00 2001 From: aalej Date: Fri, 24 Feb 2023 02:23:11 +0800 Subject: [PATCH 0813/1699] Added condition to change value of port when options.port value is specified. (#5531) --- CHANGELOG.md | 1 + src/functionsShellCommandAction.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ec64aa51f..c34f3d920f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Allow configuration of the Cloud Function generated for full-stack web frameworks (#5504) +- Fixes bug where passing `--port` flag in `functions:shell` does not set which port to emulate functions (#5521) - Improve error message during deploy when given invalid hosting rewrite rule (#5533) - Generate ESM-compatible SSR function for web frameworks (#5540) diff --git a/src/functionsShellCommandAction.ts b/src/functionsShellCommandAction.ts index e6ac89148eb..9d637803aae 100644 --- a/src/functionsShellCommandAction.ts +++ b/src/functionsShellCommandAction.ts @@ -49,6 +49,10 @@ export const actionFunction = async (options: Options) => { // If the port was not set by the --port flag or determined from 'firebase.json', just scan // up from 5000 let port = 5000; + if (typeof options.port === "number") { + port = options.port; + } + const functionsInfo = remoteEmulators[Emulators.FUNCTIONS]; if (functionsInfo) { utils.logLabeledWarning( From cc889a51a631da81478ee36a46df1c60cf218c6d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 23 Feb 2023 11:31:48 -0800 Subject: [PATCH 0814/1699] Inject `FIREBASE_CONFIG` environment variable for scripts executed via emulators:exec command. (#5544) As discussed in https://github.com/firebase/firebase-tools/issues/5536#issuecomment-1435432041, we learned that old versions of Firebase Functions SDK always made `process.env.FIREBASE_CONFIG` available to the underlying script that imported the Firebase Functions SDK. While making `process.env.FIREBASE_CONFIG` available wasn't intentional, we'll consider this a case of [Hyrum's Law](https://www.hyrumslaw.com/) and will try our best to fix the regression by making `process.env.FIREBASE_CONFIG` environment variable available if not set in the parent process. Fixes https://github.com/firebase/firebase-tools/issues/5536 --- CHANGELOG.md | 1 + src/emulator/commandUtils.ts | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c34f3d920f9..9effcb8b088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Fixes bug where passing `--port` flag in `functions:shell` does not set which port to emulate functions (#5521) - Improve error message during deploy when given invalid hosting rewrite rule (#5533) - Generate ESM-compatible SSR function for web frameworks (#5540) +- Fix bug emulators:exec script didn't populate FIREBASE_CONFIG environment variable (#5544) diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index 1bea0bc62b7..bba72c808d2 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -329,6 +329,18 @@ async function runScript(script: string, extraEnv: Record): Prom utils.logBullet(`Running script: ${clc.bold(script)}`); const env: NodeJS.ProcessEnv = { ...process.env, ...extraEnv }; + // Hyrum's Law strikes here: + // Scripts that imported older versions of Firebase Functions SDK accidentally made + // the FIREBASE_CONFIG environment variable always available to the script. + // Many users ended up depending on this behavior, so we conditionally inject the env var + // if the FIREBASE_CONFIG env var isn't explicitly set in the parent process. + if (env.GCLOUD_PROJECT && !env.FIREBASE_CONFIG) { + env.FIREBASE_CONFIG = JSON.stringify({ + projectId: env.GCLOUD_PROJECT, + storageBucket: `${env.GCLOUD_PROJECT}.appspot.com`, + databaseURL: `https://${env.GCLOUD_PROJECT}.firebaseio.com`, + }); + } const emulatorInfos = EmulatorRegistry.listRunningWithInfo(); setEnvVarsForEmulators(env, emulatorInfos); @@ -448,7 +460,7 @@ const JAVA_HINT = "Please make sure Java is installed and on your system PATH."; /** * Return whether Java major verion is supported. Throws if Java not available. * - * @returns Java major version (for Java >= 9) or -1 otherwise + * @return Java major version (for Java >= 9) or -1 otherwise */ export async function checkJavaMajorVersion(): Promise { return new Promise((resolve, reject) => { @@ -510,7 +522,7 @@ export async function checkJavaMajorVersion(): Promise { }); }).then((output) => { let versionInt = -1; - const match = output.match(JAVA_VERSION_REGEX); + const match = JAVA_VERSION_REGEX.exec(output); if (match) { const version = match[1]; versionInt = parseInt(version, 10); From ee26beae740aa906c604e728b2a752f0a74d7994 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 23 Feb 2023 23:35:10 +0000 Subject: [PATCH 0815/1699] 11.24.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 6c791c2a581..0b556f51fbd 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.23.1", + "version": "11.24.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.23.1", + "version": "11.24.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 7864561d863..1c9d9eebdc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.23.1", + "version": "11.24.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From b3f4aebdac2e8a6d8d4528ecfc633c001fcc7e65 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 23 Feb 2023 23:35:24 +0000 Subject: [PATCH 0816/1699] [firebase-release] Removed change log and reset repo after 11.24.0 release --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9effcb8b088..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +0,0 @@ -- Allow configuration of the Cloud Function generated for full-stack web frameworks (#5504) -- Fixes bug where passing `--port` flag in `functions:shell` does not set which port to emulate functions (#5521) -- Improve error message during deploy when given invalid hosting rewrite rule (#5533) -- Generate ESM-compatible SSR function for web frameworks (#5540) -- Fix bug emulators:exec script didn't populate FIREBASE_CONFIG environment variable (#5544) From fe6becb1fb71cf9761b8a32bf99f47f74bc7facc Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 27 Feb 2023 13:20:24 -0800 Subject: [PATCH 0817/1699] Rename CHANGELOG template so clean-publish doesn't delete it (#5554) * Rename CHANGELOG template so clen-publish doesnt delete it before publishing * Rename to CL-template.md because clean-package uses very broad regex --- CHANGELOG.md | 1 + src/commands/ext-dev-init.ts | 2 +- templates/extensions/{CHANGELOG.md => CL-template.md} | 0 3 files changed, 2 insertions(+), 1 deletion(-) rename templates/extensions/{CHANGELOG.md => CL-template.md} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..997a4f98e8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes an issue where `ext:dev:init` would fail due to a missing CHANGELOG.md file (#5530). diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index 00a650c3415..b771e15e1ab 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -30,7 +30,7 @@ function readCommonTemplates() { extSpecTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "extension.yaml"), "utf8"), preinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "PREINSTALL.md"), "utf8"), postinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "POSTINSTALL.md"), "utf8"), - changelogTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "CHANGELOG.md"), "utf8"), + changelogTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "CL-template.md"), "utf8"), }; } diff --git a/templates/extensions/CHANGELOG.md b/templates/extensions/CL-template.md similarity index 100% rename from templates/extensions/CHANGELOG.md rename to templates/extensions/CL-template.md From 14edc7c2fbff5d07759100ca13f923d3c040e954 Mon Sep 17 00:00:00 2001 From: Jeff <3759507+jhuleatt@users.noreply.github.com> Date: Fri, 3 Mar 2023 14:12:23 -0500 Subject: [PATCH 0818/1699] Update functions init template (#5569) --- templates/init/functions/python/main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/init/functions/python/main.py b/templates/init/functions/python/main.py index 1d82099501f..1d2add1e26a 100644 --- a/templates/init/functions/python/main.py +++ b/templates/init/functions/python/main.py @@ -2,12 +2,12 @@ # To get started, simply uncomment the below code or create your own. # Deploy with `firebase deploy` -from firebase_functions import https +from firebase_functions import https_fn from firebase_admin import initialize_app # initialize_app() # # -# @https.on_request() -# def on_request_example(req: https.Request) -> https.Response: -# return https.Response("Hello world!") \ No newline at end of file +# @https_fn.on_request() +# def on_request_example(req: https_fn.Request) -> https_fn.Response: +# return https_fn.Response("Hello world!") \ No newline at end of file From 2d8d818a06b9eb49d95f62062daa230a52042de5 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 7 Mar 2023 20:13:14 +0000 Subject: [PATCH 0819/1699] 11.24.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0b556f51fbd..b74dd2e15e5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.24.0", + "version": "11.24.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.24.0", + "version": "11.24.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 1c9d9eebdc0..94f0ed8ac40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.24.0", + "version": "11.24.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From d659b9dfaf71dcccb4432de5df1cac4a70ade2d5 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 7 Mar 2023 20:13:29 +0000 Subject: [PATCH 0820/1699] [firebase-release] Removed change log and reset repo after 11.24.1 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 997a4f98e8a..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes an issue where `ext:dev:init` would fail due to a missing CHANGELOG.md file (#5530). From b3081110fe0e53ec4292132af697d609bf848805 Mon Sep 17 00:00:00 2001 From: Christopher Cartland Date: Tue, 14 Mar 2023 15:35:38 -0700 Subject: [PATCH 0821/1699] Add trailing comma to comply ESLint comma-dangle (#5586) firebase init, choosing functions, TypeScript, and enable ESLint to catch bugs and enforce style. Without this comma, the lint reports an error. npm --prefix "functions" run lint 30:27 error Missing trailing comma to match comma-dangle --- templates/init/functions/typescript/_eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/init/functions/typescript/_eslintrc b/templates/init/functions/typescript/_eslintrc index 4ed894186d0..b16341a8fea 100644 --- a/templates/init/functions/typescript/_eslintrc +++ b/templates/init/functions/typescript/_eslintrc @@ -27,6 +27,6 @@ module.exports = { rules: { "quotes": ["error", "double"], "import/no-unresolved": 0, - "indent": ["error", 2] + "indent": ["error", 2], }, }; From 493bf6e4f6134b73b9e7528760a47cff54dc5dc9 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 14 Mar 2023 23:42:31 -0700 Subject: [PATCH 0822/1699] Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) Object containing environment variable was mistakenly shared for all function. This is usually okay as CF3 doesn't allow user to define per-function environment variable. However, Firebase-defined `EVENTARC_CLOUD_EVENT_SOURCE` env var is unique per function and due to this bug, all but one function has correct value for it. Fixes https://github.com/firebase/firebase-tools/issues/5333. --- CHANGELOG.md | 1 + src/deploy/functions/prepare.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..4b92ff4089b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 70f6713d6a8..38f22c88675 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -135,7 +135,7 @@ export async function prepare( } for (const endpoint of backend.allEndpoints(wantBackend)) { - endpoint.environmentVariables = wantBackend.environmentVariables || {}; + endpoint.environmentVariables = { ...wantBackend.environmentVariables } || {}; let resource: string; if (endpoint.platform === "gcfv1") { resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`; From e16fe0a8018da72b15fdc45439c48bb4ebaa74f7 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 15 Mar 2023 00:03:31 -0700 Subject: [PATCH 0823/1699] Cleaning up outdated warnings around --params (#5595) * Cleaning up outdated warnings around --params * Clean up tests too * Unused vars * Variable name improvement --- src/extensions/paramHelper.ts | 74 ++----------- src/extensions/warnings.ts | 11 -- src/test/extensions/paramHelper.spec.ts | 138 +----------------------- 3 files changed, 12 insertions(+), 211 deletions(-) diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 4ab26447699..8231d69ebaf 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -5,17 +5,16 @@ import * as fs from "fs-extra"; import { FirebaseError } from "../error"; import { logger } from "../logger"; import { ExtensionInstance, ExtensionSpec, Param } from "./types"; -import { - getFirebaseProjectParams, - populateDefaultParams, - substituteParams, - validateCommandLineParams, -} from "./extensionsHelper"; +import { getFirebaseProjectParams, substituteParams } from "./extensionsHelper"; import * as askUserForParam from "./askUserForParam"; import { track } from "../track"; import * as env from "../functions/env"; import { cloneDeep } from "../utils"; -import { paramsFlagDeprecationWarning } from "./warnings"; + +const NONINTERACTIVE_ERROR_MESSAGE = + "As of firebase-tools@11, `ext:install`, `ext:update` and `ext:configure` are interactive only commands. " + + "To deploy an extension noninteractively, use an extensions manifest and `firebase deploy --only extensions`. " + + "See https://firebase.google.com/docs/extensions/manifest for more details"; /** * Interface for holding different param values for different environments/configs. @@ -84,8 +83,7 @@ export function getParamsWithCurrentValuesAsDefaults( } /** - * Gets params from the user, either by - * reading the env file passed in the --params command line option + * Gets params from the user * or prompting the user for each param. * @param projectId the id of the project in use * @param paramSpecs a list of params, ie. extensionSpec.params @@ -101,24 +99,8 @@ export async function getParams(args: { reconfiguring?: boolean; }): Promise> { let params: Record; - if (args.nonInteractive && !args.paramsEnvPath) { - const paramsMessage = args.paramSpecs - .map((p) => { - return `\t${p.param}${p.required ? "" : " (Optional)"}`; - }) - .join("\n"); - throw new FirebaseError( - "In non-interactive mode but no `--params` flag found. " + - "To install this extension in non-interactive mode, set `--params` to a path to an .env file" + - " containing values for this extension's params:\n" + - paramsMessage - ); - } else if (args.paramsEnvPath) { - paramsFlagDeprecationWarning(); - params = getParamsFromFile({ - paramSpecs: args.paramSpecs, - paramsEnvPath: args.paramsEnvPath, - }); + if (args.nonInteractive) { + throw new FirebaseError(NONINTERACTIVE_ERROR_MESSAGE); } else { const firebaseProjectParams = await getFirebaseProjectParams(args.projectId); params = await askUserForParam.ask({ @@ -144,23 +126,8 @@ export async function getParamsForUpdate(args: { instanceId: string; }): Promise> { let params: Record; - if (args.nonInteractive && !args.paramsEnvPath) { - const paramsMessage = args.newSpec.params - .map((p) => { - return `\t${p.param}${p.required ? "" : " (Optional)"}`; - }) - .join("\n"); - throw new FirebaseError( - "In non-interactive mode but no `--params` flag found. " + - "To update this extension in non-interactive mode, set `--params` to a path to an .env file" + - " containing values for this extension's params:\n" + - paramsMessage - ); - } else if (args.paramsEnvPath) { - params = getParamsFromFile({ - paramSpecs: args.newSpec.params, - paramsEnvPath: args.paramsEnvPath, - }); + if (args.nonInteractive) { + throw new FirebaseError(NONINTERACTIVE_ERROR_MESSAGE); } else { params = await promptForNewParams({ spec: args.spec, @@ -234,25 +201,6 @@ export async function promptForNewParams(args: { return newParamBindingOptions; } -function getParamsFromFile(args: { - paramSpecs: Param[]; - paramsEnvPath: string; -}): Record { - let envParams; - try { - envParams = readEnvFile(args.paramsEnvPath); - void track("Extension Env File", "Present"); - } catch (err: any) { - void track("Extension Env File", "Invalid"); - throw new FirebaseError(`Error reading env file: ${err.message}\n`, { original: err }); - } - const params = populateDefaultParams(envParams, args.paramSpecs); - validateCommandLineParams(params, args.paramSpecs); - logger.info(`Using param values from ${args.paramsEnvPath}`); - - return buildBindingOptionsWithBaseValue(params); -} - export function readEnvFile(envPath: string): Record { const buf = fs.readFileSync(path.resolve(envPath), "utf8"); const result = env.parse(buf.toString().trim()); diff --git a/src/extensions/warnings.ts b/src/extensions/warnings.ts index 4561e90852b..2d347b0ee69 100644 --- a/src/extensions/warnings.ts +++ b/src/extensions/warnings.ts @@ -122,17 +122,6 @@ export async function displayWarningsForDeploy(instancesToCreate: InstanceSpec[] return experimental.length > 0 || eapExtensions.length > 0; } -/** - * paramsFlagDeprecationWarning displays a warning about the future depreaction of the --params flag. - */ -export function paramsFlagDeprecationWarning() { - logger.warn( - "The --params flag is deprecated and will be removed in firebase-tools@11. " + - "Instead, use an extensions manifest and `firebase deploy --only extensions` to deploy extensions noninteractively. " + - "See https://firebase.google.com/docs/extensions/manifest for more details" - ); -} - export function outOfBandChangesWarning(instanceIds: string[]) { logger.warn( "The following instances may have been changed in the Firebase console or by another machine since the last deploy from this machine.\n\t" + diff --git a/src/test/extensions/paramHelper.spec.ts b/src/test/extensions/paramHelper.spec.ts index 749567abbc3..44adf6f0ea4 100644 --- a/src/test/extensions/paramHelper.spec.ts +++ b/src/test/extensions/paramHelper.spec.ts @@ -3,11 +3,9 @@ import * as sinon from "sinon"; import * as fs from "fs-extra"; import { FirebaseError } from "../../error"; -import { logger } from "../../logger"; import { ExtensionInstance, Param, ParamType } from "../../extensions/types"; import * as extensionsHelper from "../../extensions/extensionsHelper"; import * as paramHelper from "../../extensions/paramHelper"; -import * as env from "../../functions/env"; import * as prompt from "../../prompt"; import { cloneDeep } from "../../utils"; @@ -114,153 +112,19 @@ describe("paramHelper", () => { }); describe("getParams", () => { - let envStub: sinon.SinonStub; let promptStub: sinon.SinonStub; - let loggerSpy: sinon.SinonSpy; beforeEach(() => { sinon.stub(fs, "readFileSync").returns(""); - envStub = sinon.stub(env, "parse"); sinon.stub(extensionsHelper, "getFirebaseProjectParams").resolves({ PROJECT_ID }); promptStub = sinon.stub(prompt, "promptOnce").resolves("user input"); - loggerSpy = sinon.spy(logger, "warn"); }); afterEach(() => { sinon.restore(); }); - it("should read params from envFilePath if it is provided and is valid", async () => { - envStub.returns({ - envs: { - A_PARAMETER: "aValue", - ANOTHER_PARAMETER: "value", - }, - errors: [], - }); - - const params = await paramHelper.getParams({ - projectId: PROJECT_ID, - paramSpecs: TEST_PARAMS, - nonInteractive: false, - paramsEnvPath: "./a/path/to/a/file.env", - instanceId: INSTANCE_ID, - }); - - expect(params).to.eql({ - A_PARAMETER: { baseValue: "aValue" }, - ANOTHER_PARAMETER: { baseValue: "value" }, - }); - }); - - it("should return the defaults for params that are not in envFilePath", async () => { - envStub.returns({ - envs: { - A_PARAMETER: "aValue", - }, - errors: [], - }); - - const params = await paramHelper.getParams({ - projectId: PROJECT_ID, - paramSpecs: TEST_PARAMS, - nonInteractive: false, - paramsEnvPath: "./a/path/to/a/file.env", - instanceId: INSTANCE_ID, - }); - - expect(params).to.eql({ - A_PARAMETER: { baseValue: "aValue" }, - ANOTHER_PARAMETER: { baseValue: "default" }, - }); - }); - - it("should omit optional params that are not in envFilePath", async () => { - envStub.returns({ - envs: { - A_PARAMETER: "aValue", - }, - errors: [], - }); - - const params = await paramHelper.getParams({ - projectId: PROJECT_ID, - paramSpecs: TEST_PARAMS_3, - nonInteractive: false, - paramsEnvPath: "./a/path/to/a/file.env", - instanceId: INSTANCE_ID, - }); - - expect(params).to.eql({ - A_PARAMETER: { baseValue: "aValue" }, - }); - }); - - it("should throw if a required param without a default is not in envFilePath", async () => { - envStub.returns({ - envs: { - ANOTHER_PARAMETER: "aValue", - }, - errors: [], - }); - - await expect( - paramHelper.getParams({ - projectId: PROJECT_ID, - paramSpecs: TEST_PARAMS, - nonInteractive: false, - paramsEnvPath: "./a/path/to/a/file.env", - instanceId: INSTANCE_ID, - }) - ).to.be.rejectedWith( - FirebaseError, - "A_PARAMETER has not been set in the given params file and there is no default available. " + - "Please set this variable before installing again." - ); - }); - - it("should warn about extra params provided in the env file", async () => { - envStub.returns({ - envs: { - A_PARAMETER: "aValue", - ANOTHER_PARAMETER: "default", - A_THIRD_PARAMETER: "aValue", - A_FOURTH_PARAMETER: "default", - }, - errors: [], - }); - await paramHelper.getParams({ - projectId: PROJECT_ID, - paramSpecs: TEST_PARAMS, - nonInteractive: false, - paramsEnvPath: "./a/path/to/a/file.env", - instanceId: INSTANCE_ID, - }); - - expect(loggerSpy).to.have.been.calledWith( - "Warning: The following params were specified in your env file but" + - " do not exist in the extension spec: A_THIRD_PARAMETER, A_FOURTH_PARAMETER." - ); - }); - - it("should throw FirebaseError if an invalid envFilePath is provided", async () => { - envStub.returns({ - envs: {}, - errors: ["An error"], - }); - - await expect( - paramHelper.getParams({ - projectId: PROJECT_ID, - paramSpecs: TEST_PARAMS, - nonInteractive: false, - paramsEnvPath: "./a/path/to/a/file.env", - instanceId: INSTANCE_ID, - }) - ).to.be.rejectedWith(FirebaseError, "Error reading env file"); - }); - - it("should prompt the user for params if no env file is provided", async () => { + it("should prompt the user for params", async () => { const params = await paramHelper.getParams({ projectId: PROJECT_ID, paramSpecs: TEST_PARAMS, From 18f1d810c7300900005d03c902b81105be518c1a Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:08:11 -0400 Subject: [PATCH 0824/1699] Fix function deploy retry after quota exceeded bug and increase backoff (#5601) * fix retry bug, adjust throttler options * update changelog --- CHANGELOG.md | 1 + src/deploy/functions/release/fabricator.ts | 2 ++ src/deploy/functions/release/index.ts | 10 +++++----- src/gcp/cloudfunctions.ts | 2 ++ src/gcp/cloudfunctionsv2.ts | 1 + src/gcp/run.ts | 3 +++ 6 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b92ff4089b..b4a051124af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ +- Fix function deploy retry after quota exceeded bug and increase backoff. (#5601) - Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index f3346d2b3a5..12f12982397 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -298,6 +298,7 @@ export class Fabricator { } throw new FirebaseError("Unexpected error creating Pub/Sub topic", { original: err as Error, + status: err.status, }); } }) @@ -336,6 +337,7 @@ export class Fabricator { } throw new FirebaseError("Unexpected error creating Eventarc channel", { original: err as Error, + status: err.status, }); } }) diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index f0cbef1b72c..08c46191304 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -59,16 +59,16 @@ export async function release( } } - const functionExecutor: executor.QueueExecutor = new executor.QueueExecutor({ + const throttlerOptions = { retries: 30, backoff: 20000, concurrency: 40, - maxBackoff: 40000, - }); + maxBackoff: 100000, + }; const fab = new fabricator.Fabricator({ - functionExecutor, - executor: new executor.QueueExecutor({}), + functionExecutor: new executor.QueueExecutor(throttlerOptions), + executor: new executor.QueueExecutor(throttlerOptions), sources: context.sources, appEngineLocation: getAppEngineLocation(context.firebaseConfig), projectNumber: options.projectNumber || (await getProjectNumber(context.projectId)), diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index 06b1f389696..af2a1de2985 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -165,6 +165,7 @@ function functionsOpLogReject(funcName: string, type: string, err: any): void { } throw new FirebaseError(`Failed to ${type} function ${funcName}`, { original: err, + status: err?.context?.response?.statusCode, context: { function: funcName }, }); } @@ -244,6 +245,7 @@ export async function setIamPolicy(options: IamOptions): Promise { } catch (err: any) { throw new FirebaseError(`Failed to set the IAM Policy on the function ${options.name}`, { original: err, + status: err?.context?.response?.statusCode, }); } } diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index cf924e5715c..6c3942c49c9 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -262,6 +262,7 @@ function functionsOpLogReject(funcName: string, type: string, err: any): void { } throw new FirebaseError(`Failed to ${type} function ${funcName}`, { original: err, + status: err?.context?.response?.statusCode, context: { function: funcName }, }); } diff --git a/src/gcp/run.ts b/src/gcp/run.ts index c54692998c8..28f59e9106f 100644 --- a/src/gcp/run.ts +++ b/src/gcp/run.ts @@ -153,6 +153,7 @@ export async function getService(name: string): Promise { } catch (err: any) { throw new FirebaseError(`Failed to fetch Run service ${name}`, { original: err, + status: err?.context?.response?.statusCode, }); } } @@ -220,6 +221,7 @@ export async function replaceService(name: string, service: Service): Promise Date: Wed, 15 Mar 2023 13:30:40 -0700 Subject: [PATCH 0825/1699] Disable scheduled run of fn integration test. (#5606) --- .github/workflows/functions.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/functions.yaml b/.github/workflows/functions.yaml index 8bc9a57772c..2815ff8aef7 100644 --- a/.github/workflows/functions.yaml +++ b/.github/workflows/functions.yaml @@ -4,10 +4,10 @@ name: Functions deploy test # https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow on: workflow_dispatch: - schedule: - # Run the action every 2 hours. - # * is a special character in YAML so you have to quote this string - - cron: "0 */2 * * *" +# schedule: +# # Run the action every 2 hours. +# # * is a special character in YAML so you have to quote this string +# - cron: "0 */2 * * *" permissions: contents: read From dc5596db15769017efcf6099cbf0dcdb5a4d243b Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Wed, 15 Mar 2023 23:16:59 -0400 Subject: [PATCH 0826/1699] Remove call to Cloud Run API and set CPU & concurrency in GCF API instead (#5605) * remove set run traits, set in gcf api instead * simplify cpu conversion, since endpoint field is already guaranteed to be a number * added note to only convert cpu and concurrency fields for 2nd gen functions later * update changelog * remove tests * add unit test accidentally deleted --- CHANGELOG.md | 1 + src/deploy/functions/release/fabricator.ts | 40 ---- src/gcp/cloudfunctionsv2.ts | 15 +- .../functions/release/fabricator.spec.ts | 206 ------------------ src/test/gcp/cloudfunctionsv2.spec.ts | 21 ++ 5 files changed, 36 insertions(+), 247 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a051124af..13d5c019e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ +- Remove call to Cloud Run API and set CPU & concurrency in GCF API instead. (#5605) - Fix function deploy retry after quota exceeded bug and increase backoff. (#5601) - Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 12f12982397..409b9cc2e4b 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -394,26 +394,6 @@ export class Fabricator { .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker)) .catch(rethrowAs(endpoint, "set invoker")); } - - const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; - - // "CustomCPU" here means "not the CPU that GCF gives us". GCF automatically - // sets the CPU for a Run service based on the RAM settings. The value GCF - // uses is memoryToGen1Cpu. - const hasCustomCPU = endpoint.cpu !== backend.memoryToGen1Cpu(mem); - // I don't love editing an input in this function, but the code gets ugly - // otherwise. It's nice to not resolve concurrency in the prepare stage - // because it helps us know whether we should call setRunTraits on update. - if (!endpoint.concurrency) { - endpoint.concurrency = - (endpoint.cpu as number) >= backend.MIN_CPU_FOR_CONCURRENCY - ? backend.DEFAULT_CONCURRENCY - : 1; - } - const hasConcurrency = endpoint.concurrency !== 1; - if (hasCustomCPU || hasConcurrency) { - await this.setRunTraits(serviceName, endpoint); - } } async updateV1Function(endpoint: backend.Endpoint, scraper: SourceTokenScraper): Promise { @@ -504,26 +484,6 @@ export class Fabricator { .run(() => run.setInvokerUpdate(endpoint.project, serviceName, invoker!)) .catch(rethrowAs(endpoint, "set invoker")); } - - // Ideally we'd avoid a read of the Cloud Run service. We need to read if we - // believe a setting (CPU or concurrency) has changed because the user - // changed their mind or if GCF has stamped over the user's choice (we're - // using custom VM shapes). - const hasCustomCPU = - endpoint.cpu !== - backend.memoryToGen1Cpu(endpoint.availableMemoryMb || backend.DEFAULT_MEMORY); - const explicitConcurrency = endpoint.concurrency !== undefined; - if (hasCustomCPU || explicitConcurrency) { - // GCF may have stamped over the CPU to be <1 which would reset concurrency. - // We may have to reapply defaults. - if (endpoint.concurrency === undefined) { - endpoint.concurrency = - (endpoint.cpu as number) < backend.MIN_CPU_FOR_CONCURRENCY - ? 1 - : backend.DEFAULT_CONCURRENCY; - } - await this.setRunTraits(serviceName, endpoint); - } } async deleteV1Function(endpoint: backend.Endpoint): Promise { diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 6c3942c49c9..7884cf520ac 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -115,10 +115,12 @@ export interface ServiceConfig { timeoutSeconds?: number | null; availableMemory?: string | null; + availableCpu?: string | null; environmentVariables?: Record | null; secretEnvironmentVariables?: SecretEnvVar[] | null; maxInstanceCount?: number | null; minInstanceCount?: number | null; + maxInstanceRequestConcurrency?: number | null; vpcConnector?: string | null; vpcConnectorEgressSettings?: VpcConnectorEgressSettings | null; ingressSettings?: IngressSettings | null; @@ -467,10 +469,21 @@ export function functionFromEndpoint( ); // Memory must be set because the default value of GCF gen 2 is Megabytes and // we use mebibytes - const mem: number = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; + const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY; gcfFunction.serviceConfig.availableMemory = mem > 1024 ? `${mem / 1024}Gi` : `${mem}Mi`; proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances"); proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances"); + // N.B. only convert CPU and concurrency fields for 2nd gen functions, once we + // eventually use the v2 API to configure both 1st and 2nd gen functions) + proto.renameIfPresent( + gcfFunction.serviceConfig, + endpoint, + "maxInstanceRequestConcurrency", + "concurrency" + ); + proto.convertIfPresent(gcfFunction.serviceConfig, endpoint, "availableCpu", "cpu", (cpu) => { + return String(cpu); + }); if (endpoint.vpc) { proto.renameIfPresent(gcfFunction.serviceConfig, endpoint.vpc, "vpcConnector", "connector"); diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index 1b34776d040..f5a26a0633e 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -433,13 +433,6 @@ describe("Fabricator", () => { }); describe("createV2Function", () => { - let setRunTraits: sinon.SinonStub; - - beforeEach(() => { - setRunTraits = sinon.stub(fab, "setRunTraits"); - setRunTraits.resolves(); - }); - it("handles topics that already exist", async () => { pubsub.createTopic.callsFake(() => { const err = new Error("Already exists"); @@ -631,64 +624,6 @@ describe("Fabricator", () => { ); }); - it("sets run traits by default for large functions", async () => { - gcfv2.createFunction.resolves({ name: "op", done: false }); - poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); - run.setInvokerCreate.resolves(); - const ep = endpoint( - { httpsTrigger: {} }, - { platform: "gcfv2", availableMemoryMb: 256, cpu: 1 } - ); - - await fab.createV2Function(ep); - expect(setRunTraits).to.have.been.calledWith("service", ep); - }); - - it("sets run traits for functions with concurrency", async () => { - gcfv2.createFunction.resolves({ name: "op", done: false }); - poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); - run.setInvokerCreate.resolves(); - const ep = endpoint( - { httpsTrigger: {} }, - { platform: "gcfv2", availableMemoryMb: 2048, cpu: 1, concurrency: 80 } - ); - - await fab.createV2Function(ep); - expect(setRunTraits).to.have.been.calledWith("service", ep); - }); - - it("does not set concurrency by default for small functions", async () => { - gcfv2.createFunction.resolves({ name: "op", done: false }); - poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); - run.setInvokerCreate.resolves(); - const ep = endpoint( - { httpsTrigger: {} }, - { - platform: "gcfv2", - availableMemoryMb: 256, - cpu: backend.memoryToGen1Cpu(256), - concurrency: 1, - } - ); - - await fab.createV2Function(ep); - expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["public"]); - expect(setRunTraits).to.not.have.been.called; - }); - - it("sets explicit concurrency", async () => { - gcfv2.createFunction.resolves({ name: "op", done: false }); - poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); - run.setInvokerCreate.resolves(); - const ep = endpoint( - { httpsTrigger: {} }, - { platform: "gcfv2", availableMemoryMb: 2048, cpu: 1, concurrency: 2 } - ); - - await fab.createV2Function(ep); - expect(setRunTraits).to.have.been.calledWith("service", ep); - }); - describe("httpsTrigger", () => { it("sets invoker to public by default", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); @@ -798,13 +733,6 @@ describe("Fabricator", () => { }); describe("updateV2Function", () => { - let setRunTraits: sinon.SinonStub; - - beforeEach(() => { - setRunTraits = sinon.stub(fab, "setRunTraits"); - setRunTraits.rejects(new Error("Unexpected setRunTraits call")); - }); - it("throws on update function failure", async () => { gcfv2.updateFunction.rejects(new Error("Server failure")); @@ -901,27 +829,6 @@ describe("Fabricator", () => { await fab.updateV2Function(ep); expect(run.setInvokerUpdate).to.not.have.been.called; }); - - it("Reapplies customer VM preferences after GCF overwrite", async () => { - setRunTraits.resolves(); - gcfv2.updateFunction.resolves({ name: "op", done: false }); - poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); - run.setInvokerUpdate.resolves(); - const ep = endpoint( - { httpsTrigger: {} }, - { - platform: "gcfv2", - availableMemoryMb: 256, - cpu: 1, - } - ); - - await fab.updateV2Function(ep); - expect(setRunTraits).to.have.been.calledWithMatch("service", { - cpu: 1, - concurrency: backend.DEFAULT_CONCURRENCY, - }); - }); }); describe("deleteV2Function", () => { @@ -938,119 +845,6 @@ describe("Fabricator", () => { }); }); - describe("setRunTraits", () => { - let service: runNS.Service; - beforeEach(() => { - service = { - apiVersion: "serving.knative.dev/v1", - kind: "Service", - metadata: { - name: "service", - namespace: "project", - }, - spec: { - template: { - metadata: { - name: "service", - namespace: "project", - }, - spec: { - containerConcurrency: 1, - containers: [ - { - image: "image", - ports: [ - { - name: "main", - containerPort: 8080, - }, - ], - env: {}, - resources: { - limits: { - memory: "256M", - cpu: "0.1667", - }, - }, - }, - ], - }, - }, - traffic: [], - }, - }; - }); - - it("replaces the service to set concurrency", async () => { - run.getService.resolves(service); - run.updateService.resolves(service); - await fab.setRunTraits( - service.metadata.name, - endpoint( - { httpsTrigger: {} }, - { - cpu: 1, - concurrency: 80, - } - ) - ); - expect(run.updateService).to.have.been.called; - }); - - it("replaces the service to set CPU", async () => { - service.spec.template.spec.containers[0].resources.limits.cpu = (1 / 6).toString(); - run.getService.resolves(service); - run.updateService.resolves(service); - await fab.setRunTraits( - service.metadata.name, - endpoint( - { httpsTrigger: {} }, - { - cpu: 1, - concurrency: 1, - } - ) - ); - expect(run.updateService).to.have.been.called; - }); - - it("doesn't setRunTraits when already at the correct value", async () => { - service.spec.template.spec.containerConcurrency = 80; - service.spec.template.spec.containers[0].resources.limits.cpu = "1"; - run.getService.resolves(service); - - await fab.setRunTraits( - "service", - endpoint( - { - httpsTrigger: {}, - }, - { - cpu: 1, - concurrency: 80, - } - ) - ); - expect(run.updateService).to.not.have.been.called; - }); - - it("wraps errors", async () => { - run.getService.rejects(new Error("Oh noes!")); - - await expect(fab.setRunTraits("service", endpoint())).to.eventually.be.rejectedWith( - reporter.DeploymentError, - "set concurrency" - ); - - run.getService.resolves(service); - run.replaceService.rejects(new Error("read only")); - await expect(fab.setRunTraits("service", endpoint())).to.eventually.be.rejectedWith( - reporter.DeploymentError, - "set concurrency" - ); - }); - }); - describe("upsertScheduleV1", () => { const ep = endpoint({ scheduleTrigger: { diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index 16e05e77844..cc03fa73788 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -347,6 +347,27 @@ describe("cloudfunctionsv2", () => { ).to.deep.equal(complexGcfFunction); }); + it("should correctly convert CPU and concurrency values", () => { + const endpoint: backend.Endpoint = { + ...ENDPOINT, + platform: "gcfv2", + httpsTrigger: {}, + concurrency: 40, + cpu: 2, + }; + const gcfFunction: Omit = { + ...CLOUD_FUNCTION_V2, + serviceConfig: { + ...CLOUD_FUNCTION_V2.serviceConfig, + maxInstanceRequestConcurrency: 40, + availableCpu: "2", + }, + }; + expect( + cloudfunctionsv2.functionFromEndpoint(endpoint, CLOUD_FUNCTION_V2_SOURCE) + ).to.deep.equal(gcfFunction); + }); + it("should export codebase as label", () => { expect( cloudfunctionsv2.functionFromEndpoint( From 542986cc6b62d998a3c4905b85d5be752db480b2 Mon Sep 17 00:00:00 2001 From: Victor Elmir Verchkovski Date: Thu, 16 Mar 2023 12:47:30 -0500 Subject: [PATCH 0827/1699] Firebase CLI Supports Multiple Firestore Databases (#5548) b/267551167 go/add-database-id-to-firebase-cli To support multiple databases in the Firebase CLI, commands should handle an optional databaseId to specify which database it should be applied to. --- CHANGELOG.md | 2 + schema/firebase-config.json | 166 ++++++++++++++++++++----- src/commands/firestore-delete.ts | 26 ++-- src/commands/firestore-indexes-list.ts | 12 +- src/deploy/firestore/deploy.ts | 47 +++---- src/deploy/firestore/prepare.ts | 92 ++++++++++---- src/deploy/firestore/release.ts | 30 +++-- src/emulator/controller.ts | 26 +++- src/firebaseConfig.ts | 20 ++- src/firestore/delete.ts | 17 ++- src/firestore/fsConfig.ts | 78 ++++++++++++ src/firestore/indexes.ts | 37 ++++-- src/firestore/options.ts | 15 +++ src/firestore/util.ts | 22 ++-- src/rulesDeploy.ts | 4 +- src/test/firestore/indexes.spec.ts | 24 ---- src/test/firestore/util.test.ts | 49 ++++++++ 17 files changed, 513 insertions(+), 154 deletions(-) create mode 100644 src/firestore/fsConfig.ts create mode 100644 src/firestore/options.ts create mode 100644 src/test/firestore/util.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d5c019e7e..112042483a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- Adds support for optional `--database` argument in Firestore commands (#5548). +- Adds multiple firestore database targets support in firebase.json (#5548). - Remove call to Cloud Run API and set CPU & concurrency in GCF API instead. (#5605) - Fix function deploy retry after quota exceeded bug and increase backoff. (#5601) - Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) diff --git a/schema/firebase-config.json b/schema/firebase-config.json index e2536dd4f15..cb16905cefe 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -426,42 +426,150 @@ "$ref": "#/definitions/ExtensionsConfig" }, "firestore": { - "additionalProperties": false, - "properties": { - "indexes": { - "type": "string" - }, - "postdeploy": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "database": { + "type": "string" }, - { + "indexes": { "type": "string" - } - ] - }, - "predeploy": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" }, - { + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { "type": "string" } - ] + }, + "type": "object" }, - "rules": { - "type": "string" + { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "database": { + "type": "string" + }, + "indexes": { + "type": "string" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "target" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "database": { + "type": "string" + }, + "indexes": { + "type": "string" + }, + "postdeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "predeploy": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ] + }, + "rules": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + } + ] + }, + "type": "array" } - }, - "type": "object" + ] }, "functions": { "anyOf": [ diff --git a/src/commands/firestore-delete.ts b/src/commands/firestore-delete.ts index ed2e0ce50a3..59201e2c21e 100644 --- a/src/commands/firestore-delete.ts +++ b/src/commands/firestore-delete.ts @@ -8,14 +8,15 @@ import { FirestoreDelete } from "../firestore/delete"; import { promptOnce } from "../prompt"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; +import { FirestoreOptions } from "../firestore/options"; -function getConfirmationMessage(deleteOp: FirestoreDelete, options: any) { +function confirmationMessage(deleteOp: FirestoreDelete, options: FirestoreOptions) { if (options.allCollections) { return ( "You are about to delete " + clc.bold(clc.yellow(clc.underline("THE ENTIRE DATABASE"))) + " for " + - clc.cyan(options.project) + + clc.cyan(deleteOp.getRoot()) + ". Are you sure?" ); } @@ -28,7 +29,7 @@ function getConfirmationMessage(deleteOp: FirestoreDelete, options: any) { clc.cyan(deleteOp.path) + " and all of its subcollections " + " for " + - clc.cyan(options.project) + + clc.cyan(deleteOp.getRoot()) + ". Are you sure?" ); } @@ -38,7 +39,7 @@ function getConfirmationMessage(deleteOp: FirestoreDelete, options: any) { "You are about to delete the document at " + clc.cyan(deleteOp.path) + " for " + - clc.cyan(options.project) + + clc.cyan(deleteOp.getRoot()) + ". Are you sure?" ); } @@ -50,7 +51,7 @@ function getConfirmationMessage(deleteOp: FirestoreDelete, options: any) { clc.cyan(deleteOp.path) + " and all of their subcollections " + " for " + - clc.cyan(options.project) + + clc.cyan(deleteOp.getRoot()) + ". Are you sure?" ); } @@ -60,7 +61,7 @@ function getConfirmationMessage(deleteOp: FirestoreDelete, options: any) { "You are about to delete all documents in the collection at " + clc.cyan(deleteOp.path) + " for " + - clc.cyan(options.project) + + clc.cyan(deleteOp.getRoot()) + ". Are you sure?" ); } @@ -84,18 +85,27 @@ export const command = new Command("firestore:delete [path]") "including all collections and documents. Any other flags or arguments will be ignored." ) .option("-f, --force", "No confirmation. Otherwise, a confirmation prompt will appear.") + .option( + "--database ", + 'Database ID for database to delete from. "(default)" if none is provided.' + ) .before(printNoticeIfEmulated, Emulators.FIRESTORE) .before(requirePermissions, ["datastore.entities.list", "datastore.entities.delete"]) - .action(async (path: string | undefined, options: any) => { + .action(async (path: string | undefined, options: FirestoreOptions) => { // Guarantee path if (!path && !options.allCollections) { return utils.reject("Must specify a path.", { exit: 1 }); } + if (!options.database) { + options.database = "(default)"; + } + const deleteOp = new FirestoreDelete(options.project, path, { recursive: options.recursive, shallow: options.shallow, allCollections: options.allCollections, + databaseId: options.database, }); const confirm = await promptOnce( @@ -103,7 +113,7 @@ export const command = new Command("firestore:delete [path]") type: "confirm", name: "force", default: false, - message: getConfirmationMessage(deleteOp, options), + message: confirmationMessage(deleteOp, options), }, options ); diff --git a/src/commands/firestore-indexes-list.ts b/src/commands/firestore-indexes-list.ts index b0349865160..0cf1c40c78d 100644 --- a/src/commands/firestore-indexes-list.ts +++ b/src/commands/firestore-indexes-list.ts @@ -5,6 +5,7 @@ import { logger } from "../logger"; import { requirePermissions } from "../requirePermissions"; import { Emulators } from "../emulator/types"; import { warnEmulatorNotSupported } from "../emulator/commandUtils"; +import { FirestoreOptions } from "../firestore/options"; export const command = new Command("firestore:indexes") .description("List indexes in your project's Cloud Firestore database.") @@ -13,13 +14,18 @@ export const command = new Command("firestore:indexes") "Pretty print. When not specified the indexes are printed in the " + "JSON specification format." ) + .option( + "--database ", + "Database ID of the firestore database from which to list indexes. (default) if none provided." + ) .before(requirePermissions, ["datastore.indexes.list"]) .before(warnEmulatorNotSupported, Emulators.FIRESTORE) - .action(async (options: any) => { + .action(async (options: FirestoreOptions) => { const indexApi = new fsi.FirestoreIndexes(); - const indexes = await indexApi.listIndexes(options.project); - const fieldOverrides = await indexApi.listFieldOverrides(options.project); + const databaseId = options.database ?? "(default)"; + const indexes = await indexApi.listIndexes(options.project, databaseId); + const fieldOverrides = await indexApi.listFieldOverrides(options.project, databaseId); const indexSpec = indexApi.makeIndexSpec(indexes, fieldOverrides); diff --git a/src/deploy/firestore/deploy.ts b/src/deploy/firestore/deploy.ts index 080e1a3d761..b4730f43584 100644 --- a/src/deploy/firestore/deploy.ts +++ b/src/deploy/firestore/deploy.ts @@ -1,18 +1,17 @@ -import * as _ from "lodash"; import * as clc from "colorette"; -import { FirebaseError } from "../../error"; import { FirestoreIndexes } from "../../firestore/indexes"; import { logger } from "../../logger"; import utils = require("../../utils"); import { RulesDeploy, RulesetServiceType } from "../../rulesDeploy"; +import { IndexContext } from "./prepare"; /** * Deploys Firestore Rules. * @param context The deploy context. */ async function deployRules(context: any): Promise { - const rulesDeploy: RulesDeploy = _.get(context, "firestore.rulesDeploy"); + const rulesDeploy: RulesDeploy = context?.firestore?.rulesDeploy; if (!context.firestoreRules || !rulesDeploy) { return; } @@ -28,26 +27,32 @@ async function deployIndexes(context: any, options: any): Promise { if (!context.firestoreIndexes) { return; } + const indexesContext: IndexContext[] = context?.firestore?.indexes; - const indexesFileName = _.get(context, "firestore.indexes.name"); - const indexesSrc = _.get(context, "firestore.indexes.content"); - if (!indexesSrc) { - logger.debug("No Firestore indexes present."); - return; - } - - const indexes = indexesSrc.indexes; - if (!indexes) { - throw new FirebaseError(`Index file must contain an "indexes" property.`); - } - - const fieldOverrides = indexesSrc.fieldOverrides || []; + utils.logBullet(clc.bold(clc.cyan("firestore: ")) + "deploying indexes..."); + const firestoreIndexes = new FirestoreIndexes(); + await Promise.all( + indexesContext.map(async (indexContext: IndexContext): Promise => { + const { databaseId, indexesFileName, indexesRawSpec } = indexContext; + if (!indexesRawSpec) { + logger.debug(`No Firestore indexes present for ${databaseId} database.`); + return; + } + const indexes = indexesRawSpec.indexes; + if (!indexes) { + logger.error(`${databaseId} database index file must contain "indexes" property.`); + return; + } + const fieldOverrides = indexesRawSpec.fieldOverrides || []; - await new FirestoreIndexes().deploy(options, indexes, fieldOverrides); - utils.logSuccess( - `${clc.bold(clc.green("firestore:"))} deployed indexes in ${clc.bold( - indexesFileName - )} successfully` + await firestoreIndexes.deploy(options, indexes, fieldOverrides, databaseId).then(() => { + utils.logSuccess( + `${clc.bold(clc.green("firestore:"))} deployed indexes in ${clc.bold( + indexesFileName + )} successfully for ${databaseId} database` + ); + }); + }) ); } diff --git a/src/deploy/firestore/prepare.ts b/src/deploy/firestore/prepare.ts index bb8585e4110..d3cdd8bcfd4 100644 --- a/src/deploy/firestore/prepare.ts +++ b/src/deploy/firestore/prepare.ts @@ -1,49 +1,67 @@ -import * as _ from "lodash"; import * as clc from "colorette"; import { loadCJSON } from "../../loadCJSON"; import { RulesDeploy, RulesetServiceType } from "../../rulesDeploy"; import utils = require("../../utils"); import { Options } from "../../options"; +import * as fsConfig from "../../firestore/fsConfig"; + +export interface RulesContext { + databaseId: string; + rulesFile: string; +} + +export interface IndexContext { + databaseId: string; + indexesFileName: string; + indexesRawSpec: any; // could be the old v1beta1 indexes spec or the new v1/v1 format +} /** * Prepares Firestore Rules deploys. * @param context The deploy context. - * @param options The CLI options object. + * @param rulesDeploy The object encapsulating logic for deploying rules. + * @param databaseId The id of the database rulesFile corresponds to. + * @param rulesFile File name for the Firestore rules to be deployed. */ -async function prepareRules(context: any, options: Options): Promise { - const rulesFile = options.config.src.firestore?.rules; - - if (context.firestoreRules && rulesFile) { - const rulesDeploy = new RulesDeploy(options, RulesetServiceType.CLOUD_FIRESTORE); - _.set(context, "firestore.rulesDeploy", rulesDeploy); - rulesDeploy.addFile(rulesFile); - await rulesDeploy.compile(); - } +function prepareRules( + context: any, + rulesDeploy: RulesDeploy, + databaseId: string, + rulesFile: string +): void { + rulesDeploy.addFile(rulesFile); + context.firestore.rules.push({ + databaseId, + rulesFile, + } as RulesContext); } + /** * Prepares Firestore Indexes deploys. * @param context The deploy context. * @param options The CLI options object. + * @param databaseId The id of the database indexesFileName corresponds to. + * @param indexesFileName File name for the index configs to be parsed from. */ -function prepareIndexes(context: any, options: Options): void { - if (!context.firestoreIndexes || !options.config.src.firestore?.indexes) { - return; - } - - const indexesFileName = options.config.src.firestore.indexes; +function prepareIndexes( + context: any, + options: Options, + databaseId: string, + indexesFileName: string +): void { const indexesPath = options.config.path(indexesFileName); - const parsedSrc = loadCJSON(indexesPath); + const indexesRawSpec = loadCJSON(indexesPath); utils.logBullet( `${clc.bold(clc.cyan("firestore:"))} reading indexes from ${clc.bold(indexesFileName)}...` ); - context.firestore = context.firestore || {}; - context.firestore.indexes = { - name: indexesFileName, - content: parsedSrc, - }; + context.firestore.indexes.push({ + databaseId, + indexesFileName, + indexesRawSpec, + } as IndexContext); } /** @@ -65,6 +83,30 @@ export default async function (context: any, options: any): Promise { context.firestoreRules = true; } - prepareIndexes(context, options); - await prepareRules(context, options); + const firestoreConfigs: fsConfig.ParsedFirestoreConfig[] = fsConfig.getFirestoreConfig( + context.projectId, + options + ); + if (!firestoreConfigs || firestoreConfigs.length === 0) { + return; + } + + context.firestore = context.firestore || {}; + context.firestore.indexes = []; + context.firestore.rules = []; + const rulesDeploy: RulesDeploy = new RulesDeploy(options, RulesetServiceType.CLOUD_FIRESTORE); + context.firestore.rulesDeploy = rulesDeploy; + + for (const firestoreConfig of firestoreConfigs) { + if (firestoreConfig.indexes) { + prepareIndexes(context, options, firestoreConfig.database, firestoreConfig.indexes); + } + if (firestoreConfig.rules) { + prepareRules(context, rulesDeploy, firestoreConfig.database, firestoreConfig.rules); + } + } + + if (context.firestore.rules.length > 0) { + await rulesDeploy.compile(); + } } diff --git a/src/deploy/firestore/release.ts b/src/deploy/firestore/release.ts index 36087d3b39e..d5b59967746 100644 --- a/src/deploy/firestore/release.ts +++ b/src/deploy/firestore/release.ts @@ -1,8 +1,7 @@ -import * as _ from "lodash"; - import { RulesDeploy, RulesetServiceType } from "../../rulesDeploy"; +import { logger } from "../../logger"; import { Options } from "../../options"; -import { FirebaseError } from "../../error"; +import { RulesContext } from "./prepare"; /** * Releases Firestore rules. @@ -10,16 +9,25 @@ import { FirebaseError } from "../../error"; * @param options The CLI options object. */ export default async function (context: any, options: Options): Promise { - const rulesDeploy: RulesDeploy = _.get(context, "firestore.rulesDeploy"); + const rulesDeploy: RulesDeploy = context?.firestore?.rulesDeploy; if (!context.firestoreRules || !rulesDeploy) { return; } - const rulesFile = options.config.src.firestore?.rules; - if (!rulesFile) { - throw new FirebaseError( - `Invalid firestore config: ${JSON.stringify(options.config.src.firestore)}` - ); - } - await rulesDeploy.release(rulesFile, RulesetServiceType.CLOUD_FIRESTORE); + const rulesContext: RulesContext[] = context?.firestore?.rules; + await Promise.all( + rulesContext.map(async (ruleContext: RulesContext): Promise => { + const databaseId = ruleContext.databaseId; + const rulesFile = ruleContext.rulesFile; + if (!rulesFile) { + logger.error( + `Invalid firestore config for ${databaseId} database: ${JSON.stringify( + options.config.src.firestore + )}` + ); + return; + } + return rulesDeploy.release(rulesFile, RulesetServiceType.CLOUD_FIRESTORE, databaseId); + }) + ); } diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index b2bb0a91ee4..f5ec217362a 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -1,6 +1,7 @@ import * as clc from "colorette"; import * as fs from "fs"; import * as path from "path"; +import * as fsConfig from "../firestore/fsConfig"; import { logger } from "../logger"; import { track, trackEmulator } from "../track"; @@ -594,8 +595,29 @@ export async function startAll( } const config = options.config; - const rulesLocalPath = config.src.firestore?.rules; - let rulesFileFound = false; + // emulator does not support multiple databases yet + // TODO(VicVer09): b/269787702 + let rulesLocalPath; + let rulesFileFound; + const firestoreConfigs: fsConfig.ParsedFirestoreConfig[] = fsConfig.getFirestoreConfig( + projectId, + options + ); + if (!firestoreConfigs) { + firestoreLogger.logLabeled( + "WARN", + "firestore", + `Cloud Firestore config does not exist in firebase.json.` + ); + } else if (firestoreConfigs.length !== 1) { + firestoreLogger.logLabeled( + "WARN", + "firestore", + `Cloud Firestore Emulator does not support multiple databases yet.` + ); + } else if (firestoreConfigs[0].rules) { + rulesLocalPath = firestoreConfigs[0].rules; + } if (rulesLocalPath) { const rules: string = config.path(rulesLocalPath); rulesFileFound = fs.existsSync(rules); diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index 32583495261..b24e30a2d1b 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -29,6 +29,21 @@ type DatabaseMultiple = ({ }> & Deployable)[]; +type FirestoreSingle = { + database?: string; + rules?: string; + indexes?: string; +} & Deployable; + +type FirestoreMultiple = ({ + rules?: string; + indexes?: string; +} & RequireAtLeastOne<{ + database: string; + target: string; +}> & + Deployable)[]; + export type HostingSource = { glob: string } | { source: string } | { regex: string }; type HostingRedirects = HostingSource & { @@ -140,10 +155,7 @@ type StorageMultiple = ({ // Full Configs export type DatabaseConfig = DatabaseSingle | DatabaseMultiple; -export type FirestoreConfig = { - rules?: string; - indexes?: string; -} & Deployable; +export type FirestoreConfig = FirestoreSingle | FirestoreMultiple; export type FunctionConfig = { source?: string; diff --git a/src/firestore/delete.ts b/src/firestore/delete.ts index 0ec36fdd493..cdc72e1dfa3 100644 --- a/src/firestore/delete.ts +++ b/src/firestore/delete.ts @@ -38,6 +38,7 @@ export class FirestoreDelete { private recursive: boolean; private shallow: boolean; private allCollections: boolean; + private databaseId: string; private readBatchSize: number; private maxPendingDeletes: number; @@ -61,13 +62,19 @@ export class FirestoreDelete { constructor( project: string, path: string | undefined, - options: { recursive?: boolean; shallow?: boolean; allCollections?: boolean } + options: { + recursive?: boolean; + shallow?: boolean; + allCollections?: boolean; + databaseId: string; + } ) { this.project = project; this.path = path || ""; this.recursive = Boolean(options.recursive); this.shallow = Boolean(options.shallow); this.allCollections = Boolean(options.allCollections); + this.databaseId = options.databaseId; // Tunable deletion parameters this.readBatchSize = 7500; @@ -79,7 +86,8 @@ export class FirestoreDelete { this.path = this.path.replace(/(^\/+|\/+$)/g, ""); this.allDescendants = this.recursive; - this.root = "projects/" + project + "/databases/(default)/documents"; + + this.root = `projects/${project}/databases/${this.databaseId}/documents`; const segments = this.path.split("/"); this.isDocumentPath = segments.length % 2 === 0; @@ -514,6 +522,7 @@ export class FirestoreDelete { const collectionId = collectionIds[i]; const deleteOp = new FirestoreDelete(this.project, collectionId, { recursive: true, + databaseId: this.databaseId, }); promises.push(deleteOp.execute()); @@ -554,4 +563,8 @@ export class FirestoreDelete { return this.deletePath(); }); } + + public getRoot(): string { + return this.root; + } } diff --git a/src/firestore/fsConfig.ts b/src/firestore/fsConfig.ts new file mode 100644 index 00000000000..cee0c6aad3c --- /dev/null +++ b/src/firestore/fsConfig.ts @@ -0,0 +1,78 @@ +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import { Options } from "../options"; + +export interface ParsedFirestoreConfig { + database: string; + rules?: string; + indexes?: string; +} + +export function getFirestoreConfig(projectId: string, options: Options): ParsedFirestoreConfig[] { + const fsConfig = options.config.src.firestore; + if (fsConfig === undefined) { + return []; + } + + const rc = options.rc; + let allDatabases = !options.only; + const onlyDatabases = new Set(); + if (options.only) { + const split = options.only.split(","); + if (split.includes("firestore")) { + allDatabases = true; + } else { + for (const value of split) { + if (value.startsWith("firestore:")) { + const target = value.split(":")[1]; + onlyDatabases.add(target); + } + } + } + } + + // single DB + if (!Array.isArray(fsConfig)) { + if (fsConfig) { + // databaseId is (default) if none provided + const databaseId = fsConfig.database || `(default)`; + return [{ rules: fsConfig.rules, indexes: fsConfig.indexes, database: databaseId }]; + } else { + logger.debug("Possibly invalid database config: ", JSON.stringify(fsConfig)); + return []; + } + } + + const results: ParsedFirestoreConfig[] = []; + for (const c of fsConfig) { + const { database, target } = c; + if (target) { + if (allDatabases || onlyDatabases.has(target)) { + // Make sure the target exists (this will throw otherwise) + rc.requireTarget(projectId, "firestore", target); + // Get a list of firestore instances the target maps to + const databases = rc.target(projectId, "firestore", target); + for (const database of databases) { + results.push({ database, rules: c.rules, indexes: c.indexes }); + } + onlyDatabases.delete(target); + } + } else if (database) { + if (allDatabases) { + results.push(c as ParsedFirestoreConfig); + } + } else { + throw new FirebaseError('Must supply either "target" or "databaseId" in firestore config'); + } + } + + if (!allDatabases && onlyDatabases.size !== 0) { + throw new FirebaseError( + `Could not find configurations in firebase.json for the following database targets: ${[ + ...onlyDatabases, + ].join(", ")}` + ); + } + + return results; +} diff --git a/src/firestore/indexes.ts b/src/firestore/indexes.ts index 711bd674c2b..bcd5cf09ff5 100644 --- a/src/firestore/indexes.ts +++ b/src/firestore/indexes.ts @@ -26,7 +26,8 @@ export class FirestoreIndexes { async deploy( options: { project: string; nonInteractive: boolean; force: boolean }, indexes: any[], - fieldOverrides: any[] + fieldOverrides: any[], + databaseId: string = "(default)" ): Promise { const spec = this.upgradeOldSpec({ indexes, @@ -39,8 +40,11 @@ export class FirestoreIndexes { const indexesToDeploy: Spec.Index[] = spec.indexes; const fieldOverridesToDeploy: Spec.FieldOverride[] = spec.fieldOverrides; - const existingIndexes: API.Index[] = await this.listIndexes(options.project); - const existingFieldOverrides: API.Field[] = await this.listFieldOverrides(options.project); + const existingIndexes: API.Index[] = await this.listIndexes(options.project, databaseId); + const existingFieldOverrides: API.Field[] = await this.listFieldOverrides( + options.project, + databaseId + ); const indexesToDelete = existingIndexes.filter((index) => { return !indexesToDeploy.some((spec) => this.indexMatchesSpec(index, spec)); @@ -100,7 +104,7 @@ export class FirestoreIndexes { logger.debug(`Skipping existing index: ${JSON.stringify(index)}`); } else { logger.debug(`Creating new index: ${JSON.stringify(index)}`); - await this.createIndex(options.project, index); + await this.createIndex(options.project, index, databaseId); } } @@ -149,7 +153,7 @@ export class FirestoreIndexes { logger.debug(`Skipping existing field override: ${JSON.stringify(field)}`); } else { logger.debug(`Updating field override: ${JSON.stringify(field)}`); - await this.patchField(options.project, field); + await this.patchField(options.project, field, databaseId); } } @@ -168,8 +172,8 @@ export class FirestoreIndexes { * List all indexes that exist on a given project. * @param project the Firebase project id. */ - async listIndexes(project: string): Promise { - const url = `/projects/${project}/databases/(default)/collectionGroups/-/indexes`; + async listIndexes(project: string, databaseId: string = "(default)"): Promise { + const url = `/projects/${project}/databases/${databaseId}/collectionGroups/-/indexes`; const res = await this.apiClient.get<{ indexes?: API.Index[] }>(url); const indexes = res.body.indexes; if (!indexes) { @@ -196,8 +200,11 @@ export class FirestoreIndexes { * List all field configuration overrides defined on the given project. * @param project the Firebase project. */ - async listFieldOverrides(project: string): Promise { - const parent = `projects/${project}/databases/(default)/collectionGroups/-`; + async listFieldOverrides( + project: string, + databaseId: string = "(default)" + ): Promise { + const parent = `projects/${project}/databases/${databaseId}/collectionGroups/-`; const url = `/${parent}/fields?filter=indexConfig.usesAncestorConfig=false OR ttlConfig:*`; const res = await this.apiClient.get<{ fields?: API.Field[] }>(url); @@ -371,8 +378,12 @@ export class FirestoreIndexes { * @param project the Firebase project. * @param spec the new field override specification. */ - async patchField(project: string, spec: Spec.FieldOverride): Promise { - const url = `/projects/${project}/databases/(default)/collectionGroups/${spec.collectionGroup}/fields/${spec.fieldPath}`; + async patchField( + project: string, + spec: Spec.FieldOverride, + databaseId: string = "(default)" + ): Promise { + const url = `/projects/${project}/databases/${databaseId}/collectionGroups/${spec.collectionGroup}/fields/${spec.fieldPath}`; const indexes = spec.indexes.map((index) => { return { @@ -419,8 +430,8 @@ export class FirestoreIndexes { /** * Create a new index on the specified project. */ - createIndex(project: string, index: Spec.Index): Promise { - const url = `/projects/${project}/databases/(default)/collectionGroups/${index.collectionGroup}/indexes`; + createIndex(project: string, index: Spec.Index, databaseId: string = "(default)"): Promise { + const url = `/projects/${project}/databases/${databaseId}/collectionGroups/${index.collectionGroup}/indexes`; return this.apiClient.post(url, { fields: index.fields, queryScope: index.queryScope, diff --git a/src/firestore/options.ts b/src/firestore/options.ts new file mode 100644 index 00000000000..68e7b416691 --- /dev/null +++ b/src/firestore/options.ts @@ -0,0 +1,15 @@ +import { Options } from "../options"; + +/** + * The set of fields that the Firestore commands need from Options. + * It is preferable that all codebases use this technique so that they keep + * strong typing in their codebase but limit the codebase to have less to mock. + */ +export interface FirestoreOptions extends Options { + project: string; + database?: string; + nonInteractive: boolean; + allCollections?: boolean; + shallow?: boolean; + recursive?: boolean; +} diff --git a/src/firestore/util.ts b/src/firestore/util.ts index d084246f4b6..96f342e2edf 100644 --- a/src/firestore/util.ts +++ b/src/firestore/util.ts @@ -2,23 +2,25 @@ import { FirebaseError } from "../error"; interface IndexName { projectId: string; + databaseId: string; collectionGroupId: string; indexId: string; } interface FieldName { projectId: string; + databaseId: string; collectionGroupId: string; fieldPath: string; } -// projects/$PROJECT_ID/databases/(default)/collectionGroups/$COLLECTION_GROUP_ID/indexes/$INDEX_ID +// projects/$PROJECT_ID/databases/$DATABASE_ID/collectionGroups/$COLLECTION_GROUP_ID/indexes/$INDEX_ID const INDEX_NAME_REGEX = - /projects\/([^\/]+?)\/databases\/\(default\)\/collectionGroups\/([^\/]+?)\/indexes\/([^\/]*)/; + /projects\/([^\/]+?)\/databases\/([^\/]+?)\/collectionGroups\/([^\/]+?)\/indexes\/([^\/]*)/; -// projects/$PROJECT_ID/databases/(default)/collectionGroups/$COLLECTION_GROUP_ID/fields/$FIELD_ID +// projects/$PROJECT_ID/databases/$DATABASE_ID/collectionGroups/$COLLECTION_GROUP_ID/fields/$FIELD_ID const FIELD_NAME_REGEX = - /projects\/([^\/]+?)\/databases\/\(default\)\/collectionGroups\/([^\/]+?)\/fields\/([^\/]*)/; + /projects\/([^\/]+?)\/databases\/([^\/]+?)\/collectionGroups\/([^\/]+?)\/fields\/([^\/]*)/; /** * Parse an Index name into useful pieces. @@ -29,14 +31,15 @@ export function parseIndexName(name?: string): IndexName { } const m = name.match(INDEX_NAME_REGEX); - if (!m || m.length < 4) { + if (!m || m.length < 5) { throw new FirebaseError(`Error parsing index name: ${name}`); } return { projectId: m[1], - collectionGroupId: m[2], - indexId: m[3], + databaseId: m[2], + collectionGroupId: m[3], + indexId: m[4], }; } @@ -51,8 +54,9 @@ export function parseFieldName(name: string): FieldName { return { projectId: m[1], - collectionGroupId: m[2], - fieldPath: m[3], + databaseId: m[2], + collectionGroupId: m[3], + fieldPath: m[4], }; } diff --git a/src/rulesDeploy.ts b/src/rulesDeploy.ts index 3d6a5f58f69..5a484844f44 100644 --- a/src/rulesDeploy.ts +++ b/src/rulesDeploy.ts @@ -254,9 +254,7 @@ export class RulesDeploy { await gcp.rules.updateOrCreateRelease( this.options.project, this.rulesetNames[filename], - resourceName === RulesetServiceType.FIREBASE_STORAGE - ? `${resourceName}/${subResourceName}` - : resourceName + subResourceName ? `${resourceName}/${subResourceName}` : resourceName ); utils.logLabeledSuccess( RulesetType[this.type], diff --git a/src/test/firestore/indexes.spec.ts b/src/test/firestore/indexes.spec.ts index ab8a899ddd3..6af149a6a13 100644 --- a/src/test/firestore/indexes.spec.ts +++ b/src/test/firestore/indexes.spec.ts @@ -4,7 +4,6 @@ import { FirebaseError } from "../../error"; import * as API from "../../firestore/indexes-api"; import * as Spec from "../../firestore/indexes-spec"; import * as sort from "../../firestore/indexes-sort"; -import * as util from "../../firestore/util"; const idx = new FirestoreIndexes(); @@ -100,29 +99,6 @@ describe("IndexValidation", () => { }).to.throw(FirebaseError, /Must contain exactly one of "order,arrayConfig"/); }); }); - -describe("IndexNameParsing", () => { - it("should parse an index name correctly", () => { - const name = - "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123/"; - expect(util.parseIndexName(name)).to.eql({ - projectId: "myproject", - collectionGroupId: "collection", - indexId: "abc123", - }); - }); - - it("should parse a field name correctly", () => { - const name = - "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123/"; - expect(util.parseFieldName(name)).to.eql({ - projectId: "myproject", - collectionGroupId: "collection", - fieldPath: "abc123", - }); - }); -}); - describe("IndexSpecMatching", () => { it("should identify a positive index spec match", () => { const apiIndex: API.Index = { diff --git a/src/test/firestore/util.test.ts b/src/test/firestore/util.test.ts new file mode 100644 index 00000000000..5db7635ddb9 --- /dev/null +++ b/src/test/firestore/util.test.ts @@ -0,0 +1,49 @@ +import { expect } from "chai"; + +import * as util from "../../firestore/util"; + +describe("IndexNameParsing", () => { + it("should parse an index name correctly", () => { + const name = + "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123/"; + expect(util.parseIndexName(name)).to.eql({ + projectId: "myproject", + databaseId: "(default)", + collectionGroupId: "collection", + indexId: "abc123", + }); + }); + + it("should parse a field name correctly", () => { + const name = + "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123/"; + expect(util.parseFieldName(name)).to.eql({ + projectId: "myproject", + databaseId: "(default)", + collectionGroupId: "collection", + fieldPath: "abc123", + }); + }); + + it("should parse an index name from a named database correctly", () => { + const name = + "/projects/myproject/databases/named-db/collectionGroups/collection/indexes/abc123/"; + expect(util.parseIndexName(name)).to.eql({ + projectId: "myproject", + databaseId: "named-db", + collectionGroupId: "collection", + indexId: "abc123", + }); + }); + + it("should parse a field name from a named database correctly", () => { + const name = + "/projects/myproject/databases/named-db/collectionGroups/collection/fields/abc123/"; + expect(util.parseFieldName(name)).to.eql({ + projectId: "myproject", + databaseId: "named-db", + collectionGroupId: "collection", + fieldPath: "abc123", + }); + }); +}); From 006ed1173558c851554f6ca5c708ca9902381142 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Thu, 16 Mar 2023 15:52:05 -0400 Subject: [PATCH 0828/1699] Add 2nd gen firestore triggers to firebase deploy (#5592) * add firestore triggers to deploy path * add changelog entry * remove logging * better error message * install npm 8.7 & run linter --- CHANGELOG.md | 1 + src/deploy/functions/services/firestore.ts | 25 ++++++ src/deploy/functions/services/index.ts | 19 ++++- src/functions/events/v2.ts | 10 ++- src/gcp/firestore.ts | 37 +++++++++ .../functions/services/firestore.spec.ts | 78 +++++++++++++++++++ 6 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 src/deploy/functions/services/firestore.ts create mode 100644 src/test/deploy/functions/services/firestore.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 112042483a2..be75717ec41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - Remove call to Cloud Run API and set CPU & concurrency in GCF API instead. (#5605) - Fix function deploy retry after quota exceeded bug and increase backoff. (#5601) - Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) +- Adds 2nd gen firestore triggers to firebase deploy (#5592). diff --git a/src/deploy/functions/services/firestore.ts b/src/deploy/functions/services/firestore.ts new file mode 100644 index 00000000000..d4eb39b45d5 --- /dev/null +++ b/src/deploy/functions/services/firestore.ts @@ -0,0 +1,25 @@ +import * as backend from "../backend"; +import * as firestore from "../../../gcp/firestore"; +import { FirebaseError } from "../../../error"; + +/** + * Sets a firestore event trigger's region to the firestore database region. + * @param endpoint the firestore endpoint + */ +export async function ensureFirestoreTriggerRegion( + endpoint: backend.Endpoint & backend.EventTriggered +): Promise { + const db = await firestore.getDatabase( + endpoint.project, + endpoint.eventTrigger.eventFilters?.database || "(default)" + ); + const dbRegion = db.locationId; + if (!endpoint.eventTrigger.region) { + endpoint.eventTrigger.region = dbRegion; + } + if (endpoint.eventTrigger.region !== dbRegion) { + throw new FirebaseError( + "A firestore trigger location must match the firestore database region." + ); + } +} diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 3009175ca77..af560ab938b 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -7,6 +7,7 @@ import { ensureFirebaseAlertsTriggerRegion } from "./firebaseAlerts"; import { ensureDatabaseTriggerRegion } from "./database"; import { ensureRemoteConfigTriggerRegion } from "./remoteConfig"; import { ensureTestLabTriggerRegion } from "./testLab"; +import { ensureFirestoreTriggerRegion } from "./firestore"; /** A standard void No Op */ export const noop = (): Promise => Promise.resolve(); @@ -23,7 +24,8 @@ export type Name = | "authblocking" | "database" | "remoteconfig" - | "testlab"; + | "testlab" + | "firestore"; /** A service interface for the underlying GCP event services */ export interface Service { @@ -117,6 +119,17 @@ const testLabService: Service = { unregisterTrigger: noop, }; +/** A firestore service object */ +const firestoreService: Service = { + name: "firestore", + api: "firestore.googleapis.com", + requiredProjectBindings: noopProjectBindings, + ensureTriggerRegion: ensureFirestoreTriggerRegion, + validateTrigger: noop, + registerTrigger: noop, + unregisterTrigger: noop, +}; + /** Mapping from event type string to service object */ const EVENT_SERVICE_MAPPING: Record = { "google.cloud.pubsub.topic.v1.messagePublished": pubSubService, @@ -133,6 +146,10 @@ const EVENT_SERVICE_MAPPING: Record = { "google.firebase.database.ref.v1.deleted": databaseService, "google.firebase.remoteconfig.remoteConfig.v1.updated": remoteConfigService, "google.firebase.testlab.testMatrix.v1.completed": testLabService, + "google.cloud.firestore.document.v1.written": firestoreService, + "google.cloud.firestore.document.v1.created": firestoreService, + "google.cloud.firestore.document.v1.updated": firestoreService, + "google.cloud.firestore.document.v1.deleted": firestoreService, }; /** diff --git a/src/functions/events/v2.ts b/src/functions/events/v2.ts index 0298210e261..98433c0a1e3 100644 --- a/src/functions/events/v2.ts +++ b/src/functions/events/v2.ts @@ -20,10 +20,18 @@ export const REMOTE_CONFIG_EVENT = "google.firebase.remoteconfig.remoteConfig.v1 export const TEST_LAB_EVENT = "google.firebase.testlab.testMatrix.v1.completed"; +export const FIRESTORE_EVENTS = [ + "google.cloud.firestore.document.v1.written", + "google.cloud.firestore.document.v1.created", + "google.cloud.firestore.document.v1.updated", + "google.cloud.firestore.document.v1.deleted", +] as const; + export type Event = | typeof PUBSUB_PUBLISH_EVENT | (typeof STORAGE_EVENTS)[number] | typeof FIREBASE_ALERTS_PUBLISH_EVENT | (typeof DATABASE_EVENTS)[number] | typeof REMOTE_CONFIG_EVENT - | typeof TEST_LAB_EVENT; + | typeof TEST_LAB_EVENT + | (typeof FIRESTORE_EVENTS)[number]; diff --git a/src/gcp/firestore.ts b/src/gcp/firestore.ts index 6e20e385f99..2d3a3d7b82b 100644 --- a/src/gcp/firestore.ts +++ b/src/gcp/firestore.ts @@ -1,5 +1,6 @@ import { firestoreOriginOrEmulator } from "../api"; import { Client } from "../apiv2"; +import { logger } from "../logger"; const apiClient = new Client({ auth: true, @@ -7,6 +8,42 @@ const apiClient = new Client({ urlPrefix: firestoreOriginOrEmulator, }); +interface Database { + name: string; + uid: string; + createTime: string; + updateTime: string; + locationId: string; + type: "DATABASE_TYPE_UNSPECIFIED" | "FIRESTORE_NATIVE" | "DATASTORE_MODE"; + concurrencyMode: + | "CONCURRENCY_MODE_UNSPECIFIED" + | "OPTIMISTIC" + | "PESSIMISTIC" + | "OPTIMISTIC_WITH_ENTITY_GROUPS"; + appEngineIntegrationMode: "APP_ENGINE_INTEGRATION_MODE_UNSPECIFIED" | "ENABLED" | "DISABLED"; + keyPrefix: string; + etag: string; +} + +/** + * Get a firebase database instance. + * + * @param {string} project the Google Cloud project + * @param {string} database the Firestore database name + */ +export async function getDatabase(project: string, database: string): Promise { + const url = `projects/${project}/databases/${database}`; + try { + const resp = await apiClient.get(url); + return resp.body; + } catch (err: unknown) { + logger.info( + `There was an error retrieving the Firestore database. Currently, the database id is set to ${database}, make sure it exists.` + ); + throw err; + } +} + /** * List all collection IDs. * diff --git a/src/test/deploy/functions/services/firestore.spec.ts b/src/test/deploy/functions/services/firestore.spec.ts new file mode 100644 index 00000000000..71331b81d0f --- /dev/null +++ b/src/test/deploy/functions/services/firestore.spec.ts @@ -0,0 +1,78 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import { ensureFirestoreTriggerRegion } from "../../../../deploy/functions/services/firestore"; +import * as firestore from "../../../../gcp/firestore"; + +const projectNumber = "123456789"; + +const databaseResp = { + name: "projects/123456789/databases/(default)", + uid: "f1483bb9-dec9-422f-b786-906e32627426", + createTime: "2021-06-29T13:40:07.183Z", + updateTime: "2022-02-06T09:21:27.239176Z", + locationId: "nam5", + type: "FIRESTORE_NATIVE", + concurrencyMode: "PESSIMISTIC", + appEngineIntegrationMode: "ENABLED", + keyPrefix: "s", + deleteProtectionState: "DELETE_PROTECTION_DISABLED", + etag: "IJbigIfZ2/0CMI75lMHa2f0C", +}; + +describe("ensureFirestoreTriggerRegion", () => { + let firestoreStub: sinon.SinonStub; + + beforeEach(() => { + firestoreStub = sinon + .stub(firestore, "getDatabase") + .throws("unexpected call to firestore.getDatabase"); + }); + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("should throw an error if the trigger region is different than the firestore region", async () => { + firestoreStub.resolves(databaseResp); + const ep: any = { + project: projectNumber, + eventTrigger: { + eventFilters: { database: "(default)" }, + region: "us-east1", + }, + }; + + await expect(ensureFirestoreTriggerRegion(ep)).to.be.rejectedWith( + "A firestore trigger location must match the firestore database region." + ); + }); + + it("should not throw if the trigger region is not set", async () => { + firestoreStub.resolves(databaseResp); + const ep: any = { + project: projectNumber, + eventTrigger: { + eventFilters: { database: "(default)" }, + }, + }; + + await ensureFirestoreTriggerRegion(ep); + + expect(ep.eventTrigger.region).to.eq("nam5"); + }); + + it("should not throw if the trigger region is set correctly", async () => { + firestoreStub.resolves(databaseResp); + const ep: any = { + project: projectNumber, + eventTrigger: { + eventFilters: { database: "(default)" }, + region: "nam5", + }, + }; + + await ensureFirestoreTriggerRegion(ep); + + expect(ep.eventTrigger.region).to.eq("nam5"); + }); +}); From 0ed8abf0d31efb100a16443f5cbd8328f932801c Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 16 Mar 2023 16:02:12 -0700 Subject: [PATCH 0829/1699] Replace import assignments (#5603) --- src/commands/apps-android-sha-list.ts | 2 +- src/commands/apps-list.ts | 2 +- src/commands/database-instances-list.ts | 2 +- src/commands/experiments-list.ts | 2 +- src/commands/ext-configure.ts | 2 +- src/commands/ext-dev-init.ts | 2 +- src/commands/ext-dev-list.ts | 2 +- src/commands/ext-dev-publish.ts | 2 +- src/commands/ext-dev-usage.ts | 2 +- src/commands/ext-info.ts | 2 +- src/commands/ext-install.ts | 2 +- src/commands/ext-uninstall.ts | 2 +- src/commands/ext-update.ts | 2 +- src/commands/functions-list.ts | 2 +- src/commands/functions-secrets-get.ts | 2 +- src/commands/hosting-channel-list.ts | 2 +- src/commands/hosting-sites-get.ts | 2 +- src/commands/hosting-sites-list.ts | 2 +- src/commands/projects-list.ts | 2 +- src/commands/remoteconfig-get.ts | 4 ++-- src/commands/remoteconfig-versions-list.ts | 4 ++-- src/emulator/auth/server.ts | 2 +- src/emulator/commandUtils.ts | 2 +- src/emulator/extensionsEmulator.ts | 2 +- src/emulator/storage/index.ts | 2 +- src/extensions/askUserForConsent.ts | 2 +- src/extensions/billingMigrationHelper.ts | 2 +- src/extensions/change-log.ts | 4 ++-- src/extensions/displayExtensionInfo.ts | 2 +- src/extensions/listExtensions.ts | 2 +- src/init/features/database.ts | 2 +- src/init/features/firestore/indexes.ts | 2 +- src/init/features/firestore/rules.ts | 2 +- src/profileReport.ts | 4 ++-- src/test/apiv2.spec.ts | 2 +- src/test/emulators/auth/setup.ts | 2 +- 36 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/commands/apps-android-sha-list.ts b/src/commands/apps-android-sha-list.ts index 75a9d39776b..8f5cc79daf6 100644 --- a/src/commands/apps-android-sha-list.ts +++ b/src/commands/apps-android-sha-list.ts @@ -1,4 +1,4 @@ -import Table = require("cli-table"); +const Table = require("cli-table"); import { Command } from "../command"; import { needProjectId } from "../projectUtils"; diff --git a/src/commands/apps-list.ts b/src/commands/apps-list.ts index 0a8027c7fcd..9d423c5835b 100644 --- a/src/commands/apps-list.ts +++ b/src/commands/apps-list.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; import * as ora from "ora"; -import Table = require("cli-table"); +const Table = require("cli-table"); import { Command } from "../command"; import { needProjectId } from "../projectUtils"; diff --git a/src/commands/database-instances-list.ts b/src/commands/database-instances-list.ts index fe59b786fad..d0263e6e2a6 100644 --- a/src/commands/database-instances-list.ts +++ b/src/commands/database-instances-list.ts @@ -1,5 +1,5 @@ import { Command } from "../command"; -import Table = require("cli-table"); +const Table = require("cli-table"); import * as clc from "colorette"; import * as ora from "ora"; diff --git a/src/commands/experiments-list.ts b/src/commands/experiments-list.ts index 87d4efe8afb..9de6b6bab78 100644 --- a/src/commands/experiments-list.ts +++ b/src/commands/experiments-list.ts @@ -1,5 +1,5 @@ import { Command } from "../command"; -import Table = require("cli-table"); +const Table = require("cli-table"); import * as experiments from "../experiments"; import { partition } from "../functional"; import { logger } from "../logger"; diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 13ac7acf4b6..32903744082 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -1,5 +1,5 @@ import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; diff --git a/src/commands/ext-dev-init.ts b/src/commands/ext-dev-init.ts index b771e15e1ab..5057ee3c000 100644 --- a/src/commands/ext-dev-init.ts +++ b/src/commands/ext-dev-init.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; diff --git a/src/commands/ext-dev-list.ts b/src/commands/ext-dev-list.ts index 560d9b836fe..7a0b7f425a6 100644 --- a/src/commands/ext-dev-list.ts +++ b/src/commands/ext-dev-list.ts @@ -1,5 +1,5 @@ import * as clc from "colorette"; -import Table = require("cli-table"); +const Table = require("cli-table"); import { Command } from "../command"; import { FirebaseError } from "../error"; diff --git a/src/commands/ext-dev-publish.ts b/src/commands/ext-dev-publish.ts index 7a876921bbc..bd37163ef53 100644 --- a/src/commands/ext-dev-publish.ts +++ b/src/commands/ext-dev-publish.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import { Command } from "../command"; import { publishExtensionVersionFromLocalSource, logPrefix } from "../extensions/extensionsHelper"; diff --git a/src/commands/ext-dev-usage.ts b/src/commands/ext-dev-usage.ts index 8ab746a67d3..75fbbc2b4e4 100644 --- a/src/commands/ext-dev-usage.ts +++ b/src/commands/ext-dev-usage.ts @@ -1,4 +1,4 @@ -import Table = require("cli-table"); +const Table = require("cli-table"); import * as clc from "colorette"; import * as utils from "../utils"; import { Command } from "../command"; diff --git a/src/commands/ext-info.ts b/src/commands/ext-info.ts index 157eb60285e..253cf236e21 100644 --- a/src/commands/ext-info.ts +++ b/src/commands/ext-info.ts @@ -10,7 +10,7 @@ import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; const FUNCTION_TYPE_REGEX = /\..+\.function/; diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 096aeaca30b..9b144099c15 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import { displayExtInfo } from "../extensions/displayExtensionInfo"; import * as askUserForEventsConfig from "../extensions/askUserForEventsConfig"; diff --git a/src/commands/ext-uninstall.ts b/src/commands/ext-uninstall.ts index 2ec8134c9fd..9459d9e14da 100644 --- a/src/commands/ext-uninstall.ts +++ b/src/commands/ext-uninstall.ts @@ -1,5 +1,5 @@ import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 42e52072f9c..eba16f4d652 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; diff --git a/src/commands/functions-list.ts b/src/commands/functions-list.ts index cea4c9df254..ee4d0e38a17 100644 --- a/src/commands/functions-list.ts +++ b/src/commands/functions-list.ts @@ -6,7 +6,7 @@ import { Options } from "../options"; import { requirePermissions } from "../requirePermissions"; import * as backend from "../deploy/functions/backend"; import { logger } from "../logger"; -import Table = require("cli-table"); +const Table = require("cli-table"); export const command = new Command("functions:list") .description("list all deployed functions in your Firebase project") diff --git a/src/commands/functions-secrets-get.ts b/src/commands/functions-secrets-get.ts index bc546589fe3..48e52403b10 100644 --- a/src/commands/functions-secrets-get.ts +++ b/src/commands/functions-secrets-get.ts @@ -1,4 +1,4 @@ -import Table = require("cli-table"); +const Table = require("cli-table"); import { Command } from "../command"; import { logger } from "../logger"; diff --git a/src/commands/hosting-channel-list.ts b/src/commands/hosting-channel-list.ts index fdd0bbb25fb..6abc3440327 100644 --- a/src/commands/hosting-channel-list.ts +++ b/src/commands/hosting-channel-list.ts @@ -1,5 +1,5 @@ import { bold } from "colorette"; -import Table = require("cli-table"); +const Table = require("cli-table"); import { Channel, listChannels } from "../hosting/api"; import { Command } from "../command"; diff --git a/src/commands/hosting-sites-get.ts b/src/commands/hosting-sites-get.ts index 3666c689884..bc32b327bb2 100644 --- a/src/commands/hosting-sites-get.ts +++ b/src/commands/hosting-sites-get.ts @@ -1,4 +1,4 @@ -import Table = require("cli-table"); +const Table = require("cli-table"); import { Command } from "../command"; import { Site, getSite } from "../hosting/api"; diff --git a/src/commands/hosting-sites-list.ts b/src/commands/hosting-sites-list.ts index 6efba760f9b..31b27ccdc6e 100644 --- a/src/commands/hosting-sites-list.ts +++ b/src/commands/hosting-sites-list.ts @@ -1,5 +1,5 @@ import { bold } from "colorette"; -import Table = require("cli-table"); +const Table = require("cli-table"); import { Command } from "../command"; import { Site, listSites } from "../hosting/api"; diff --git a/src/commands/projects-list.ts b/src/commands/projects-list.ts index 3789c93db88..295616d4fbe 100644 --- a/src/commands/projects-list.ts +++ b/src/commands/projects-list.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; import * as ora from "ora"; -import Table = require("cli-table"); +const Table = require("cli-table"); import { Command } from "../command"; import { FirebaseProjectMetadata, listFirebaseProjects } from "../management/projects"; diff --git a/src/commands/remoteconfig-get.ts b/src/commands/remoteconfig-get.ts index faf204badad..deb88f264ea 100644 --- a/src/commands/remoteconfig-get.ts +++ b/src/commands/remoteconfig-get.ts @@ -9,9 +9,9 @@ import { parseTemplateForTable } from "../remoteconfig/get"; import { Options } from "../options"; import * as utils from "../utils"; -import Table = require("cli-table"); +const Table = require("cli-table"); import * as fs from "fs"; -import util = require("util"); +import * as util from "util"; import { FirebaseError } from "../error"; const tableHead = ["Entry Name", "Value"]; diff --git a/src/commands/remoteconfig-versions-list.ts b/src/commands/remoteconfig-versions-list.ts index 4c2753b32ca..e8f16c1c804 100644 --- a/src/commands/remoteconfig-versions-list.ts +++ b/src/commands/remoteconfig-versions-list.ts @@ -7,11 +7,11 @@ import { requirePermissions } from "../requirePermissions"; import { Version, ListVersionsResult } from "../remoteconfig/interfaces"; import { datetimeString } from "../utils"; -import Table = require("cli-table"); +const Table = require("cli-table"); const tableHead = ["Update User", "Version Number", "Update Time"]; -function pushTableContents(table: Table, version: Version): number { +function pushTableContents(table: typeof Table, version: Version): number { return table.push([ version.updateUser?.email, version.versionNumber, diff --git a/src/emulator/auth/server.ts b/src/emulator/auth/server.ts index f7c61b6c9d2..bc5f23b84d9 100644 --- a/src/emulator/auth/server.ts +++ b/src/emulator/auth/server.ts @@ -32,7 +32,7 @@ import { import { logError } from "./utils"; import { camelCase } from "lodash"; import { registerHandlers } from "./handlers"; -import bodyParser = require("body-parser"); +import * as bodyParser from "body-parser"; import { URLSearchParams } from "url"; import { decode, JwtHeader } from "jsonwebtoken"; const apiSpec = apiSpecUntyped as OpenAPIObject; diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index bba72c808d2..b1c8ea4a4a7 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -17,7 +17,7 @@ import { promptOnce } from "../prompt"; import * as fsutils from "../fsutils"; import Signals = NodeJS.Signals; import SignalsListener = NodeJS.SignalsListener; -import Table = require("cli-table"); +const Table = require("cli-table"); import { emulatorSession } from "../track"; import { setEnvVarsForEmulators } from "./env"; diff --git a/src/emulator/extensionsEmulator.ts b/src/emulator/extensionsEmulator.ts index 4aeb6e7b0c6..3c522dbf001 100644 --- a/src/emulator/extensionsEmulator.ts +++ b/src/emulator/extensionsEmulator.ts @@ -3,7 +3,7 @@ import * as spawn from "cross-spawn"; import * as fs from "fs-extra"; import * as os from "os"; import * as path from "path"; -import Table = require("cli-table"); +const Table = require("cli-table"); import * as planner from "../deploy/extensions/planner"; import { enableApiURI } from "../ensureApiEnabled"; diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index e88e3ee0e9d..8979967759d 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -8,7 +8,7 @@ import { EmulatorLogger } from "../emulatorLogger"; import { createStorageRulesManager, StorageRulesManager } from "./rules/manager"; import { StorageRulesIssues, StorageRulesRuntime } from "./rules/runtime"; import { SourceFile } from "./rules/types"; -import express = require("express"); +import * as express from "express"; import { getAdminCredentialValidator, getAdminOnlyFirebaseRulesValidator, diff --git a/src/extensions/askUserForConsent.ts b/src/extensions/askUserForConsent.ts index 9e6525b2b0d..306246782d2 100644 --- a/src/extensions/askUserForConsent.ts +++ b/src/extensions/askUserForConsent.ts @@ -1,5 +1,5 @@ import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import { FirebaseError } from "../error"; import { logPrefix } from "../extensions/extensionsHelper"; diff --git a/src/extensions/billingMigrationHelper.ts b/src/extensions/billingMigrationHelper.ts index 169c98ac6a6..f0f246a1110 100644 --- a/src/extensions/billingMigrationHelper.ts +++ b/src/extensions/billingMigrationHelper.ts @@ -1,5 +1,5 @@ import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import { FirebaseError } from "../error"; import { ExtensionSpec } from "./types"; diff --git a/src/extensions/change-log.ts b/src/extensions/change-log.ts index ea6a21e0e73..716bbe13118 100644 --- a/src/extensions/change-log.ts +++ b/src/extensions/change-log.ts @@ -2,8 +2,8 @@ import * as clc from "colorette"; import { marked } from "marked"; import * as path from "path"; import * as semver from "semver"; -import TerminalRenderer = require("marked-terminal"); -import Table = require("cli-table"); +import * as TerminalRenderer from "marked-terminal"; +const Table = require("cli-table"); import { listExtensionVersions } from "./extensionsApi"; import { readFile } from "./localHelper"; diff --git a/src/extensions/displayExtensionInfo.ts b/src/extensions/displayExtensionInfo.ts index 5be6b21d0f3..aef30fe192a 100644 --- a/src/extensions/displayExtensionInfo.ts +++ b/src/extensions/displayExtensionInfo.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; import { marked } from "marked"; -import TerminalRenderer = require("marked-terminal"); +import * as TerminalRenderer from "marked-terminal"; import * as utils from "../utils"; import { logPrefix } from "./extensionsHelper"; diff --git a/src/extensions/listExtensions.ts b/src/extensions/listExtensions.ts index 1bfe3db72bb..5d2e0959ebe 100644 --- a/src/extensions/listExtensions.ts +++ b/src/extensions/listExtensions.ts @@ -1,5 +1,5 @@ import * as clc from "colorette"; -import Table = require("cli-table"); +const Table = require("cli-table"); import { listInstances } from "./extensionsApi"; import { logger } from "../logger"; diff --git a/src/init/features/database.ts b/src/init/features/database.ts index 2bc6012a067..6d4d5eeb0ee 100644 --- a/src/init/features/database.ts +++ b/src/init/features/database.ts @@ -12,7 +12,7 @@ import { checkInstanceNameAvailable, getDatabaseInstanceDetails, } from "../../management/database"; -import ora = require("ora"); +import * as ora from "ora"; import { ensure } from "../../ensureApiEnabled"; import { getDefaultDatabaseInstance } from "../../getDefaultDatabaseInstance"; import { FirebaseError } from "../../error"; diff --git a/src/init/features/firestore/indexes.ts b/src/init/features/firestore/indexes.ts index d76c7ee0dcc..2fe0f8c4238 100644 --- a/src/init/features/firestore/indexes.ts +++ b/src/init/features/firestore/indexes.ts @@ -1,5 +1,5 @@ import * as clc from "colorette"; -import fs = require("fs"); +import * as fs from "fs"; import { FirebaseError } from "../../../error"; import iv2 = require("../../../firestore/indexes"); diff --git a/src/init/features/firestore/rules.ts b/src/init/features/firestore/rules.ts index 5a738d1eb13..9e24d2c30f2 100644 --- a/src/init/features/firestore/rules.ts +++ b/src/init/features/firestore/rules.ts @@ -1,5 +1,5 @@ import * as clc from "colorette"; -import fs = require("fs"); +import * as fs from "fs"; import gcp = require("../../../gcp"); import fsutils = require("../../../fsutils"); diff --git a/src/profileReport.ts b/src/profileReport.ts index dad2223ce70..7933a481969 100644 --- a/src/profileReport.ts +++ b/src/profileReport.ts @@ -1,5 +1,5 @@ import * as clc from "colorette"; -import Table = require("cli-table"); +const Table = require("cli-table"); import * as fs from "fs"; import * as _ from "lodash"; import * as readline from "readline"; @@ -597,7 +597,7 @@ export class ProfileReport { write(clc.bold(clc.yellow(title)) + "\n"); } }; - const writeTable = (title: string, table: Table) => { + const writeTable = (title: string, table: typeof Table) => { writeTitle(title); write(table.toString() + "\n"); }; diff --git a/src/test/apiv2.spec.ts b/src/test/apiv2.spec.ts index 68ef5a05895..68e4e95bb5a 100644 --- a/src/test/apiv2.spec.ts +++ b/src/test/apiv2.spec.ts @@ -2,7 +2,7 @@ import { createServer, Server } from "http"; import { expect } from "chai"; import * as nock from "nock"; import AbortController from "abort-controller"; -import proxySetup = require("proxy"); +const proxySetup = require("proxy"); import { Client } from "../apiv2"; import { FirebaseError } from "../error"; diff --git a/src/test/emulators/auth/setup.ts b/src/test/emulators/auth/setup.ts index 12918a4d1d3..be542944d3c 100644 --- a/src/test/emulators/auth/setup.ts +++ b/src/test/emulators/auth/setup.ts @@ -1,6 +1,6 @@ import { Suite } from "mocha"; import { useFakeTimers } from "sinon"; -import supertest = require("supertest"); +import * as supertest from "supertest"; import { createApp } from "../../../emulator/auth/server"; import { AgentProjectState } from "../../../emulator/auth/state"; import { SingleProjectMode } from "../../../emulator/auth"; From 37dd6d6f6582a962ca14403766df2626261fc7a7 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 17 Mar 2023 10:00:10 -0700 Subject: [PATCH 0830/1699] Remove repeated EAP warning prompt for extensions deploy (#5604) --- src/deploy/extensions/prepare.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/deploy/extensions/prepare.ts b/src/deploy/extensions/prepare.ts index dca4de8c357..ee2418e79a2 100644 --- a/src/deploy/extensions/prepare.ts +++ b/src/deploy/extensions/prepare.ts @@ -87,24 +87,6 @@ export async function prepare(context: Context, options: Options, payload: Paylo } } - if (await displayWarningsForDeploy(payload.instancesToCreate)) { - if (!options.force && options.nonInteractive) { - throw new FirebaseError( - "Pass the --force flag to acknowledge these terms in non-interactive mode" - ); - } else if ( - !options.force && - !options.nonInteractive && - !(await prompt.promptOnce({ - type: "confirm", - message: `Do you wish to continue deploying these extensions?`, - default: true, - })) - ) { - throw new FirebaseError("Deployment cancelled"); - } - } - const permissionsNeeded: string[] = []; if (payload.instancesToCreate.length) { From d49107174a1b06e7e00c528f80347087d10963c3 Mon Sep 17 00:00:00 2001 From: Hsin-pei Toh <37965489+tohhsinpei@users.noreply.github.com> Date: Fri, 17 Mar 2023 15:49:18 -0400 Subject: [PATCH 0831/1699] Add database:import command for non-atomic import (#5396) * Import large data file using chunked writes * Add unit tests * Separate database:import command; add unit test for request * Add test case for array in JSON data * Address PR feedback * Disallow STDIN and arg data input * Stream top-level JSON objects * Support importing at data path * Update dependencies * Minor rewording of CLI prompt * Add CHANGELOG entry * Run npm install * Address PR feedback * Address PR feedback * Increase chunk size to 10 MB --- CHANGELOG.md | 1 + src/commands/database-import.ts | 104 ++++++++++++++++++ src/commands/index.ts | 1 + src/database/import.ts | 176 +++++++++++++++++++++++++++++++ src/test/database/import.spec.ts | 92 ++++++++++++++++ 5 files changed, 374 insertions(+) create mode 100644 src/commands/database-import.ts create mode 100644 src/database/import.ts create mode 100644 src/test/database/import.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index be75717ec41..c4095e369cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - Fix function deploy retry after quota exceeded bug and increase backoff. (#5601) - Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) - Adds 2nd gen firestore triggers to firebase deploy (#5592). +- Adds `database:import` command for non-atomic imports (#5396). diff --git a/src/commands/database-import.ts b/src/commands/database-import.ts new file mode 100644 index 00000000000..3cf2b9e317b --- /dev/null +++ b/src/commands/database-import.ts @@ -0,0 +1,104 @@ +import * as clc from "colorette"; +import * as fs from "fs"; +import * as utils from "../utils"; + +import { Command } from "../command"; +import DatabaseImporter from "../database/import"; +import { Emulators } from "../emulator/types"; +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import { needProjectId } from "../projectUtils"; +import { Options } from "../options"; +import { printNoticeIfEmulated } from "../emulator/commandUtils"; +import { promptOnce } from "../prompt"; +import { DatabaseInstance, populateInstanceDetails } from "../management/database"; +import { realtimeOriginOrEmulatorOrCustomUrl } from "../database/api"; +import { requireDatabaseInstance } from "../requireDatabaseInstance"; +import { requirePermissions } from "../requirePermissions"; + +interface DatabaseImportOptions extends Options { + instance: string; + instanceDetails: DatabaseInstance; + disableTriggers?: boolean; + filter?: string; +} + +export const command = new Command("database:import [infile]") + .description( + "non-atomically import the contents of a JSON file to the specified path in Realtime Database" + ) + .withForce() + .option( + "--instance ", + "use the database .firebaseio.com (if omitted, use default database instance)" + ) + .option( + "--disable-triggers", + "suppress any Cloud functions triggered by this operation, default to true", + true + ) + .option( + "--filter ", + "import only data at this path in the JSON file (if omitted, import entire file)" + ) + .before(requirePermissions, ["firebasedatabase.instances.update"]) + .before(requireDatabaseInstance) + .before(populateInstanceDetails) + .before(printNoticeIfEmulated, Emulators.DATABASE) + .action(async (path: string, infile: string | undefined, options: DatabaseImportOptions) => { + if (!path.startsWith("/")) { + throw new FirebaseError("Path must begin with /"); + } + + if (!infile) { + throw new FirebaseError("No file supplied"); + } + + const projectId = needProjectId(options); + const origin = realtimeOriginOrEmulatorOrCustomUrl(options.instanceDetails.databaseUrl); + const dbPath = utils.getDatabaseUrl(origin, options.instance, path); + const dbUrl = new URL(dbPath); + if (options.disableTriggers) { + dbUrl.searchParams.set("disableTriggers", "true"); + } + + const confirm = await promptOnce( + { + type: "confirm", + name: "force", + default: false, + message: "You are about to import data to " + clc.cyan(dbPath) + ". Are you sure?", + }, + options + ); + if (!confirm) { + throw new FirebaseError("Command aborted."); + } + + const inStream = fs.createReadStream(infile); + const dataPath = options.filter || ""; + const importer = new DatabaseImporter(dbUrl, inStream, dataPath); + + let responses; + try { + responses = await importer.execute(); + } catch (err: any) { + if (err instanceof FirebaseError) { + throw err; + } + logger.debug(err); + throw new FirebaseError(`Unexpected error while importing data: ${err}`, { exit: 2 }); + } + + if (responses.length) { + utils.logSuccess("Data persisted successfully"); + } else { + utils.logWarning("No data was persisted. Check the data path supplied."); + } + + logger.info(); + logger.info( + clc.bold("View data at:"), + utils.getDatabaseViewDataUrl(origin, projectId, options.instance, path) + ); + }); diff --git a/src/commands/index.ts b/src/commands/index.ts index 957c666aead..a23e82d9bee 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -45,6 +45,7 @@ export function load(client: any): any { client.crashlytics.mappingfile.upload = loadCommand("crashlytics-mappingfile-upload"); client.database = {}; client.database.get = loadCommand("database-get"); + client.database.import = loadCommand("database-import"); client.database.instances = {}; client.database.instances.create = loadCommand("database-instances-create"); client.database.instances.list = loadCommand("database-instances-list"); diff --git a/src/database/import.ts b/src/database/import.ts new file mode 100644 index 00000000000..df977f04f9a --- /dev/null +++ b/src/database/import.ts @@ -0,0 +1,176 @@ +import * as Chain from "stream-chain"; +import * as clc from "colorette"; +import * as Filter from "stream-json/filters/Filter"; +import * as stream from "stream"; +import * as StreamObject from "stream-json/streamers/StreamObject"; + +import pLimit from "p-limit"; + +import { URL } from "url"; +import { Client, ClientResponse } from "../apiv2"; +import { FirebaseError } from "../error"; + +const MAX_CHUNK_SIZE = 1024 * 1024 * 10; +const CONCURRENCY_LIMIT = 5; + +type JsonType = { [key: string]: JsonType } | string | number | boolean; + +type Data = { + json: JsonType; + pathname: string; +}; + +type ChunkedData = { + chunks: Data[] | null; + size: number; +}; + +/** + * Imports JSON data to a given RTDB instance. + * + * The data is parsed and chunked into subtrees of ~10 MB, to be subsequently written in parallel. + */ +export default class DatabaseImporter { + private client: Client; + private limit = pLimit(CONCURRENCY_LIMIT); + + constructor( + private dbUrl: URL, + private inStream: stream.Readable, + private dataPath: string, + private chunkSize = MAX_CHUNK_SIZE + ) { + this.client = new Client({ urlPrefix: dbUrl.origin, auth: true }); + } + + /** + * Writes the chunked data to RTDB. Any existing data at the specified location will be overwritten. + */ + async execute(): Promise[]> { + await this.checkLocationIsEmpty(); + return this.readAndWriteChunks(); + } + + private async checkLocationIsEmpty(): Promise { + const response = await this.client.request({ + method: "GET", + path: this.dbUrl.pathname + ".json", + queryParams: { shallow: "true" }, + }); + + if (response.body) { + throw new FirebaseError( + "Importing is only allowed for an empty location. Delete all data by running " + + clc.bold(`firebase database:remove ${this.dbUrl.pathname} --disable-triggers`) + + ", then rerun this command.", + { exit: 2 } + ); + } + } + + private readAndWriteChunks(): Promise[]> { + const { dbUrl } = this; + const chunkData = this.chunkData.bind(this); + const writeChunk = this.writeChunk.bind(this); + const getJoinedPath = this.joinPath.bind(this); + + const readChunks = new stream.Transform({ objectMode: true }); + readChunks._transform = function (chunk: { key: string; value: JsonType }, _, done) { + const data = { json: chunk.value, pathname: getJoinedPath(dbUrl.pathname, chunk.key) }; + const chunkedData = chunkData(data); + const chunks = chunkedData.chunks || [data]; + for (const chunk of chunks) { + this.push(chunk); + } + done(); + }; + + const writeChunks = new stream.Transform({ objectMode: true }); + writeChunks._transform = async function (chunk: Data, _, done) { + const res = await writeChunk(chunk); + this.push(res); + done(); + }; + + return new Promise((resolve, reject) => { + const responses: ClientResponse[] = []; + const pipeline = new Chain([ + this.inStream, + Filter.withParser({ + filter: this.computeFilterString(this.dataPath) || (() => true), + pathSeparator: "/", + }), + StreamObject.streamObject(), + ]); + pipeline + .on("error", (err: Error) => + reject( + new FirebaseError( + `Invalid data; couldn't parse JSON object, array, or value. ${err.message}`, + { + original: err, + exit: 2, + } + ) + ) + ) + .pipe(readChunks) + .pipe(writeChunks) + .on("data", (res: ClientResponse) => responses.push(res)) + .on("error", reject) + .once("end", () => resolve(responses)); + }); + } + + private writeChunk(chunk: Data): Promise> { + return this.limit(() => + this.client.request({ + method: "PUT", + path: chunk.pathname + ".json", + body: JSON.stringify(chunk.json), + queryParams: this.dbUrl.searchParams, + }) + ); + } + + private chunkData({ json, pathname }: Data): ChunkedData { + if (typeof json === "string" || typeof json === "number" || typeof json === "boolean") { + // Leaf node, cannot be chunked + return { chunks: null, size: JSON.stringify(json).length }; + } else { + // Children node + let size = 2; // {} + + const chunks = []; + let hasChunkedChild = false; + + for (const [key, val] of Object.entries(json)) { + size += key.length + 3; // "": + + const child = { json: val, pathname: this.joinPath(pathname, key) }; + const childChunks = this.chunkData(child); + size += childChunks.size; + if (childChunks.chunks) { + hasChunkedChild = true; + chunks.push(...childChunks.chunks); + } else { + chunks.push(child); + } + } + + if (hasChunkedChild || size >= this.chunkSize) { + return { chunks, size }; + } else { + return { chunks: null, size }; + } + } + } + + private computeFilterString(dataPath: string): string { + return dataPath.split("/").filter(Boolean).join("/"); + } + + private joinPath(root: string, key: string): string { + return [root, key].join("/").replace("//", "/"); + } +} diff --git a/src/test/database/import.spec.ts b/src/test/database/import.spec.ts new file mode 100644 index 00000000000..fe7a0524394 --- /dev/null +++ b/src/test/database/import.spec.ts @@ -0,0 +1,92 @@ +import * as nock from "nock"; +import * as stream from "stream"; +import * as utils from "../../utils"; +import { expect } from "chai"; + +import DatabaseImporter from "../../database/import"; +import { FirebaseError } from "../../error"; + +const dbUrl = new URL("https://test-db.firebaseio.com/foo"); + +describe("DatabaseImporter", () => { + const DATA = { a: 100, b: [true, "bar", { f: { g: 0, h: 1 }, i: "baz" }] }; + let DATA_STREAM: stream.Readable; + + beforeEach(() => { + DATA_STREAM = utils.stringToStream(JSON.stringify(DATA))!; + }); + + it("throws FirebaseError when JSON is invalid", async () => { + nock("https://test-db.firebaseio.com").get("/foo.json?shallow=true").reply(200); + const INVALID_JSON = '{"a": {"b"}}'; + const importer = new DatabaseImporter( + dbUrl, + utils.stringToStream(INVALID_JSON)!, + /* importPath= */ "/" + ); + + await expect(importer.execute()).to.be.rejectedWith( + FirebaseError, + "Invalid data; couldn't parse JSON object, array, or value." + ); + }); + + it("chunks data in top-level objects", async () => { + nock("https://test-db.firebaseio.com").get("/foo.json?shallow=true").reply(200); + nock("https://test-db.firebaseio.com").put("/foo/a.json", "100").reply(200); + nock("https://test-db.firebaseio.com") + .put("/foo/b.json", JSON.stringify([true, "bar", { f: { g: 0, h: 1 }, i: "baz" }])) + .reply(200); + const importer = new DatabaseImporter(dbUrl, DATA_STREAM, /* importPath= */ "/"); + + const responses = await importer.execute(); + + expect(responses).to.have.length(2); + expect(nock.isDone()).to.be.true; + }); + + it("chunks data according to provided chunk size", async () => { + nock("https://test-db.firebaseio.com").get("/foo.json?shallow=true").reply(200); + nock("https://test-db.firebaseio.com").put("/foo/a.json", "100").reply(200); + nock("https://test-db.firebaseio.com").put("/foo/b/0.json", "true").reply(200); + nock("https://test-db.firebaseio.com").put("/foo/b/1.json", '"bar"').reply(200); + nock("https://test-db.firebaseio.com") + .put("/foo/b/2/f.json", JSON.stringify({ g: 0, h: 1 })) + .reply(200); + nock("https://test-db.firebaseio.com").put("/foo/b/2/i.json", '"baz"').reply(200); + const importer = new DatabaseImporter( + dbUrl, + DATA_STREAM, + /* importPath= */ "/", + /* chunkSize= */ 20 + ); + + const responses = await importer.execute(); + + expect(responses).to.have.length(5); + expect(nock.isDone()).to.be.true; + }); + + it("imports from data path", async () => { + nock("https://test-db.firebaseio.com").get("/foo.json?shallow=true").reply(200); + nock("https://test-db.firebaseio.com") + .put("/foo/b.json", JSON.stringify([true, "bar", { f: { g: 0, h: 1 }, i: "baz" }])) + .reply(200); + const importer = new DatabaseImporter(dbUrl, DATA_STREAM, /* importPath= */ "/b"); + + const responses = await importer.execute(); + + expect(responses).to.have.length(1); + expect(nock.isDone()).to.be.true; + }); + + it("throws FirebaseError when data location is nonempty", async () => { + nock("https://test-db.firebaseio.com").get("/foo.json?shallow=true").reply(200, { a: "foo" }); + const importer = new DatabaseImporter(dbUrl, DATA_STREAM, /* importPath= */ "/"); + + await expect(importer.execute()).to.be.rejectedWith( + FirebaseError, + /Importing is only allowed for an empty location./ + ); + }); +}); From b177da255568c14803fae9629f9492a8f22af936 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 20 Mar 2023 11:05:52 -0700 Subject: [PATCH 0832/1699] Wrap init and logout commands in named exported functions (#5611) --- src/commands/init.ts | 260 +++++++++++++++++++++-------------------- src/commands/logout.ts | 132 +++++++++++---------- 2 files changed, 203 insertions(+), 189 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 26546c2e190..0fd07913955 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -12,6 +12,7 @@ import { prompt, promptOnce } from "../prompt"; import { requireAuth } from "../requireAuth"; import * as fsutils from "../fsutils"; import * as utils from "../utils"; +import { Options } from "../options"; const homeDir = os.homedir(); @@ -80,130 +81,137 @@ ${[...featureNames] export const command = new Command("init [feature]") .description(DESCRIPTION) .before(requireAuth) - .action((feature, options) => { - if (feature && !featureNames.includes(feature)) { - return utils.reject( - clc.bold(feature) + - " is not a supported feature; must be one of " + - featureNames.join(", ") + - "." - ); - } - - const cwd = options.cwd || process.cwd(); - - const warnings = []; - let warningText = ""; - if (isOutside(homeDir, cwd)) { - warnings.push("You are currently outside your home directory"); - } - if (cwd === homeDir) { - warnings.push("You are initializing your home directory as a Firebase project directory"); - } - - const existingConfig = Config.load(options, true); - if (existingConfig) { - warnings.push("You are initializing within an existing Firebase project directory"); - } - - const config = - existingConfig !== null ? existingConfig : new Config({}, { projectDir: cwd, cwd: cwd }); - - if (warnings.length) { - warningText = - "\nBefore we get started, keep in mind:\n\n " + - clc.yellow(clc.bold("* ")) + - warnings.join("\n " + clc.yellow(clc.bold("* "))) + - "\n"; - } - - logger.info( - clc.yellow(clc.bold(BANNER_TEXT)) + - "\nYou're about to initialize a Firebase project in this directory:\n\n " + - clc.bold(config.projectDir) + - "\n" + - warningText + .action(initAction); + +/** + * Init command action + * @param feature Feature to init (e.g., hosting, functions) + * @param options Command options + */ +export function initAction(feature: string, options: Options): Promise { + if (feature && !featureNames.includes(feature)) { + return utils.reject( + clc.bold(feature) + + " is not a supported feature; must be one of " + + featureNames.join(", ") + + "." ); - - const setup: Setup = { - config: config.src, - rcfile: config.readProjectFile(".firebaserc", { - json: true, - fallback: {}, - }), - }; - - let next; - // HACK: Windows Node has issues with selectables as the first prompt, so we - // add an extra confirmation prompt that fixes the problem - if (process.platform === "win32") { - next = promptOnce({ - type: "confirm", - message: "Are you ready to proceed?", - }); - } else { - next = Promise.resolve(true); - } - - return next - .then((proceed) => { - if (!proceed) { - return utils.reject("Aborted by user.", { exit: 1 }); - } - - if (feature) { - setup.featureArg = true; - setup.features = [feature]; - return undefined; - } - return prompt(setup, [ - { - type: "checkbox", - name: "features", - message: - "Which Firebase features do you want to set up for this directory? " + - "Press Space to select features, then Enter to confirm your choices.", - choices: choices, - }, - ]); - }) - .then(() => { - if (!setup.features || setup.features?.length === 0) { - return utils.reject( - "Must select at least one feature. Use " + - clc.bold(clc.underline("SPACEBAR")) + - " to select features, or specify a feature by running " + - clc.bold("firebase init [feature_name]") - ); - } - - // Always set up project - setup.features.unshift("project"); - - // If there is more than one account, add an account choice phase - const allAccounts = getAllAccounts(); - if (allAccounts.length > 1) { - setup.features.unshift("account"); - } - - // "hosting:github" is a part of "hosting", so if both are selected, "hosting:github" is ignored. - if (setup.features.includes("hosting") && setup.features.includes("hosting:github")) { - setup.features = setup.features.filter((f) => f !== "hosting:github"); - } - - return init(setup, config, options); - }) - .then(() => { - logger.info(); - utils.logBullet("Writing configuration info to " + clc.bold("firebase.json") + "..."); - config.writeProjectFile("firebase.json", setup.config); - utils.logBullet("Writing project information to " + clc.bold(".firebaserc") + "..."); - config.writeProjectFile(".firebaserc", setup.rcfile); - if (!fsutils.fileExistsSync(config.path(".gitignore"))) { - utils.logBullet("Writing gitignore file to " + clc.bold(".gitignore") + "..."); - config.writeProjectFile(".gitignore", GITIGNORE_TEMPLATE); - } - logger.info(); - utils.logSuccess("Firebase initialization complete!"); - }); - }); + } + + const cwd = options.cwd || process.cwd(); + + const warnings = []; + let warningText = ""; + if (isOutside(homeDir, cwd)) { + warnings.push("You are currently outside your home directory"); + } + if (cwd === homeDir) { + warnings.push("You are initializing your home directory as a Firebase project directory"); + } + + const existingConfig = Config.load(options, true); + if (existingConfig) { + warnings.push("You are initializing within an existing Firebase project directory"); + } + + const config = + existingConfig !== null ? existingConfig : new Config({}, { projectDir: cwd, cwd: cwd }); + + if (warnings.length) { + warningText = + "\nBefore we get started, keep in mind:\n\n " + + clc.yellow(clc.bold("* ")) + + warnings.join("\n " + clc.yellow(clc.bold("* "))) + + "\n"; + } + + logger.info( + clc.yellow(clc.bold(BANNER_TEXT)) + + "\nYou're about to initialize a Firebase project in this directory:\n\n " + + clc.bold(config.projectDir) + + "\n" + + warningText + ); + + const setup: Setup = { + config: config.src, + rcfile: config.readProjectFile(".firebaserc", { + json: true, + fallback: {}, + }), + }; + + let next; + // HACK: Windows Node has issues with selectables as the first prompt, so we + // add an extra confirmation prompt that fixes the problem + if (process.platform === "win32") { + next = promptOnce({ + type: "confirm", + message: "Are you ready to proceed?", + }); + } else { + next = Promise.resolve(true); + } + + return next + .then((proceed) => { + if (!proceed) { + return utils.reject("Aborted by user.", { exit: 1 }); + } + + if (feature) { + setup.featureArg = true; + setup.features = [feature]; + return undefined; + } + return prompt(setup, [ + { + type: "checkbox", + name: "features", + message: + "Which Firebase features do you want to set up for this directory? " + + "Press Space to select features, then Enter to confirm your choices.", + choices: choices, + }, + ]); + }) + .then(() => { + if (!setup.features || setup.features?.length === 0) { + return utils.reject( + "Must select at least one feature. Use " + + clc.bold(clc.underline("SPACEBAR")) + + " to select features, or specify a feature by running " + + clc.bold("firebase init [feature_name]") + ); + } + + // Always set up project + setup.features.unshift("project"); + + // If there is more than one account, add an account choice phase + const allAccounts = getAllAccounts(); + if (allAccounts.length > 1) { + setup.features.unshift("account"); + } + + // "hosting:github" is a part of "hosting", so if both are selected, "hosting:github" is ignored. + if (setup.features.includes("hosting") && setup.features.includes("hosting:github")) { + setup.features = setup.features.filter((f) => f !== "hosting:github"); + } + + return init(setup, config, options); + }) + .then(() => { + logger.info(); + utils.logBullet("Writing configuration info to " + clc.bold("firebase.json") + "..."); + config.writeProjectFile("firebase.json", setup.config); + utils.logBullet("Writing project information to " + clc.bold(".firebaserc") + "..."); + config.writeProjectFile(".firebaserc", setup.rcfile); + if (!fsutils.fileExistsSync(config.path(".gitignore"))) { + utils.logBullet("Writing gitignore file to " + clc.bold(".gitignore") + "..."); + config.writeProjectFile(".gitignore", GITIGNORE_TEMPLATE); + } + logger.info(); + utils.logSuccess("Firebase initialization complete!"); + }); +} diff --git a/src/commands/logout.ts b/src/commands/logout.ts index e3149a1d978..8db356cfc1e 100644 --- a/src/commands/logout.ts +++ b/src/commands/logout.ts @@ -5,85 +5,91 @@ import * as clc from "colorette"; import * as utils from "../utils"; import * as auth from "../auth"; import { promptOnce } from "../prompt"; +import { Options } from "../options"; export const command = new Command("logout [email]") .description("log the CLI out of Firebase") - .action(async (email: string | undefined, options: any) => { - const globalToken = utils.getInheritedOption(options, "token"); - utils.assertIsStringOrUndefined(globalToken); + .action(logoutAction); - const allAccounts = auth.getAllAccounts(); - if (allAccounts.length === 0 && !globalToken) { - logger.info("No need to logout, not logged in"); - return; - } - - const defaultAccount = auth.getGlobalDefaultAccount(); - const additionalAccounts = auth.getAdditionalAccounts(); +/** + * Logout command action + * @param email Email of account to log out. + * @param options Command options. + */ +export async function logoutAction(email: string | undefined, options: Options): Promise { + const globalToken = utils.getInheritedOption(options, "token"); + utils.assertIsStringOrUndefined(globalToken); - const accountsToLogOut = email - ? allAccounts.filter((a) => a.user.email === email) - : allAccounts; + const allAccounts = auth.getAllAccounts(); + if (allAccounts.length === 0 && !globalToken) { + logger.info("No need to logout, not logged in"); + return; + } - if (email && accountsToLogOut.length === 0) { - utils.logWarning(`No account matches ${email}, can't log out.`); - return; - } + const defaultAccount = auth.getGlobalDefaultAccount(); + const additionalAccounts = auth.getAdditionalAccounts(); - // If they are logging out of their primary account, choose one to - // replace it. - const logoutDefault = email === defaultAccount?.user.email; - let newDefaultAccount: auth.Account | undefined = undefined; - if (logoutDefault && additionalAccounts.length > 0) { - if (additionalAccounts.length === 1) { - newDefaultAccount = additionalAccounts[0]; - } else { - const choices = additionalAccounts.map((a) => { - return { - name: a.user.email, - value: a, - }; - }); - - newDefaultAccount = await promptOnce({ - type: "list", - message: - "You are logging out of your default account, which account should become the new default?", - choices, - }); - } - } + const accountsToLogOut = email ? allAccounts.filter((a) => a.user.email === email) : allAccounts; - for (const account of accountsToLogOut) { - const token = account.tokens.refresh_token; + if (email && accountsToLogOut.length === 0) { + utils.logWarning(`No account matches ${email}, can't log out.`); + return; + } - if (token) { - auth.setRefreshToken(token); - try { - await auth.logout(token); - } catch (e: any) { - utils.logWarning( - `Invalid refresh token for ${account.user.email}, did not need to deauthorize` - ); - } + // If they are logging out of their primary account, choose one to + // replace it. + const logoutDefault = email === defaultAccount?.user.email; + let newDefaultAccount: auth.Account | undefined = undefined; + if (logoutDefault && additionalAccounts.length > 0) { + if (additionalAccounts.length === 1) { + newDefaultAccount = additionalAccounts[0]; + } else { + const choices = additionalAccounts.map((a) => { + return { + name: a.user.email, + value: a, + }; + }); - utils.logSuccess(`Logged out from ${clc.bold(account.user.email)}`); - } + newDefaultAccount = await promptOnce({ + type: "list", + message: + "You are logging out of your default account, which account should become the new default?", + choices, + }); } + } + + for (const account of accountsToLogOut) { + const token = account.tokens.refresh_token; - if (globalToken) { - auth.setRefreshToken(globalToken); + if (token) { + auth.setRefreshToken(token); try { - await auth.logout(globalToken); + await auth.logout(token); } catch (e: any) { - utils.logWarning("Invalid refresh token, did not need to deauthorize"); + utils.logWarning( + `Invalid refresh token for ${account.user.email}, did not need to deauthorize` + ); } - utils.logSuccess(`Logged out from token "${clc.bold(globalToken)}"`); + utils.logSuccess(`Logged out from ${clc.bold(account.user.email)}`); } + } - if (newDefaultAccount) { - utils.logSuccess(`Setting default account to "${newDefaultAccount.user.email}"`); - auth.setGlobalDefaultAccount(newDefaultAccount); + if (globalToken) { + auth.setRefreshToken(globalToken); + try { + await auth.logout(globalToken); + } catch (e: any) { + utils.logWarning("Invalid refresh token, did not need to deauthorize"); } - }); + + utils.logSuccess(`Logged out from token "${clc.bold(globalToken)}"`); + } + + if (newDefaultAccount) { + utils.logSuccess(`Setting default account to "${newDefaultAccount.user.email}"`); + auth.setGlobalDefaultAccount(newDefaultAccount); + } +} From d5fbe5b2620b83e4d4f7732f625fe85201c8608a Mon Sep 17 00:00:00 2001 From: Nils Reichardt Date: Tue, 21 Mar 2023 18:02:43 +0100 Subject: [PATCH 0833/1699] Use `checkout@v3` for Firebase Hosting Preview GitHub Workflow (#5600) Co-authored-by: joehan Co-authored-by: Bryan Kendall --- src/init/features/hosting/github.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init/features/hosting/github.ts b/src/init/features/hosting/github.ts index 0e6fba574ef..0f78bea141f 100644 --- a/src/init/features/hosting/github.ts +++ b/src/init/features/hosting/github.ts @@ -30,7 +30,7 @@ let YML_FULL_PATH_MERGE: string; const YML_PULL_REQUEST_FILENAME = "firebase-hosting-pull-request.yml"; const YML_MERGE_FILENAME = "firebase-hosting-merge.yml"; -const CHECKOUT_GITHUB_ACTION_NAME = "actions/checkout@v2"; +const CHECKOUT_GITHUB_ACTION_NAME = "actions/checkout@v3"; const HOSTING_GITHUB_ACTION_NAME = "FirebaseExtended/action-hosting-deploy@v0"; const githubApiClient = new Client({ urlPrefix: githubApiOrigin, auth: false }); From 4c75ab671f00b055976cf8274a5d98c4803e315c Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 21 Mar 2023 11:57:42 -0700 Subject: [PATCH 0834/1699] Extract auth and project types into their own files (#5615) * Fix jsdoc comment * Extract auth and project types * Clean up * Cleanup and formatting --- src/auth.ts | 60 ++++---------------------- src/commands/login-list.ts | 2 +- src/commands/login.ts | 5 ++- src/commands/logout.ts | 28 +++++++----- src/commands/projects-addfirebase.ts | 7 +-- src/commands/projects-create.ts | 2 +- src/commands/projects-list.ts | 3 +- src/commands/use.ts | 7 +-- src/defaultCredentials.ts | 2 +- src/emulator/functionsEmulator.ts | 2 +- src/init/features/account.ts | 2 +- src/init/features/project.ts | 2 +- src/management/projects.ts | 27 +----------- src/requireAuth.ts | 3 +- src/test/auth.spec.ts | 39 ++++++++++------- src/test/defaultCredentials.spec.ts | 3 +- src/test/init/features/project.spec.ts | 3 +- src/test/management/projects.spec.ts | 15 ++++--- src/types/auth/index.d.ts | 46 ++++++++++++++++++++ src/types/project/index.d.ts | 25 +++++++++++ 20 files changed, 151 insertions(+), 132 deletions(-) create mode 100644 src/types/auth/index.d.ts create mode 100644 src/types/project/index.d.ts diff --git a/src/auth.ts b/src/auth.ts index 6eb37183188..56e5f31cdd9 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -30,57 +30,15 @@ import { githubOrigin, googleOrigin, } from "./api"; - -// The wire protocol for an access token returned by Google. -// When we actually refresh from the server we should always have -// these optional fields, but when a user passes --token we may -// only have access_token. -export interface Tokens { - id_token?: string; - access_token: string; - refresh_token?: string; - scopes?: string[]; -} - -export interface User { - email: string; - - iss?: string; - azp?: string; - aud?: string; - sub?: number; - hd?: string; - email_verified?: boolean; - at_hash?: string; - iat?: number; - exp?: number; -} - -export interface Account { - user: User; - tokens: Tokens; -} - -interface TokensWithExpiration extends Tokens { - expires_at?: number; -} - -interface TokensWithTTL extends Tokens { - expires_in?: number; -} - -interface UserCredentials { - user: string | User; - tokens: TokensWithExpiration; - scopes: string[]; -} - -// https://docs.github.com/en/developers/apps/authorizing-oauth-apps -interface GitHubAuthResponse { - access_token: string; - scope: string; - token_type: string; -} +import { + Account, + User, + Tokens, + TokensWithExpiration, + TokensWithTTL, + GitHubAuthResponse, + UserCredentials, +} from "./types/auth"; portfinder.setBasePort(9005); diff --git a/src/commands/login-list.ts b/src/commands/login-list.ts index f173a9ab292..41da77c60dc 100644 --- a/src/commands/login-list.ts +++ b/src/commands/login-list.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; -import { User } from "../auth"; +import { User } from "../types/auth"; import { Command } from "../command"; import { logger } from "../logger"; import * as utils from "../utils"; diff --git a/src/commands/login.ts b/src/commands/login.ts index e5a8bdd80c3..d7a8c833831 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -10,6 +10,7 @@ import { promptOnce } from "../prompt"; import * as auth from "../auth"; import { isCloudEnvironment } from "../utils"; +import { User, Tokens } from "../types/auth"; export const command = new Command("login") .description("log the CLI into Firebase") @@ -25,8 +26,8 @@ export const command = new Command("login") ); } - const user = options.user as auth.User | undefined; - const tokens = options.tokens as auth.Tokens | undefined; + const user = options.user as User | undefined; + const tokens = options.tokens as Tokens | undefined; if (user && tokens && !options.reauth) { logger.info("Already logged in as", clc.bold(user.email)); diff --git a/src/commands/logout.ts b/src/commands/logout.ts index 8db356cfc1e..e8745ff9d18 100644 --- a/src/commands/logout.ts +++ b/src/commands/logout.ts @@ -3,9 +3,17 @@ import { logger } from "../logger"; import * as clc from "colorette"; import * as utils from "../utils"; -import * as auth from "../auth"; import { promptOnce } from "../prompt"; import { Options } from "../options"; +import { Account } from "../types/auth"; +import { + getAllAccounts, + getGlobalDefaultAccount, + getAdditionalAccounts, + setRefreshToken, + logout, + setGlobalDefaultAccount, +} from "../auth"; export const command = new Command("logout [email]") .description("log the CLI out of Firebase") @@ -20,14 +28,14 @@ export async function logoutAction(email: string | undefined, options: Options): const globalToken = utils.getInheritedOption(options, "token"); utils.assertIsStringOrUndefined(globalToken); - const allAccounts = auth.getAllAccounts(); + const allAccounts = getAllAccounts(); if (allAccounts.length === 0 && !globalToken) { logger.info("No need to logout, not logged in"); return; } - const defaultAccount = auth.getGlobalDefaultAccount(); - const additionalAccounts = auth.getAdditionalAccounts(); + const defaultAccount = getGlobalDefaultAccount(); + const additionalAccounts = getAdditionalAccounts(); const accountsToLogOut = email ? allAccounts.filter((a) => a.user.email === email) : allAccounts; @@ -39,7 +47,7 @@ export async function logoutAction(email: string | undefined, options: Options): // If they are logging out of their primary account, choose one to // replace it. const logoutDefault = email === defaultAccount?.user.email; - let newDefaultAccount: auth.Account | undefined = undefined; + let newDefaultAccount: Account | undefined = undefined; if (logoutDefault && additionalAccounts.length > 0) { if (additionalAccounts.length === 1) { newDefaultAccount = additionalAccounts[0]; @@ -64,9 +72,9 @@ export async function logoutAction(email: string | undefined, options: Options): const token = account.tokens.refresh_token; if (token) { - auth.setRefreshToken(token); + setRefreshToken(token); try { - await auth.logout(token); + await logout(token); } catch (e: any) { utils.logWarning( `Invalid refresh token for ${account.user.email}, did not need to deauthorize` @@ -78,9 +86,9 @@ export async function logoutAction(email: string | undefined, options: Options): } if (globalToken) { - auth.setRefreshToken(globalToken); + setRefreshToken(globalToken); try { - await auth.logout(globalToken); + await logout(globalToken); } catch (e: any) { utils.logWarning("Invalid refresh token, did not need to deauthorize"); } @@ -90,6 +98,6 @@ export async function logoutAction(email: string | undefined, options: Options): if (newDefaultAccount) { utils.logSuccess(`Setting default account to "${newDefaultAccount.user.email}"`); - auth.setGlobalDefaultAccount(newDefaultAccount); + setGlobalDefaultAccount(newDefaultAccount); } } diff --git a/src/commands/projects-addfirebase.ts b/src/commands/projects-addfirebase.ts index cd0a5c6e8d4..aa285dc7884 100644 --- a/src/commands/projects-addfirebase.ts +++ b/src/commands/projects-addfirebase.ts @@ -1,10 +1,7 @@ import { Command } from "../command"; import { FirebaseError } from "../error"; -import { - addFirebaseToCloudProjectAndLog, - FirebaseProjectMetadata, - promptAvailableProjectId, -} from "../management/projects"; +import { addFirebaseToCloudProjectAndLog, promptAvailableProjectId } from "../management/projects"; +import { FirebaseProjectMetadata } from "../types/project"; import { requireAuth } from "../requireAuth"; export const command = new Command("projects:addfirebase [projectId]") diff --git a/src/commands/projects-create.ts b/src/commands/projects-create.ts index 5b811b9f816..3865087bc87 100644 --- a/src/commands/projects-create.ts +++ b/src/commands/projects-create.ts @@ -2,10 +2,10 @@ import { Command } from "../command"; import { FirebaseError } from "../error"; import { createFirebaseProjectAndLog, - FirebaseProjectMetadata, ProjectParentResourceType, PROJECTS_CREATE_QUESTIONS, } from "../management/projects"; +import { FirebaseProjectMetadata } from "../types/project"; import { prompt } from "../prompt"; import { requireAuth } from "../requireAuth"; diff --git a/src/commands/projects-list.ts b/src/commands/projects-list.ts index 295616d4fbe..af52e507145 100644 --- a/src/commands/projects-list.ts +++ b/src/commands/projects-list.ts @@ -3,7 +3,8 @@ import * as ora from "ora"; const Table = require("cli-table"); import { Command } from "../command"; -import { FirebaseProjectMetadata, listFirebaseProjects } from "../management/projects"; +import { listFirebaseProjects } from "../management/projects"; +import { FirebaseProjectMetadata } from "../types/project"; import { requireAuth } from "../requireAuth"; import { logger } from "../logger"; diff --git a/src/commands/use.ts b/src/commands/use.ts index b9afe8b1570..9e797263e9c 100644 --- a/src/commands/use.ts +++ b/src/commands/use.ts @@ -1,11 +1,8 @@ import * as clc from "colorette"; import { Command } from "../command"; -import { - FirebaseProjectMetadata, - getFirebaseProject, - listFirebaseProjects, -} from "../management/projects"; +import { getFirebaseProject, listFirebaseProjects } from "../management/projects"; +import { FirebaseProjectMetadata } from "../types/project"; import { logger } from "../logger"; import { Options } from "../options"; import { prompt } from "../prompt"; diff --git a/src/defaultCredentials.ts b/src/defaultCredentials.ts index 74b67b17bf1..7526283ef93 100644 --- a/src/defaultCredentials.ts +++ b/src/defaultCredentials.ts @@ -2,7 +2,7 @@ import * as fs from "fs"; import * as path from "path"; import { clientId, clientSecret } from "./api"; -import { Tokens, User, Account } from "./auth"; +import { Tokens, User, Account } from "./types/auth"; import { logger } from "./logger"; // Interface for a valid JSON refresh token credential, so the diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 88556e87536..acd04b190ef 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -9,7 +9,7 @@ import * as semver from "semver"; import { URL } from "url"; import { EventEmitter } from "events"; -import { Account } from "../auth"; +import { Account } from "../types/auth"; import { logger } from "../logger"; import { track, trackEmulator } from "../track"; import { Constants } from "./constants"; diff --git a/src/init/features/account.ts b/src/init/features/account.ts index ee239817bba..9bb7fd5d859 100644 --- a/src/init/features/account.ts +++ b/src/init/features/account.ts @@ -5,9 +5,9 @@ import { loginAdditionalAccount, setActiveAccount, findAccountByEmail, - Account, setProjectAccount, } from "../../auth"; +import { Account } from "../../types/auth"; import { promptOnce } from "../../prompt"; import { FirebaseError } from "../../error"; diff --git a/src/init/features/project.ts b/src/init/features/project.ts index 4170a8d14ad..42787066c28 100644 --- a/src/init/features/project.ts +++ b/src/init/features/project.ts @@ -5,12 +5,12 @@ import { FirebaseError } from "../../error"; import { addFirebaseToCloudProjectAndLog, createFirebaseProjectAndLog, - FirebaseProjectMetadata, getFirebaseProject, getOrPromptProject, PROJECTS_CREATE_QUESTIONS, promptAvailableProjectId, } from "../../management/projects"; +import { FirebaseProjectMetadata } from "../../types/project"; import { logger } from "../../logger"; import { prompt, promptOnce } from "../../prompt"; import * as utils from "../../utils"; diff --git a/src/management/projects.ts b/src/management/projects.ts index 73ce49682ac..1cfc82e9377 100644 --- a/src/management/projects.ts +++ b/src/management/projects.ts @@ -8,38 +8,13 @@ import { Question, promptOnce } from "../prompt"; import * as api from "../api"; import { logger } from "../logger"; import * as utils from "../utils"; +import { FirebaseProjectMetadata, CloudProjectInfo, ProjectPage } from "../types/project"; const TIMEOUT_MILLIS = 30000; const MAXIMUM_PROMPT_LIST = 100; const PROJECT_LIST_PAGE_SIZE = 1000; const CREATE_PROJECT_API_REQUEST_TIMEOUT_MILLIS = 15000; -export interface CloudProjectInfo { - project: string /* The resource name of the GCP project: "projects/projectId" */; - displayName?: string; - locationId?: string; -} - -export interface ProjectPage { - projects: T[]; - nextPageToken?: string; -} - -export interface FirebaseProjectMetadata { - name: string /* The fully qualified resource name of the Firebase project */; - projectId: string; - projectNumber: string; - displayName: string; - resources?: DefaultProjectResources; -} - -export interface DefaultProjectResources { - hostingSite?: string; - realtimeDatabaseInstance?: string; - storageBucket?: string; - locationId?: string; -} - export enum ProjectParentResourceType { ORGANIZATION = "organization", FOLDER = "folder", diff --git a/src/requireAuth.ts b/src/requireAuth.ts index 5d2199ca5c0..792fce54df1 100644 --- a/src/requireAuth.ts +++ b/src/requireAuth.ts @@ -7,7 +7,8 @@ import { FirebaseError } from "./error"; import { logger } from "./logger"; import * as utils from "./utils"; import * as scopes from "./scopes"; -import { Tokens, User, setRefreshToken, setActiveAccount } from "./auth"; +import { Tokens, User } from "./types/auth"; +import { setRefreshToken, setActiveAccount } from "./auth"; const AUTH_ERROR_MESSAGE = `Command requires authentication, please run ${clc.bold( "firebase login" diff --git a/src/test/auth.spec.ts b/src/test/auth.spec.ts index e89a6777e64..d79f921510a 100644 --- a/src/test/auth.spec.ts +++ b/src/test/auth.spec.ts @@ -1,8 +1,15 @@ import { expect } from "chai"; import * as sinon from "sinon"; -import * as auth from "../auth"; +import { + getAdditionalAccounts, + getAllAccounts, + getGlobalDefaultAccount, + getProjectDefaultAccount, + selectAccount, +} from "../auth"; import { configstore } from "../configstore"; +import { Account } from "../types/auth"; describe("auth", () => { const sandbox: sinon.SinonSandbox = sinon.createSandbox(); @@ -33,13 +40,13 @@ describe("auth", () => { describe("no accounts", () => { it("returns no global account when config is empty", () => { - const account = auth.getGlobalDefaultAccount(); + const account = getGlobalDefaultAccount(); expect(account).to.be.undefined; }); }); describe("single account", () => { - const defaultAccount: auth.Account = { + const defaultAccount: Account = { user: { email: "test@test.com", }, @@ -54,24 +61,24 @@ describe("auth", () => { }); it("returns global default account", () => { - const account = auth.getGlobalDefaultAccount(); + const account = getGlobalDefaultAccount(); expect(account).to.deep.equal(defaultAccount); }); it("returns no additional accounts", () => { - const additional = auth.getAdditionalAccounts(); + const additional = getAdditionalAccounts(); expect(additional.length).to.equal(0); }); it("returns exactly one total account", () => { - const all = auth.getAllAccounts(); + const all = getAllAccounts(); expect(all.length).to.equal(1); expect(all[0]).to.deep.equal(defaultAccount); }); }); describe("multi account", () => { - const defaultAccount: auth.Account = { + const defaultAccount: Account = { user: { email: "test@test.com", }, @@ -80,7 +87,7 @@ describe("auth", () => { }, }; - const additionalUser1: auth.Account = { + const additionalUser1: Account = { user: { email: "test1@test.com", }, @@ -89,7 +96,7 @@ describe("auth", () => { }, }; - const additionalUser2: auth.Account = { + const additionalUser2: Account = { user: { email: "test2@test.com", }, @@ -98,7 +105,7 @@ describe("auth", () => { }, }; - const additionalAccounts: auth.Account[] = [additionalUser1, additionalUser2]; + const additionalAccounts: Account[] = [additionalUser1, additionalUser2]; const activeAccounts = { "/path/project1": "test1@test.com", @@ -112,32 +119,32 @@ describe("auth", () => { }); it("returns global default account", () => { - const account = auth.getGlobalDefaultAccount(); + const account = getGlobalDefaultAccount(); expect(account).to.deep.equal(defaultAccount); }); it("returns additional accounts", () => { - const additional = auth.getAdditionalAccounts(); + const additional = getAdditionalAccounts(); expect(additional).to.deep.equal(additionalAccounts); }); it("returns all accounts", () => { - const all = auth.getAllAccounts(); + const all = getAllAccounts(); expect(all).to.deep.equal([defaultAccount, ...additionalAccounts]); }); it("respects project default when present", () => { - const account = auth.getProjectDefaultAccount("/path/project1"); + const account = getProjectDefaultAccount("/path/project1"); expect(account).to.deep.equal(additionalUser1); }); it("ignores project default when not present", () => { - const account = auth.getProjectDefaultAccount("/path/project2"); + const account = getProjectDefaultAccount("/path/project2"); expect(account).to.deep.equal(defaultAccount); }); it("prefers account flag to project root", () => { - const account = auth.selectAccount("test2@test.com", "/path/project1"); + const account = selectAccount("test2@test.com", "/path/project1"); expect(account).to.deep.equal(additionalUser2); }); }); diff --git a/src/test/defaultCredentials.spec.ts b/src/test/defaultCredentials.spec.ts index 79666270625..c99ab0b02eb 100644 --- a/src/test/defaultCredentials.spec.ts +++ b/src/test/defaultCredentials.spec.ts @@ -7,7 +7,8 @@ import * as os from "os"; import * as api from "../api"; import { configstore } from "../configstore"; import * as defaultCredentials from "../defaultCredentials"; -import { Account, getGlobalDefaultAccount } from "../auth"; +import { getGlobalDefaultAccount } from "../auth"; +import { Account } from "../types/auth"; describe("defaultCredentials", () => { const sandbox: sinon.SinonSandbox = sinon.createSandbox(); diff --git a/src/test/init/features/project.spec.ts b/src/test/init/features/project.spec.ts index ba6dd76e16c..66890fd61df 100644 --- a/src/test/init/features/project.spec.ts +++ b/src/test/init/features/project.spec.ts @@ -7,8 +7,9 @@ import { doSetup } from "../../../init/features/project"; import * as projectManager from "../../../management/projects"; import * as prompt from "../../../prompt"; import { Config } from "../../../config"; +import { FirebaseProjectMetadata } from "../../../types/project"; -const TEST_FIREBASE_PROJECT: projectManager.FirebaseProjectMetadata = { +const TEST_FIREBASE_PROJECT: FirebaseProjectMetadata = { projectId: "my-project-123", projectNumber: "123456789", displayName: "my-project", diff --git a/src/test/management/projects.spec.ts b/src/test/management/projects.spec.ts index 690f85e4351..22f95eb15c6 100644 --- a/src/test/management/projects.spec.ts +++ b/src/test/management/projects.spec.ts @@ -7,6 +7,7 @@ import * as projectManager from "../../management/projects"; import * as pollUtils from "../../operation-poller"; import * as prompt from "../../prompt"; import { FirebaseError } from "../../error"; +import { CloudProjectInfo, FirebaseProjectMetadata } from "../../types/project"; const PROJECT_ID = "the-best-firebase-project"; const PROJECT_NUMBER = "1234567890"; @@ -24,7 +25,7 @@ const LOCATION_ID = "location-id"; const PAGE_TOKEN = "page-token"; const NEXT_PAGE_TOKEN = "next-page-token"; -const TEST_FIREBASE_PROJECT: projectManager.FirebaseProjectMetadata = { +const TEST_FIREBASE_PROJECT: FirebaseProjectMetadata = { projectId: "my-project-123", projectNumber: "123456789", displayName: "my-project", @@ -37,7 +38,7 @@ const TEST_FIREBASE_PROJECT: projectManager.FirebaseProjectMetadata = { }, }; -const ANOTHER_FIREBASE_PROJECT: projectManager.FirebaseProjectMetadata = { +const ANOTHER_FIREBASE_PROJECT: FirebaseProjectMetadata = { projectId: "another-project", projectNumber: "987654321", displayName: "another-project", @@ -45,19 +46,19 @@ const ANOTHER_FIREBASE_PROJECT: projectManager.FirebaseProjectMetadata = { resources: {}, }; -const TEST_CLOUD_PROJECT: projectManager.CloudProjectInfo = { +const TEST_CLOUD_PROJECT: CloudProjectInfo = { project: "projects/my-project-123", displayName: "my-project", locationId: "us-central", }; -const ANOTHER_CLOUD_PROJECT: projectManager.CloudProjectInfo = { +const ANOTHER_CLOUD_PROJECT: CloudProjectInfo = { project: "projects/another-project", displayName: "another-project", locationId: "us-central", }; -function generateFirebaseProjectList(counts: number): projectManager.FirebaseProjectMetadata[] { +function generateFirebaseProjectList(counts: number): FirebaseProjectMetadata[] { return Array.from(Array(counts), (_, i: number) => ({ name: `projects/project-id-${i}`, projectId: `project-id-${i}`, @@ -72,7 +73,7 @@ function generateFirebaseProjectList(counts: number): projectManager.FirebasePro })); } -function generateCloudProjectList(counts: number): projectManager.CloudProjectInfo[] { +function generateCloudProjectList(counts: number): CloudProjectInfo[] { return Array.from(Array(counts), (_, i: number) => ({ project: `projects/project-id-${i}`, displayName: `Project ${i}`, @@ -631,7 +632,7 @@ describe("Project management", () => { describe("getFirebaseProject", () => { it("should resolve with project information if it succeeds", async () => { - const expectedProjectInfo: projectManager.FirebaseProjectMetadata = { + const expectedProjectInfo: FirebaseProjectMetadata = { name: `projects/${PROJECT_ID}`, projectId: PROJECT_ID, displayName: PROJECT_NAME, diff --git a/src/types/auth/index.d.ts b/src/types/auth/index.d.ts new file mode 100644 index 00000000000..5ee4c2d746c --- /dev/null +++ b/src/types/auth/index.d.ts @@ -0,0 +1,46 @@ +// The wire protocol for an access token returned by Google. +// When we actually refresh from the server we should always have +// these optional fields, but when a user passes --token we may +// only have access_token. +export interface Tokens { + id_token?: string; + access_token: string; + refresh_token?: string; + scopes?: string[]; +} + +export interface User { + email: string; + + iss?: string; + azp?: string; + aud?: string; + sub?: number; + hd?: string; + email_verified?: boolean; + at_hash?: string; + iat?: number; + exp?: number; +} + +export interface Account { + user: User; + tokens: Tokens; +} +export interface TokensWithExpiration extends Tokens { + expires_at?: number; +} +export interface TokensWithTTL extends Tokens { + expires_in?: number; +} +export interface UserCredentials { + user: string | User; + tokens: TokensWithExpiration; + scopes: string[]; +} +// https://docs.github.com/en/developers/apps/authorizing-oauth-apps +export interface GitHubAuthResponse { + access_token: string; + scope: string; + token_type: string; +} diff --git a/src/types/project/index.d.ts b/src/types/project/index.d.ts new file mode 100644 index 00000000000..57241aefe20 --- /dev/null +++ b/src/types/project/index.d.ts @@ -0,0 +1,25 @@ +export interface CloudProjectInfo { + project: string /* The resource name of the GCP project: "projects/projectId" */; + displayName?: string; + locationId?: string; +} + +export interface ProjectPage { + projects: T[]; + nextPageToken?: string; +} + +export interface FirebaseProjectMetadata { + name: string /* The fully qualified resource name of the Firebase project */; + projectId: string; + projectNumber: string; + displayName: string; + resources?: DefaultProjectResources; +} + +export interface DefaultProjectResources { + hostingSite?: string; + realtimeDatabaseInstance?: string; + storageBucket?: string; + locationId?: string; +} From 83b0a70316ac11fe3e385aa6788e43ffcbdf81ab Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 21 Mar 2023 14:25:06 -0700 Subject: [PATCH 0835/1699] Adding emulator support for system params (#5617) * Adding emulator support for system params * Prettier * PR fixes and changelog entry --- CHANGELOG.md | 1 + src/extensions/emulator/optionsHelper.ts | 5 +- src/extensions/emulator/triggerHelper.ts | 48 ++++++++++++++++++- .../extensions/emulator/triggerHelper.spec.ts | 35 ++++++++++++++ 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4095e369cd..4d1fea3aa6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,5 @@ - Fix function deploy retry after quota exceeded bug and increase backoff. (#5601) - Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) - Adds 2nd gen firestore triggers to firebase deploy (#5592). +- Adds Extension emulator support for system params. - Adds `database:import` command for non-atomic imports (#5396). diff --git a/src/extensions/emulator/optionsHelper.ts b/src/extensions/emulator/optionsHelper.ts index 9d48aa6f42c..202909e4f1a 100644 --- a/src/extensions/emulator/optionsHelper.ts +++ b/src/extensions/emulator/optionsHelper.ts @@ -48,7 +48,7 @@ export async function buildOptions(options: any): Promise { * TODO: Better name? Also, should this be in extensionsEmulator instead? */ export async function getExtensionFunctionInfo( - instance: planner.InstanceSpec, + instance: planner.DeploymentInstanceSpec, paramValues: Record ): Promise<{ runtime: string; @@ -59,12 +59,13 @@ export async function getExtensionFunctionInfo( const spec = await planner.getExtensionSpec(instance); const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, paramValues); const extensionTriggers: ParsedTriggerDefinition[] = functionResources - .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r)) + .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r, instance.systemParams)) .map((trigger) => { trigger.name = `ext-${instance.instanceId}-${trigger.name}`; return trigger; }); const runtime = specHelper.getRuntime(functionResources); + const nonSecretEnv = getNonSecretEnv(spec.params, paramValues); const secretEnvVariables = getSecretEnvVars(spec.params, paramValues); return { diff --git a/src/extensions/emulator/triggerHelper.ts b/src/extensions/emulator/triggerHelper.ts index b27e864706c..12d2a150d47 100644 --- a/src/extensions/emulator/triggerHelper.ts +++ b/src/extensions/emulator/triggerHelper.ts @@ -14,11 +14,21 @@ import { } from "../../extensions/types"; import * as proto from "../../gcp/proto"; +const SUPPORTED_SYSTEM_PARAMS = { + "firebaseextensions.v1beta.function": { + regions: "firebaseextensions.v1beta.function/location", + timeoutSeconds: "firebaseextensions.v1beta.function/timeoutSeconds", + availableMemoryMb: "firebaseextensions.v1beta.function/memory", + labels: "firebaseextensions.v1beta.function/labels", + }, +}; + /** * Convert a Resource into a ParsedTriggerDefinition */ export function functionResourceToEmulatedTriggerDefintion( - resource: Resource + resource: Resource, + systemParams: Record = {} ): ParsedTriggerDefinition { const resourceType = resource.type; if (resource.type === FUNCTIONS_RESOURCE_TYPE) { @@ -27,6 +37,42 @@ export function functionResourceToEmulatedTriggerDefintion( entryPoint: resource.name, platform: "gcfv1", }; + // These get used today in the emultor. + proto.convertIfPresent( + etd, + systemParams, + "regions", + SUPPORTED_SYSTEM_PARAMS[FUNCTIONS_RESOURCE_TYPE].regions, + (str: string) => [str] + ); + proto.convertIfPresent( + etd, + systemParams, + "timeoutSeconds", + SUPPORTED_SYSTEM_PARAMS[FUNCTIONS_RESOURCE_TYPE].timeoutSeconds, + (d) => +d + ); + proto.convertIfPresent( + etd, + systemParams, + "availableMemoryMb", + SUPPORTED_SYSTEM_PARAMS[FUNCTIONS_RESOURCE_TYPE].availableMemoryMb, + (d) => +d as backend.MemoryOptions + ); + // These don't, but we inject them anyway for consistency and forward compatability + proto.convertIfPresent( + etd, + systemParams, + "labels", + SUPPORTED_SYSTEM_PARAMS[FUNCTIONS_RESOURCE_TYPE].labels, + (str: string): Record => { + const ret: Record = {}; + for (const [key, value] of str.split(",").map((label) => label.split(":"))) { + ret[key] = value; + } + return ret; + } + ); const properties = resource.properties || {}; proto.convertIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration); proto.convertIfPresent(etd, properties, "regions", "location", (str: string) => [str]); diff --git a/src/test/extensions/emulator/triggerHelper.spec.ts b/src/test/extensions/emulator/triggerHelper.spec.ts index 1f5812455b9..5ddb0bc686b 100644 --- a/src/test/extensions/emulator/triggerHelper.spec.ts +++ b/src/test/extensions/emulator/triggerHelper.spec.ts @@ -1,4 +1,5 @@ import { expect } from "chai"; +import { ParsedTriggerDefinition } from "../../../emulator/functionsEmulatorShared"; import * as triggerHelper from "../../../extensions/emulator/triggerHelper"; import { Resource } from "../../../extensions/types"; @@ -252,5 +253,39 @@ describe("triggerHelper", () => { expect(result).to.eql(expected); }); + + it("should correctly inject system params", () => { + const testResource: Resource = { + name: "test-resource", + entryPoint: "functionName", + type: "firebaseextensions.v1beta.function", + properties: { + httpsTrigger: {}, + }, + }; + const systemParams = { + "firebaseextensions.v1beta.function/location": "us-west1", + "firebaseextensions.v1beta.function/memory": "1024", + "firebaseextensions.v1beta.function/timeoutSeconds": "70", + "firebaseextensions.v1beta.function/labels": "key:val,otherkey:otherval", + }; + const expected: ParsedTriggerDefinition = { + platform: "gcfv1", + entryPoint: "test-resource", + name: "test-resource", + availableMemoryMb: 1024, + timeoutSeconds: 70, + labels: { key: "val", otherkey: "otherval" }, + regions: ["us-west1"], + httpsTrigger: {}, + }; + + const result = triggerHelper.functionResourceToEmulatedTriggerDefintion( + testResource, + systemParams + ); + + expect(result).to.eql(expected); + }); }); }); From a7446aaff696f83b33861414ac60a1c326792114 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 21 Mar 2023 14:49:23 -0700 Subject: [PATCH 0836/1699] Fix changelog (#5602) * Fix changelog. * Update CHANGELOG.md --------- Co-authored-by: joehan --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d1fea3aa6c..d042be0c071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ - Adds support for optional `--database` argument in Firestore commands (#5548). - Adds multiple firestore database targets support in firebase.json (#5548). -- Remove call to Cloud Run API and set CPU & concurrency in GCF API instead. (#5605) -- Fix function deploy retry after quota exceeded bug and increase backoff. (#5601) -- Fix bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) +- Removes call to Cloud Run API and set CPU & concurrency in GCF API instead. (#5605) +- Fixes function deploy retry after quota exceeded bug and increase backoff. (#5601) +- Fixes bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) - Adds 2nd gen firestore triggers to firebase deploy (#5592). - Adds Extension emulator support for system params. - Adds `database:import` command for non-atomic imports (#5396). From 4da61f6c1ae635e804eae0501ccd304cad68b705 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 21 Mar 2023 22:00:24 +0000 Subject: [PATCH 0837/1699] 11.25.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b74dd2e15e5..5c4ecc5acab 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.24.1", + "version": "11.25.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.24.1", + "version": "11.25.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 94f0ed8ac40..3e792b7ff64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.24.1", + "version": "11.25.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 010c43337a907468ec9b00fb7434276c6e83df83 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 21 Mar 2023 22:00:37 +0000 Subject: [PATCH 0838/1699] [firebase-release] Removed change log and reset repo after 11.25.0 release --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d042be0c071..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +0,0 @@ -- Adds support for optional `--database` argument in Firestore commands (#5548). -- Adds multiple firestore database targets support in firebase.json (#5548). -- Removes call to Cloud Run API and set CPU & concurrency in GCF API instead. (#5605) -- Fixes function deploy retry after quota exceeded bug and increase backoff. (#5601) -- Fixes bug where EVENTARC_CLOUD_EVENT_SOURCE environment variable was correctly set for some functions. (#5597) -- Adds 2nd gen firestore triggers to firebase deploy (#5592). -- Adds Extension emulator support for system params. -- Adds `database:import` command for non-atomic imports (#5396). From 48e18f93ac4c0935d416dfbe1b0c38db802f3631 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 21 Mar 2023 16:08:17 -0700 Subject: [PATCH 0839/1699] Adding p-limit as a direct dependency (#5620) * Adding p-limit as a direct dependency * Add changelog * Use * as pLimit * prettier --------- Co-authored-by: Bryan Kendall --- CHANGELOG.md | 1 + npm-shrinkwrap.json | 176 +++++++++++++++++++++++++---------------- package.json | 1 + src/database/import.ts | 3 +- 4 files changed, 113 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..54e50c2a6d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes a missing dependency on `p-limit`. (#5619) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5c4ecc5acab..00707e7a736 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -47,6 +47,7 @@ "node-fetch": "^2.6.7", "open": "^6.3.0", "ora": "^5.4.1", + "p-limit": "^3.0.1", "portfinder": "^1.0.32", "progress": "^2.0.3", "proxy-agent": "^5.0.0", @@ -1904,22 +1905,6 @@ "node": ">=10.0.0" } }, - "node_modules/@google-cloud/storage/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==", - "dev": true, - "optional": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@google/events": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@google/events/-/events-5.1.1.tgz", @@ -2103,6 +2088,21 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -11512,6 +11512,21 @@ "node": ">=8" } }, + "node_modules/nyc/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nyc/node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -12081,15 +12096,17 @@ } }, "node_modules/p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { @@ -12107,21 +12124,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate/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==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-map": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", @@ -12368,6 +12370,21 @@ "node": ">=8" } }, + "node_modules/pkg-dir/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -13032,6 +13049,21 @@ "node": ">=8" } }, + "node_modules/read-pkg-up/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/read-pkg-up/node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -15815,7 +15847,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "engines": { "node": ">=10" }, @@ -17309,16 +17340,6 @@ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, "optional": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "optional": true, - "requires": { - "yocto-queue": "^0.1.0" - } } } }, @@ -17462,6 +17483,15 @@ "p-locate": "^4.1.0" } }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -24655,6 +24685,15 @@ "p-locate": "^4.1.0" } }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -25086,12 +25125,11 @@ "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==" }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { @@ -25101,17 +25139,6 @@ "dev": true, "requires": { "p-limit": "^3.0.2" - }, - "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } } }, "p-map": { @@ -25297,6 +25324,15 @@ "p-locate": "^4.1.0" } }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -25806,6 +25842,15 @@ "p-locate": "^4.1.0" } }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -27907,8 +27952,7 @@ "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==", - "dev": true + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, "zip-stream": { "version": "4.1.0", diff --git a/package.json b/package.json index 3e792b7ff64..6c9a0c5003e 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "node-fetch": "^2.6.7", "open": "^6.3.0", "ora": "^5.4.1", + "p-limit": "^3.0.1", "portfinder": "^1.0.32", "progress": "^2.0.3", "proxy-agent": "^5.0.0", diff --git a/src/database/import.ts b/src/database/import.ts index df977f04f9a..4852a092b22 100644 --- a/src/database/import.ts +++ b/src/database/import.ts @@ -4,11 +4,10 @@ import * as Filter from "stream-json/filters/Filter"; import * as stream from "stream"; import * as StreamObject from "stream-json/streamers/StreamObject"; -import pLimit from "p-limit"; - import { URL } from "url"; import { Client, ClientResponse } from "../apiv2"; import { FirebaseError } from "../error"; +import * as pLimit from "p-limit"; const MAX_CHUNK_SIZE = 1024 * 1024 * 10; const CONCURRENCY_LIMIT = 5; From 159498f5a28eea5af8e75386d157862190a7842b Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 21 Mar 2023 23:18:53 +0000 Subject: [PATCH 0840/1699] 11.25.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 00707e7a736..9caf54c65f1 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.25.0", + "version": "11.25.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.25.0", + "version": "11.25.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 6c9a0c5003e..aade16191c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.25.0", + "version": "11.25.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 8484e4a5aaf6610b669c4c95a6530719ebf19080 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 21 Mar 2023 23:19:06 +0000 Subject: [PATCH 0841/1699] [firebase-release] Removed change log and reset repo after 11.25.1 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e50c2a6d8..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes a missing dependency on `p-limit`. (#5619) From f200dcb4865763a05c0426f4c3ec9640c9c4ff7f Mon Sep 17 00:00:00 2001 From: yixiaoshen Date: Thu, 23 Mar 2023 13:10:49 -0700 Subject: [PATCH 0842/1699] Release Cloud Firestore Emulator v1.16.1. (#5575) * Release Cloud Firestore Emulator v1.16.1. * Update CHANGELOG.md --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..a8896516bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Releases Cloud Firestore emulator v1.16.1, which adds support for read_time in ListCollectionIds. diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index db217e292ee..1b41d302f78 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -33,9 +33,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet expectedChecksum: "311609538bd65666eb724ef47c2e6466", }, firestore: { - version: "1.16.0", - expectedSize: 63422812, - expectedChecksum: "6c1a43c1b327d534f83f7386c595d7ff", + version: "1.16.1", + expectedSize: 64213741, + expectedChecksum: "befa5f6cbe258e787bf0bbb4eb9da2c8", }, storage: { version: "1.1.3", From b14b5f38fe23da6543778a588811b0e2391427c0 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 23 Mar 2023 13:40:16 -0700 Subject: [PATCH 0843/1699] Update more import assignments (#5628) Co-authored-by: joehan --- src/commands/database-instances-list.ts | 2 +- src/deploy/firestore/deploy.ts | 2 +- src/deploy/firestore/prepare.ts | 2 +- src/emulator/hostingEmulator.ts | 2 +- src/init/features/firestore/indexes.ts | 4 ++-- src/init/features/firestore/rules.ts | 6 +++--- src/init/features/remoteconfig.ts | 2 +- src/test/rulesDeploy.spec.ts | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/commands/database-instances-list.ts b/src/commands/database-instances-list.ts index d0263e6e2a6..5328715b1bc 100644 --- a/src/commands/database-instances-list.ts +++ b/src/commands/database-instances-list.ts @@ -6,7 +6,7 @@ import * as ora from "ora"; import { logger } from "../logger"; import { requirePermissions } from "../requirePermissions"; import { needProjectNumber } from "../projectUtils"; -import firedata = require("../gcp/firedata"); +import * as firedata from "../gcp/firedata"; import { Emulators } from "../emulator/types"; import { warnEmulatorNotSupported } from "../emulator/commandUtils"; import * as experiments from "../experiments"; diff --git a/src/deploy/firestore/deploy.ts b/src/deploy/firestore/deploy.ts index b4730f43584..e2bef67cab3 100644 --- a/src/deploy/firestore/deploy.ts +++ b/src/deploy/firestore/deploy.ts @@ -2,7 +2,7 @@ import * as clc from "colorette"; import { FirestoreIndexes } from "../../firestore/indexes"; import { logger } from "../../logger"; -import utils = require("../../utils"); +import * as utils from "../../utils"; import { RulesDeploy, RulesetServiceType } from "../../rulesDeploy"; import { IndexContext } from "./prepare"; diff --git a/src/deploy/firestore/prepare.ts b/src/deploy/firestore/prepare.ts index d3cdd8bcfd4..75a61f8bb20 100644 --- a/src/deploy/firestore/prepare.ts +++ b/src/deploy/firestore/prepare.ts @@ -2,7 +2,7 @@ import * as clc from "colorette"; import { loadCJSON } from "../../loadCJSON"; import { RulesDeploy, RulesetServiceType } from "../../rulesDeploy"; -import utils = require("../../utils"); +import * as utils from "../../utils"; import { Options } from "../../options"; import * as fsConfig from "../../firestore/fsConfig"; diff --git a/src/emulator/hostingEmulator.ts b/src/emulator/hostingEmulator.ts index 7e9323c8fea..ab041ed1f16 100644 --- a/src/emulator/hostingEmulator.ts +++ b/src/emulator/hostingEmulator.ts @@ -1,4 +1,4 @@ -import serveHosting = require("../serve/hosting"); +import * as serveHosting from "../serve/hosting"; import { EmulatorInfo, EmulatorInstance, Emulators } from "../emulator/types"; import { Constants } from "./constants"; diff --git a/src/init/features/firestore/indexes.ts b/src/init/features/firestore/indexes.ts index 2fe0f8c4238..268a10ca06b 100644 --- a/src/init/features/firestore/indexes.ts +++ b/src/init/features/firestore/indexes.ts @@ -2,8 +2,8 @@ import * as clc from "colorette"; import * as fs from "fs"; import { FirebaseError } from "../../../error"; -import iv2 = require("../../../firestore/indexes"); -import fsutils = require("../../../fsutils"); +import * as iv2 from "../../../firestore/indexes"; +import * as fsutils from "../../../fsutils"; import { prompt, promptOnce } from "../../../prompt"; import { logger } from "../../../logger"; diff --git a/src/init/features/firestore/rules.ts b/src/init/features/firestore/rules.ts index 9e24d2c30f2..31ca5d9b276 100644 --- a/src/init/features/firestore/rules.ts +++ b/src/init/features/firestore/rules.ts @@ -1,11 +1,11 @@ import * as clc from "colorette"; import * as fs from "fs"; -import gcp = require("../../../gcp"); -import fsutils = require("../../../fsutils"); +import * as gcp from "../../../gcp"; +import * as fsutils from "../../../fsutils"; import { prompt, promptOnce } from "../../../prompt"; import { logger } from "../../../logger"; -import utils = require("../../../utils"); +import * as utils from "../../../utils"; const DEFAULT_RULES_FILE = "firestore.rules"; diff --git a/src/init/features/remoteconfig.ts b/src/init/features/remoteconfig.ts index be17f92ebb8..fa45e22718e 100644 --- a/src/init/features/remoteconfig.ts +++ b/src/init/features/remoteconfig.ts @@ -1,5 +1,5 @@ import { promptOnce } from "../../prompt"; -import fsutils = require("../../fsutils"); +import * as fsutils from "../../fsutils"; import * as clc from "colorette"; import { Config } from "../../config"; diff --git a/src/test/rulesDeploy.spec.ts b/src/test/rulesDeploy.spec.ts index f0750793673..4b9bc1b7f14 100644 --- a/src/test/rulesDeploy.spec.ts +++ b/src/test/rulesDeploy.spec.ts @@ -9,7 +9,7 @@ import * as projectNumber from "../getProjectNumber"; import { readFileSync } from "fs-extra"; import { RulesetFile } from "../gcp/rules"; import { Config } from "../config"; -import gcp = require("../gcp"); +import * as gcp from "../gcp"; import { RulesDeploy, RulesetServiceType } from "../rulesDeploy"; From bff5da7a0af38a27cc83259bd8189d66158bdac2 Mon Sep 17 00:00:00 2001 From: Thomas Burke <40719837+thomasmburke@users.noreply.github.com> Date: Tue, 28 Mar 2023 10:41:41 -0700 Subject: [PATCH 0844/1699] quote entire custom claims object and escape inner quotes (#5630) * quote entire custom claims object and escape inner quotes --------- Co-authored-by: Thomas Burke Co-authored-by: joehan --- CHANGELOG.md | 1 + src/accountExporter.ts | 5 ++- src/test/accountExporter.spec.ts | 66 ++++++++++++++------------------ 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8896516bc6..997059b4179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Releases Cloud Firestore emulator v1.16.1, which adds support for read_time in ListCollectionIds. +- Fixes auth:export with csv format for users with custom claims. (#3319) diff --git a/src/accountExporter.ts b/src/accountExporter.ts index 99d4982dc72..53c9b239091 100644 --- a/src/accountExporter.ts +++ b/src/accountExporter.ts @@ -84,7 +84,10 @@ function transUserToArray(user: any): any[] { arr[24] = user.lastLoginAt; arr[25] = user.phoneNumber; arr[26] = user.disabled; - arr[27] = user.customAttributes; + // quote entire custom claims object and escape inner quotes with quotes + arr[27] = user.customAttributes + ? `"${user.customAttributes.replace(/(? { }); it("should call api.request multiple times for JSON export", async () => { - nock("https://www.googleapis.com") - .post("/identitytoolkit/v3/relyingparty/downloadAccount", { - maxResults: 3, - targetProjectId: "test-project-id", - }) - .reply(200, { - users: userList.slice(0, 3), - nextPageToken: "3", - }) - .post("/identitytoolkit/v3/relyingparty/downloadAccount", { - maxResults: 3, - nextPageToken: "3", - targetProjectId: "test-project-id", - }) - .reply(200, { - users: userList.slice(3, 6), - nextPageToken: "6", - }) - .post("/identitytoolkit/v3/relyingparty/downloadAccount", { - maxResults: 3, - nextPageToken: "6", - targetProjectId: "test-project-id", - }) - .reply(200, { - users: userList.slice(6, 7), - nextPageToken: "7", - }) - .post("/identitytoolkit/v3/relyingparty/downloadAccount", { - maxResults: 3, - nextPageToken: "7", - targetProjectId: "test-project-id", - }) - .reply(200, { - users: [], - nextPageToken: "7", - }); + mockAllUsersRequests(); await serialExportUsers("test-project-id", { format: "JSON", @@ -216,7 +181,7 @@ describe("accountExporter", () => { expect(nock.isDone()).to.be.true; }); - it("should export a user's custom attributes", async () => { + it("should export a user's custom attributes for JSON formats", async () => { userList[0].customAttributes = '{ "customBoolean": true, "customString": "test", "customInt": 99 }'; userList[1].customAttributes = @@ -244,6 +209,33 @@ describe("accountExporter", () => { expect(nock.isDone()).to.be.true; }); + it("should export a user's custom attributes for CSV formats", async () => { + userList[0].customAttributes = + '{ "customBoolean": true, "customString": "test", "customInt": 99 }'; + userList[1].customAttributes = '{ "customBoolean": true }'; + nock("https://www.googleapis.com") + .post("/identitytoolkit/v3/relyingparty/downloadAccount", { + maxResults: 3, + targetProjectId: "test-project-id", + }) + .reply(200, { + users: userList.slice(0, 3), + }); + await serialExportUsers("test-project-id", { + format: "JSON", + batchSize: 3, + writeStream: writeStream, + }); + expect(spyWrite.getCall(0).args[0]).to.eq(JSON.stringify(userList[0], null, 2)); + expect(spyWrite.getCall(1).args[0]).to.eq( + "," + os.EOL + JSON.stringify(userList[1], null, 2) + ); + expect(spyWrite.getCall(2).args[0]).to.eq( + "," + os.EOL + JSON.stringify(userList[2], null, 2) + ); + expect(nock.isDone()).to.be.true; + }); + function mockAllUsersRequests(): void { nock("https://www.googleapis.com") .post("/identitytoolkit/v3/relyingparty/downloadAccount", { From 9b65124943434172c8cca38cf3860487b7083189 Mon Sep 17 00:00:00 2001 From: Kevin Cheung Date: Tue, 28 Mar 2023 11:33:26 -0700 Subject: [PATCH 0845/1699] Fix typo in Extensions docs (#5632) Co-authored-by: joehan --- templates/extensions/javascript/WELCOME.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/extensions/javascript/WELCOME.md b/templates/extensions/javascript/WELCOME.md index 04423133bb0..d5e69d5b7f7 100644 --- a/templates/extensions/javascript/WELCOME.md +++ b/templates/extensions/javascript/WELCOME.md @@ -1,4 +1,4 @@ -This directory now contains the source files for a simple extension called **greet-the-world**. You can try it out right away in the Firebase Emulator suite - just naviagte to the integration-test directory and run: +This directory now contains the source files for a simple extension called **greet-the-world**. You can try it out right away in the Firebase Emulator suite - just navigate to the integration-test directory and run: `firebase emulators:start --project=` From 4c955356774dea65f81a476fe5e7f89e5c744943 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 28 Mar 2023 21:17:18 +0000 Subject: [PATCH 0846/1699] 11.25.2 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 9caf54c65f1..f6f0e7ade3f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.25.1", + "version": "11.25.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.25.1", + "version": "11.25.2", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index aade16191c7..d654404e930 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.25.1", + "version": "11.25.2", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 4f947cf97bc95cb12c652ee25339ade29dc8f5ea Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 28 Mar 2023 21:17:40 +0000 Subject: [PATCH 0847/1699] [firebase-release] Removed change log and reset repo after 11.25.2 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 997059b4179..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Releases Cloud Firestore emulator v1.16.1, which adds support for read_time in ListCollectionIds. -- Fixes auth:export with csv format for users with custom claims. (#3319) From 616e9f672a973866e1d4a308791dab8f858485d6 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 29 Mar 2023 09:58:54 -0700 Subject: [PATCH 0848/1699] Switches integration tests to package firebase-tools (#5625) * Switches integration tests to package firebase-tools instead of npm i && npm link * Make executable * PR fixes * pack to the correct location --- scripts/clean-install.sh | 21 +++++++++++++++++++++ scripts/emulator-import-export-tests/run.sh | 2 +- scripts/extensions-deploy-tests/run.sh | 2 +- scripts/extensions-emulator-tests/run.sh | 2 +- scripts/frameworks-tests/run.sh | 2 +- scripts/functions-deploy-tests/run.sh | 2 +- scripts/functions-discover-tests/run.sh | 2 +- scripts/hosting-tests/run.sh | 2 +- scripts/npm-link.sh | 7 ------- scripts/storage-deploy-tests/run.sh | 2 +- scripts/storage-emulator-integration/run.sh | 2 +- scripts/triggers-end-to-end-tests/run.sh | 2 +- scripts/webframeworks-deploy-tests/run.sh | 2 +- 13 files changed, 32 insertions(+), 18 deletions(-) create mode 100755 scripts/clean-install.sh delete mode 100755 scripts/npm-link.sh diff --git a/scripts/clean-install.sh b/scripts/clean-install.sh new file mode 100755 index 00000000000..8bfea835c92 --- /dev/null +++ b/scripts/clean-install.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +function cleanup() { + echo "Cleaning up artifacts..." + rm -rf ./clean + echo "Artifacts deleted." +} + +trap cleanup EXIT + +echo "Running clean-publish --without-publish, as we would before publishing to npm..." +npx clean-publish --without-publish --before-script ./scripts/clean-shrinkwrap.sh --temp-dir clean +echo "Ran clean-publish --without-publish." +echo "Packaging cleaned firebase-tools..." +cd ./clean +PACKED=$(npm pack --pack-destination ./ | tail -n 1) +echo "Packaged firebase-tools to $PACKED." +echo "Installing clean-packaged firebase-tools..." +npm install -g $PACKED +echo "Installed clean-packaged firebase-tools." diff --git a/scripts/emulator-import-export-tests/run.sh b/scripts/emulator-import-export-tests/run.sh index 1121c68a521..96efb4e8edd 100755 --- a/scripts/emulator-import-export-tests/run.sh +++ b/scripts/emulator-import-export-tests/run.sh @@ -1,6 +1,6 @@ #!/bin/bash source scripts/set-default-credentials.sh -./scripts/npm-link.sh +./scripts/clean-install.sh npx mocha --exit scripts/emulator-import-export-tests/tests.ts \ No newline at end of file diff --git a/scripts/extensions-deploy-tests/run.sh b/scripts/extensions-deploy-tests/run.sh index 7b7490b66eb..a02c539c814 100755 --- a/scripts/extensions-deploy-tests/run.sh +++ b/scripts/extensions-deploy-tests/run.sh @@ -2,6 +2,6 @@ set -e # Immediately exit on failure # Globally link the CLI for the testing framework -./scripts/npm-link.sh +./scripts/clean-install.sh mocha scripts/extensions-deploy-tests/tests.ts diff --git a/scripts/extensions-emulator-tests/run.sh b/scripts/extensions-emulator-tests/run.sh index b399bbc04d8..6e6acb50cd1 100755 --- a/scripts/extensions-emulator-tests/run.sh +++ b/scripts/extensions-emulator-tests/run.sh @@ -1,7 +1,7 @@ #!/bin/bash source scripts/set-default-credentials.sh -./scripts/npm-link.sh +./scripts/clean-install.sh ( cd scripts/extensions-emulator-tests/functions diff --git a/scripts/frameworks-tests/run.sh b/scripts/frameworks-tests/run.sh index f21bc96cc4a..be97ecbc507 100755 --- a/scripts/frameworks-tests/run.sh +++ b/scripts/frameworks-tests/run.sh @@ -10,7 +10,7 @@ echo "Running with Application Creds: ${GOOGLE_APPLICATION_CREDENTIALS}" echo "Target project: ${FBTOOLS_TARGET_PROJECT}" echo "Installing firebase-tools..." -./scripts/npm-link.sh +./scripts/clean-install.sh echo "Installed firebase-tools: $(which firebase)" echo "Enabling experiment..." diff --git a/scripts/functions-deploy-tests/run.sh b/scripts/functions-deploy-tests/run.sh index 3fdff889c9e..57bacc6ab10 100755 --- a/scripts/functions-deploy-tests/run.sh +++ b/scripts/functions-deploy-tests/run.sh @@ -2,7 +2,7 @@ set -e # Immediately exit on failure # Globally link the CLI for the testing framework -./scripts/npm-link.sh +./scripts/clean-install.sh # Create a secret for testing if it doesn't exist firebase functions:secrets:get TOP --project $GCLOUD_PROJECT || (echo secret | firebase functions:secrets:set --data-file=- TOP --project $GCLOUD_PROJECT -f) diff --git a/scripts/functions-discover-tests/run.sh b/scripts/functions-discover-tests/run.sh index a67e2dcc8a8..92a12377fb3 100755 --- a/scripts/functions-discover-tests/run.sh +++ b/scripts/functions-discover-tests/run.sh @@ -3,7 +3,7 @@ set -euxo pipefail # bash strict mode IFS=$'\n\t' # Globally link the CLI for the testing framework -./scripts/npm-link.sh +./scripts/clean-install.sh # Unlock internal commands for discovering functions in a project. firebase experiments:enable internaltesting diff --git a/scripts/hosting-tests/run.sh b/scripts/hosting-tests/run.sh index 608b298f0da..6ca9616917f 100755 --- a/scripts/hosting-tests/run.sh +++ b/scripts/hosting-tests/run.sh @@ -22,7 +22,7 @@ TEMP_DIR="$(mktemp -d)" echo "Created temp directory: ${TEMP_DIR}" echo "Installing firebase-tools..." -./scripts/npm-link.sh +./scripts/clean-install.sh echo "Installed firebase-tools: $(which firebase)" echo "Initializing temp directory..." diff --git a/scripts/npm-link.sh b/scripts/npm-link.sh deleted file mode 100755 index dc3674b3494..00000000000 --- a/scripts/npm-link.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "Running npm link..." -npm link - -chmod u+rx ./lib/bin/firebase.js diff --git a/scripts/storage-deploy-tests/run.sh b/scripts/storage-deploy-tests/run.sh index 4ed6394dcc1..65e5ec33f18 100755 --- a/scripts/storage-deploy-tests/run.sh +++ b/scripts/storage-deploy-tests/run.sh @@ -23,7 +23,7 @@ TEMP_DIR="$(mktemp -d)" echo "Created temp directory: ${TEMP_DIR}" echo "Installing firebase-tools..." -./scripts/npm-link.sh +./scripts/clean-install.sh echo "Installed firebase-tools: $(which firebase)" echo "Initializing temp directory..." diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index 078be5c9b9b..5868973e74e 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -2,7 +2,7 @@ set -e # Immediately exit on failure # Globally link the CLI for the testing framework -./scripts/npm-link.sh +./scripts/clean-install.sh # Set application default credentials. source scripts/set-default-credentials.sh diff --git a/scripts/triggers-end-to-end-tests/run.sh b/scripts/triggers-end-to-end-tests/run.sh index 1b0ef50a281..b4ffcd00e83 100755 --- a/scripts/triggers-end-to-end-tests/run.sh +++ b/scripts/triggers-end-to-end-tests/run.sh @@ -19,7 +19,7 @@ function cleanup() { trap cleanup EXIT source scripts/set-default-credentials.sh -./scripts/npm-link.sh +./scripts/clean-install.sh for dir in triggers v1 v2; do ( diff --git a/scripts/webframeworks-deploy-tests/run.sh b/scripts/webframeworks-deploy-tests/run.sh index 6e759d28c77..6017be3d640 100755 --- a/scripts/webframeworks-deploy-tests/run.sh +++ b/scripts/webframeworks-deploy-tests/run.sh @@ -2,7 +2,7 @@ set -e # Immediately exit on failure # Globally link the CLI for the testing framework -./scripts/npm-link.sh +./scripts/clean-install.sh (cd scripts/webframeworks-deploy-tests/hosting; npm i; npm run build) From 6d25faad77522c804730f42e14c49f8bb0f237bd Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 30 Mar 2023 01:18:59 -0400 Subject: [PATCH 0849/1699] Web frameworks simpleProxy (#5582) --- CHANGELOG.md | 1 + src/frameworks/angular/index.ts | 5 ++- src/frameworks/index.ts | 59 ++++----------------------------- src/frameworks/next/index.ts | 10 +++--- src/frameworks/utils.ts | 52 +++++++++++++++++++++++++++++ src/frameworks/vite/index.ts | 5 ++- 6 files changed, 68 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..fa68f7e3094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- The hosting emulator integration with web frameworks now has improved support for HMR and dev-tools. (#5582) diff --git a/src/frameworks/angular/index.ts b/src/frameworks/angular/index.ts index 4f6dcbb8feb..8f2c18f26ac 100644 --- a/src/frameworks/angular/index.ts +++ b/src/frameworks/angular/index.ts @@ -14,8 +14,7 @@ import { SupportLevel, } from ".."; import { promptOnce } from "../../prompt"; -import { proxyRequestHandler } from "../../hosting/proxy"; -import { warnIfCustomBuildScript } from "../utils"; +import { simpleProxy, warnIfCustomBuildScript } from "../utils"; export const name = "Angular"; export const support = SupportLevel.Experimental; @@ -106,7 +105,7 @@ export async function getDevModeHandle(dir: string) { process.stderr.write(data); }); }); - return proxyRequestHandler(await host, "Angular Live Development Server", { forceCascade: true }); + return simpleProxy(await host); } export async function ɵcodegenPublicDirectory(sourceDir: string, destDir: string) { diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index ce6bd18838b..60d28f41b84 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -391,7 +391,9 @@ export async function prepareFrameworks( } } } - if (firebaseDefaults) process.env.__FIREBASE_DEFAULTS__ = JSON.stringify(firebaseDefaults); + if (firebaseDefaults) { + process.env.__FIREBASE_DEFAULTS__ = JSON.stringify(firebaseDefaults); + } const results = await discover(getProjectPath()); if (!results) throw new Error("Epic fail."); const { framework, mayWantBackend, publicDirectory } = results; @@ -442,7 +444,10 @@ export async function prepareFrameworks( } config.webFramework = `${framework}${codegenFunctionsDirectory ? "_ssr" : ""}`; if (codegenFunctionsDirectory) { - if (firebaseDefaults) firebaseDefaults._authTokenSyncURL = "/__session"; + if (firebaseDefaults) { + firebaseDefaults._authTokenSyncURL = "/__session"; + process.env.__FIREBASE_DEFAULTS__ = JSON.stringify(firebaseDefaults); + } const rewrite: HostingRewrites = { source: "**", @@ -625,53 +630,3 @@ function codegenDevModeFunctionsDirectory() { const packageJson = {}; return Promise.resolve({ packageJson, frameworksEntry: "_devMode" }); } - -/** - * - */ -export function createServerResponseProxy( - req: IncomingMessage, - res: ServerResponse, - next: () => void -) { - const proxiedRes = new ServerResponse(req); - const buffer: [string, any[]][] = []; - proxiedRes.write = new Proxy(proxiedRes.write.bind(proxiedRes), { - apply: (target: any, thisArg, args) => { - target.call(thisArg, ...args); - buffer.push(["write", args]); - }, - }); - proxiedRes.setHeader = new Proxy(proxiedRes.setHeader.bind(proxiedRes), { - apply: (target: any, thisArg, args) => { - target.call(thisArg, ...args); - buffer.push(["setHeader", args]); - }, - }); - proxiedRes.removeHeader = new Proxy(proxiedRes.removeHeader.bind(proxiedRes), { - apply: (target: any, thisArg, args) => { - target.call(thisArg, ...args); - buffer.push(["removeHeader", args]); - }, - }); - proxiedRes.writeHead = new Proxy(proxiedRes.writeHead.bind(proxiedRes), { - apply: (target: any, thisArg, args) => { - target.call(thisArg, ...args); - buffer.push(["writeHead", args]); - }, - }); - proxiedRes.end = new Proxy(proxiedRes.end.bind(proxiedRes), { - apply: (target: any, thisArg, args) => { - target.call(thisArg, ...args); - if (proxiedRes.statusCode === 404) { - next(); - } else { - for (const [fn, args] of buffer) { - (res as any)[fn](...args); - } - res.end(...args); - } - }, - }); - return proxiedRes; -} diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index f03dd4f5f2c..cfc0492692c 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -19,7 +19,6 @@ import { streamObject } from "stream-json/streamers/StreamObject"; import { BuildResult, - createServerResponseProxy, findDependency, FrameworkType, NODE_VERSION, @@ -40,7 +39,7 @@ import { allDependencyNames, } from "./utils"; import type { Manifest, NpmLsDepdendency } from "./interfaces"; -import { readJSON } from "../utils"; +import { readJSON, simpleProxy } from "../utils"; import { warnIfCustomBuildScript } from "../utils"; import type { EmulatorInfo } from "../../emulator/types"; import { usesAppDirRouter, usesNextImage, hasUnoptimizedImage } from "./utils"; @@ -444,11 +443,10 @@ export async function getDevModeHandle(dir: string, hostingEmulatorInfo?: Emulat const handler = nextApp.getRequestHandler(); await nextApp.prepare(); - return (req: IncomingMessage, res: ServerResponse, next: () => void) => { + return simpleProxy(async (req: IncomingMessage, res: ServerResponse) => { const parsedUrl = parse(req.url!, true); - const proxy = createServerResponseProxy(req, res, next); - handler(req, proxy, parsedUrl); - }; + await handler(req, res, parsedUrl); + }); } async function getConfig(dir: string): Promise { diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index 2024df5e1b6..7857b3e20c7 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -2,6 +2,8 @@ import { readJSON as originalReadJSON } from "fs-extra"; import type { ReadOptions } from "fs-extra"; import { join } from "path"; import { readFile } from "fs/promises"; +import { IncomingMessage, request as httpRequest, ServerResponse, Agent } from "http"; +import { logger } from "../logger"; /** * Whether the given string starts with http:// or https:// @@ -39,3 +41,53 @@ export async function warnIfCustomBuildScript( ); } } + +type RequestHandler = (req: IncomingMessage, res: ServerResponse) => Promise; + +export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { + const agent = new Agent({ keepAlive: true }); + return async (originalReq: IncomingMessage, originalRes: ServerResponse, next: () => void) => { + const { method, headers, url: path } = originalReq; + if (!method || !path) { + return originalRes.end(); + } + // If the path is a the auth token sync URL pass through to Cloud Functions + const firebaseDefaultsJSON = process.env.__FIREBASE_DEFAULTS__; + const authTokenSyncURL: string | undefined = + firebaseDefaultsJSON && JSON.parse(firebaseDefaultsJSON)._authTokenSyncURL; + if (path === authTokenSyncURL) { + return next(); + } + if (typeof hostOrRequestHandler === "string") { + const host = hostOrRequestHandler; + const { hostname, port, protocol, username, password } = new URL(host); + const auth = username || password ? `${username}:${password}` : undefined; + const opts = { + agent, + auth, + protocol, + hostname, + port, + path, + method, + headers: { + ...headers, + host, + "X-Forwarded-Host": headers.host, + }, + }; + const req = httpRequest(opts, (response) => { + const { statusCode, statusMessage, headers } = response; + originalRes.writeHead(statusCode!, statusMessage, headers); + response.pipe(originalRes); + }); + originalReq.pipe(req); + req.on("error", (err) => { + logger.debug("Error encountered while proxying request:", method, path, err); + originalRes.end(); + }); + } else { + await hostOrRequestHandler(originalReq, originalRes); + } + }; +} diff --git a/src/frameworks/vite/index.ts b/src/frameworks/vite/index.ts index 9979c201484..caefbe5958b 100644 --- a/src/frameworks/vite/index.ts +++ b/src/frameworks/vite/index.ts @@ -4,9 +4,8 @@ import { existsSync } from "fs"; import { copy, pathExists } from "fs-extra"; import { join } from "path"; import { findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; -import { proxyRequestHandler } from "../../hosting/proxy"; import { promptOnce } from "../../prompt"; -import { warnIfCustomBuildScript } from "../utils"; +import { simpleProxy, warnIfCustomBuildScript } from "../utils"; export const name = "Vite"; export const support = SupportLevel.Experimental; @@ -92,7 +91,7 @@ export async function getDevModeHandle(dir: string) { process.stderr.write(data); }); }); - return proxyRequestHandler(await host, "Vite Development Server", { forceCascade: true }); + return simpleProxy(await host); } async function getConfig(root: string) { From 37cf3c544440eb071ed88afe7769fc4c795365c1 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Thu, 30 Mar 2023 13:10:42 -0700 Subject: [PATCH 0850/1699] Adding proof-of-concept for sourcing frameworks docs here in GH (#5637) * Adding Angular guide and include files. * Adding README for frameworks docs. * Going ahead and adding Express and Next.js guides as well. * Formatting --------- Co-authored-by: James Daniels --- src/frameworks/docs/README.md | 86 ++++++++ .../docs/_includes/_before-you-begin.md | 10 + .../docs/_includes/_initialize-firebase.md | 12 ++ .../docs/_includes/_preview-disclaimer.md | 4 + src/frameworks/docs/angular.md | 170 ++++++++++++++++ src/frameworks/docs/express.md | 184 ++++++++++++++++++ src/frameworks/docs/index.ts | 1 + src/frameworks/docs/nextjs.md | 112 +++++++++++ 8 files changed, 579 insertions(+) create mode 100644 src/frameworks/docs/README.md create mode 100644 src/frameworks/docs/_includes/_before-you-begin.md create mode 100644 src/frameworks/docs/_includes/_initialize-firebase.md create mode 100644 src/frameworks/docs/_includes/_preview-disclaimer.md create mode 100644 src/frameworks/docs/angular.md create mode 100644 src/frameworks/docs/express.md create mode 100644 src/frameworks/docs/index.ts create mode 100644 src/frameworks/docs/nextjs.md diff --git a/src/frameworks/docs/README.md b/src/frameworks/docs/README.md new file mode 100644 index 00000000000..3e85a8414f7 --- /dev/null +++ b/src/frameworks/docs/README.md @@ -0,0 +1,86 @@ +# Web frameworks docs on firebase.google.com + +This directory contains the source of the Web frameworks documentation on +https://firebase.google.com/docs/. + +We welcome your contributions! See [`CONTRIBUTING.md`](../CONTRIBUTING.md) for general +guidelines. + +This file has some information on how our documentation is organized and some +non-standard extensions we use. + +## Standalone files vs. page fragments + +There are two kinds of source file for our docs: + +- **Standalone files** map one-to-one to a single page on firebase.google.com. + These files are mostly-standard Markdown with filenames that correspond with + the URL at which they're eventually published. + + Standalone pages must have filenames that don't begin with an + underscore (`_`). For example, `angular.md` in this folder is + a standalone file. + +- **Page fragments** are included in other pages. We use page fragments either + to include common text in multiple pages or to help organize large pages. + Like standalone files, page fragments are also mostly-standard Markdown, but + their filenames often don't correspond with the URL at which they're + eventually published. + + Page fragments almost always have filenames that begin with an underscore + (`_`). For example, `_before-you-begin.md` is a file of standard steps that + should be included in all frameworks integration guides in this folder. + +## Non-standard Markdown + +### File includes + +> Probably not useful to you as a contributor, but documented FYI. +> We use double angle brackets to include content from another file: + +``` +<> +``` + +Note that the path is based on our internal directory structure, and not the +layout on GitHub. Also note that we sometimes use this to include non-Web frameworks +related content that's not on GitHub. + +### Page metadata + +> Probably not useful to you as a contributor, but documented FYI. +> Every standalone page begins with the following header: + +``` +Project: /docs/_project.yaml +Book: /docs/_book.yaml +``` + +These are non-standard metadata declarations used by our internal publishing +system. There's nothing you can really do with this, but it has to be on every +standalone page. + +### Getting started writing + +Unless the needs of your framework are radically different, you should +follow the outline and example presented in `angular.md`, which to date is +the most completely fleshed-out integration. Details for your framework are +likely to be different, but the overall outline should probably be similar. + +Firebase follows the [Google developer documentation style guide](https://developers.google.com/style), +which you should read before writing substantial contributions. + +Footer +© 2023 GitHub, Inc. +Footer navigation +Terms +Privacy +Security +Status +Docs +Contact GitHub +Pricing +API +Training +Blog +About diff --git a/src/frameworks/docs/_includes/_before-you-begin.md b/src/frameworks/docs/_includes/_before-you-begin.md new file mode 100644 index 00000000000..e0815b7bb42 --- /dev/null +++ b/src/frameworks/docs/_includes/_before-you-begin.md @@ -0,0 +1,10 @@ +## Before you begin + +Before you get started deploying your app to Firebase, +review the following requirements and options: + +- {{firebase_cli}} version 11.14.2 or later. Make sure to + [install the {{cli}}](/docs/cli#install_the_firebase_cli) + using your preferred method. +- Optional: Billing enabled on your Firebase project + (required if you plan to use SSR) diff --git a/src/frameworks/docs/_includes/_initialize-firebase.md b/src/frameworks/docs/_includes/_initialize-firebase.md new file mode 100644 index 00000000000..d143ccd98cc --- /dev/null +++ b/src/frameworks/docs/_includes/_initialize-firebase.md @@ -0,0 +1,12 @@ +## Initialize Firebase + +To get started, initialize Firebase for your framework project. +Use the {{firebase_cli}} for a new project, or modify `firebase.json` for an +existing project. + +### Initialize a new project + +1. In the {{firebase_cli}}, enable the web frameworks preview: +
firebase experiments:enable webframeworks
+1. Run the initialization command from the {{cli}} and then follow the prompts: +
firebase init hosting
diff --git a/src/frameworks/docs/_includes/_preview-disclaimer.md b/src/frameworks/docs/_includes/_preview-disclaimer.md new file mode 100644 index 00000000000..64eccb2e75a --- /dev/null +++ b/src/frameworks/docs/_includes/_preview-disclaimer.md @@ -0,0 +1,4 @@ +Note: Framework-aware {{hosting}} is an early public preview. This means +that the functionality might change in backward-incompatible ways. A preview +release is not subject to any SLA or deprecation policy and may receive limited +or no support. diff --git a/src/frameworks/docs/angular.md b/src/frameworks/docs/angular.md new file mode 100644 index 00000000000..9c917ce04f1 --- /dev/null +++ b/src/frameworks/docs/angular.md @@ -0,0 +1,170 @@ +Project: /docs/hosting/\_project.yaml +Book: /docs/\_book.yaml +page_type: guide + +{% include "_shared/apis/console/_local_variables.html" %} +{% include "_local_variables.html" %} +{% include "docs/hosting/_local_variables.html" %} + + + +# Integrate Angular Universal + +With the Firebase framework-aware {{cli}}, you can deploy your Angular application +to Firebase and serve dynamic content to your users. + +<<\_includes/\_preview-disclaimer.md>> + +<<\_includes/\_before-you-begin.md>> + +- Optional: AngularFire + +<<\_includes/\_initialize-firebase.md>> + +1. Choose your hosting source directory; this could be an existing Angular app. +1. Choose "Dynamic web hosting with web framework." +1. Choose Angular. + +### Initialize an existing project + +Change your hosting config in `firebase.json` to have a `source` option, rather +than a `public` option. For example: + +```json +{ + "hosting": { + "source": "./path-to-your-angular-workspace" + } +} +``` + +## Serve static content + +After initializing Firebase, you can serve static content with the standard +deployment command: + +```shell +firebase deploy +``` + +## Pre-render dynamic content + +To prerender dynamic content in Angular, you need to set up Angular Universal. +The {{firebase_cli}} expects Express Engine: + +```shell +ng add @nguniversal/express-engine +``` + +See the [Angular Universal guide](https://angular.io/guide/universal) +for more information. + +### Add prerender URLs + +By default, only the root directory will be prerendered. You can add additional +routes by locating the prerender step in `angular.json` and adding more routes: + +```json +{ + "prerender": { + "builder": "@nguniversal/builders:prerender", + "options": { + "routes": ["/", "ANOTHER_ROUTE", "AND_ANOTHER"] + }, + "configurations": { + /* ... */ + }, + "defaultConfiguration": "production" + } +} +``` + +Firebase also respects `guessRoutes` or a `routes.txt` file in the hosting root, +if you need to customize further. See [Angular’s prerendering +guide](https://angular.io/guide/prerendering) for more information on those +options. + +### Optional: add a server module + +#### Deploy + +When you deploy with `firebase deploy`, Firebase builds your browser bundle, +your server bundle, and prerenders the application. These elements are deployed +to {{hosting}} and {{cloud_functions_full}}. + +#### Custom deploy + +The {{firebase_cli}} assumes that you have server, build, and prerender steps in +your schematics with a production configuration. + +If you want to tailor the {{cli}}'s assumptions, configure `ng deploy` and edit the +configuration in `angular.json`. For example, you could disable SSR and serve +pre-rendered content exclusively by removing `serverTarget`: + +```json +{ + "deploy": { + "builder": "@angular/fire:deploy", + "options": { + "browserTarget": "app:build:production", + "serverTarget": "app:server:production", + "prerenderTarget": "app:prerender:production" + } + } +} +``` + +### Optional: integrate with the Firebase JS SDK + +When including Firebase JS SDK methods in both server and client bundles, guard +against runtime errors by checking `isSupported()` before using the product. +Not all products are [supported in all environments](/docs/web/environments-js-sdk#other_environments). + +Tip: consider using AngularFire, which does this for you automatically. + +### Optional: integrate with the Firebase Admin SDK + +Admin bundles will fail if they are included in your browser build, so consider +providing them in your server module and injecting as an optional dependency: + +```typescript +// your-component.ts +import type { app } from 'firebase-admin'; +import { FIREBASE_ADMIN } from '../app.module'; + +@Component({...}) +export class YourComponent { + + constructor(@Optional() @Inject(FIREBASE_ADMIN) admin: app.App) { + ... + } +} + +// app.server.module.ts +import * as admin from 'firebase-admin'; +import { FIREBASE_ADMIN } from './app.module'; + +@NgModule({ + … + providers: [ + … + { provide: FIREBASE_ADMIN, useFactory: () => admin.apps[0] || admin.initializeApp() } + ], +}) +export class AppServerModule {} + +// app.module.ts +import type { app } from 'firebase-admin'; + +export const FIREBASE_ADMIN = new InjectionToken('firebase-admin'); +``` + +## Serve fully dynamic content with SSR + +### Optional: integrate with Firebase Authentication + +The web framework-aware Firebase deployment tooling automatically keeps client +and server state in sync using cookies. The Express `res.locals` object will +optionally contain an authenticated Firebase App instance (`firebaseApp`) and +the currently signed in user (`currentUser`). This can be injected into your +module via the REQUEST token (exported from @nguniversal/express-engine/tokens). diff --git a/src/frameworks/docs/express.md b/src/frameworks/docs/express.md new file mode 100644 index 00000000000..6b7f55da7b6 --- /dev/null +++ b/src/frameworks/docs/express.md @@ -0,0 +1,184 @@ +Project: /docs/hosting/\_project.yaml +Book: /docs/\_book.yaml +page_type: guide + +{% include "_shared/apis/console/_local_variables.html" %} +{% include "_local_variables.html" %} +{% include "docs/hosting/_local_variables.html" %} + + + +# Integrate other frameworks with Express.js + +With some additional configuration, you can build on the basic +framework-aware {{cli}} functionality +to extend integration support to frameworks other than Angular and Next.js. + +<<\_includes/\_preview-disclaimer.md>> + +<<\_includes/\_before-you-begin.md>> + +- Optional: Billing enabled on your Firebase project + (required if you plan to use SSR) + +<<\_includes/\_initialize-firebase.md>> + +1. Choose your hosting source directory; this could be an existing web app. +1. Choose "Dynamic web hosting with web framework." +1. Choose Express.js / custom + +### Initialize an existing project + +Change your hosting config in `firebase.json` to have a `source` option, rather +than a `public` option. For example: + +```json +{ + "hosting": { + "source": "./path-to-your-express-directory" + } +} +``` + +## Serve static content + +Before deploying static content, you'll need to configure your application. + +### Configure + +In order to know how to deploy your application, the {{firebase_cli}} needs to be +able to both build your app and know where your tooling places the assets +destined for {{hosting}}. This is accomplished with the npm build script and CJS +directories directive in `package.json`. + +Given the following package.json: + +```json +{ + "name": "express-app", + "version": "0.0.0", + "scripts": { + "build": "spack", + "static": "cp static/* dist", + "prerender": "ts-node prerender.ts" + }, + … +} +``` + +The {{firebase_cli}} only calls your build script, so you’ll need to ensure that +your build script is exhaustive. + +Tip: you can add additional steps using` &&`. If you have a lot of steps, +consider a shell script or tooling like [npm-run-all](https://www.npmjs.com/package/npm-run-all) +or [wireit](https://www.npmjs.com/package/wireit). + +```json +{ + "name": "express-app", + "version": "0.0.0", + "scripts": { + "build": "spack && npm run static && npm run prerender", + "static": "cp static/* dist", + "prerender": "ts-node prerender.ts" + }, + … +} +``` + +If your framework doesn’t support pre-rendering out of the box, consider using a +tool like [Rendertron](https://github.com/GoogleChrome/rendertron). Rendertron +will allow you to make headless Chrome requests against a local instance of your +app, so you can save the resulting HTML to be served on {{hosting}}. + +Finally, different frameworks and build tools store their artifacts in different +places. Use `directories.serve` to tell the {{cli}} where your build script is +outputting the resulting artifacts: + +```json +{ + "name": "express-app", + "version": "0.0.0", + "scripts": { + "build": "spack && npm run static && npm run prerender", + "static": "cp static/* dist", + "prerender": "ts-node prerender.ts" + }, + "directories": { + "serve": "dist" + }, + … +} +``` + +### Deploy + +After configuring your app, you can serve static content with the standard +deployment command: + +```shell +firebase deploy +``` + +## Serve Dynamic Content + +To serve your Express app on {{cloud_functions_full}}, ensure that your Express app (or +express-style URL handler) is exported in such a way that Firebase can find it +after your library has been npm packed. + +To accomplish this, ensure that your `files` directive includes everything +needed for the server, and that your main entry point is set up correctly in +`package.json`: + +```json +{ + "name": "express-app", + "version": "0.0.0", + "scripts": { + "build": "spack && npm run static && npm run prerender", + "static": "cp static/* dist", + "prerender": "ts-node tools/prerender.ts" + }, + "directories": { + "serve": "dist" + }, + "files": ["dist", "server.js"], + "main": "server.js", + ... +} +``` + +Export your express app from a function named `app`: + +```js +// server.js +export function app() { + const server = express(); + … + return server; +} +``` + +Or if you’d rather export an express-style URL handler, name it `handle`: + +```js +export function handle(req, res) { + res.send(‘hello world’); +} +``` + +### Deploy + +```shell +firebase deploy +``` + +This deploys your static content to {{firebase_hosting}} and allows Firebase to +fall back to your Express app hosted on {{cloud_functions_full}}. + +## Optional: integrate with Firebase Authentication + +The web framework-aware Firebase deploy tooling will automatically keep client +and server state in sync using cookies. To access the authentication context, +the Express `res.locals` object optionally contains an authenticated Firebase +App instance (`firebaseApp`) and the currently signed in User (`currentUser`). diff --git a/src/frameworks/docs/index.ts b/src/frameworks/docs/index.ts new file mode 100644 index 00000000000..cb0ff5c3b54 --- /dev/null +++ b/src/frameworks/docs/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/src/frameworks/docs/nextjs.md b/src/frameworks/docs/nextjs.md new file mode 100644 index 00000000000..b56a05e25d3 --- /dev/null +++ b/src/frameworks/docs/nextjs.md @@ -0,0 +1,112 @@ +Project: /docs/hosting/\_project.yaml +Book: /docs/\_book.yaml +page_type: guide + +{% include "_shared/apis/console/_local_variables.html" %} +{% include "_local_variables.html" %} +{% include "docs/hosting/_local_variables.html" %} + + + +# Integrate Next.js + +Using the {{firebase_cli}}, you can deploy your Next.js Web apps to Firebase and +serve them with {{firebase_hosting}}. The {{cli}} respects your Next.js settings and +translates them to Firebase settings with zero or minimal extra configuration on +your part. If your app includes dynamic server-side logic, the {{cli}} deploys that +logic to {{cloud_functions_full}}.ß + +<<\_includes/\_preview-disclaimer.md>> + +<<\_includes/\_before-you-begin.md>> + +- Optional: Billing enabled on your Firebase project + (required if you plan to use SSR) +- Optional: use the experimental ReactFire library to benefit from its + Firebase-friendly features + +<<\_includes/\_initialize-firebase.md>> + +1. Choose your hosting source directory. If this an existing Next.js app, + the {{cli}} process completes, and you can proceed to the next section. +1. Choose "Dynamic web hosting with web framework" +1. Choose Next.js. + +## Serve static content + +After initializing Firebase, you can serve static content with the standard +deployment command: + +```shell +firebase deploy +``` + +You can [view your deployed app](/docs/hosting/test-preview-deploy#view-changes) +on its live site. + +## Pre-render dynamic content + +The {{firebase_cli}} will detect usage of +[getStaticProps](https://nextjs.org/docs/basic-features/data-fetching/get- +static-props) and [getStaticPaths](https://nextjs.org/docs/basic-features/data- +fetching/get-static-paths). + +### Optional: integrate with the Firebase JS SDK + +When including Firebase JS SDK methods in both server and client bundles, guard +against runtime errors by checking `isSupported()` before using the product. +Not all products are [supported in all environments](/docs/web/environments-js-sdk#other_environments). + +Tip: consider using +[ReactFire](https://github.com/FirebaseExtended/reactfire#reactfire), which does +this for you automatically. + +### Optional: integrate with the Firebase Admin SDK + +Admin SDK bundles will fail if included in your browser build; refer to them +only inside [getStaticProps](https://nextjs.org/docs/basic-features/data-fetching/get-static-props) +and [getStaticPaths](https://nextjs.org/docs/basic-features/data-fetching/get-static-paths). + +## Serve fully dynamic content (SSR) + +The {{firebase_cli}} will detect usage of +[getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props). + +## Configure {{hosting}} behavior with `next.config.js` + +### Image Optimization + +Using [Next.js Image Optimization](https://nextjs.org/docs/basic-features/image-optimization) +is supported, but it will trigger creation of a function +(in [{{cloud_functions_full}}](/docs/functions/)), even if you’re not using SSR. + +Note: Because of this, image optimization and {{hosting}} preview channels don’t +interoperate well together. + +### Redirects, Rewrites, and Headers + +The {{firebase_cli}} respects [redirects](https://nextjs.org/docs/api-reference/next.config.js/redirects), +[rewrites](https://nextjs.org/docs/api-reference/next.config.js/rewrites), and +[headers](https://nextjs.org/docs/api-reference/next.config.js/headers) in +`next.config.js`, converting them to their +respective equivalent {{firebase_hosting}} configuration at deploy time. If a +Next.js redirect, rewrite, or header cannot be converted to an equivalent +{{firebase_hosting}} header, it falls back and builds a function—even if you +aren’t using image optimization or SSR. + +### Optional: integrate with Firebase Authentication + +The web framework-aware Firebase deployment tooling will automatically keep +client and server state in sync using cookies. There are some methods provided +for accessing the authentication context in SSR: + +- The Express `res.locals` object will optionally contain an authenticated + Firebase App instance (`firebaseApp`) and the currently signed-in user + (`currentUser`). This can be accessed in `getServerSideProps`. +- The authenticated Firebase App name is provided on the route query + (`__firebaseAppName`). This allows for manual integration while in context: + +```typescript +// get the authenticated Firebase App +const firebaseApp = getApp(useRouter().query.__firebaseAppName); +``` From 9714a6f595466a10ff991323050ebf5108f9e69b Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Fri, 31 Mar 2023 11:08:34 -0500 Subject: [PATCH 0851/1699] adds 404 check when fetching repo public key (#5643) --- CHANGELOG.md | 1 + src/init/features/hosting/github.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa68f7e3094..b37416e0d15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - The hosting emulator integration with web frameworks now has improved support for HMR and dev-tools. (#5582) +- Fixes an issue where `init hosting:github` would hang if it could not access a repository's public key. (#5317) diff --git a/src/init/features/hosting/github.ts b/src/init/features/hosting/github.ts index 0f78bea141f..b83b145e744 100644 --- a/src/init/features/hosting/github.ts +++ b/src/init/features/hosting/github.ts @@ -418,7 +418,7 @@ async function promptForRepo( key = body.key; keyId = body.key_id; } catch (e: any) { - if (e.status === 403) { + if ([403, 404].includes(e.status)) { logger.info(); logger.info(); logWarning( From 0a681c2ff467a118902313b6603bb47659a017fd Mon Sep 17 00:00:00 2001 From: egilmorez Date: Mon, 3 Apr 2023 08:04:00 -0700 Subject: [PATCH 0852/1699] Web Frameworks docs formatting for DevSite (#5645) * Removing slashes and editing the linter ignore file to allow them (as required by DevSite). --- .eslintrc.js | 1 + .prettierignore | 1 + src/frameworks/docs/angular.md | 10 +++++----- src/frameworks/docs/express.md | 10 +++++----- src/frameworks/docs/nextjs.md | 10 +++++----- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3026ae4f731..923fd5158d3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -124,5 +124,6 @@ module.exports = { "src/dynamicImport.js", "scripts/webframeworks-deploy-tests/hosting/**", "scripts/frameworks-tests/vite-project/**", + "/src/frameworks/docs/**", ], }; diff --git a/.prettierignore b/.prettierignore index d75f7c02f61..176a67e98cf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ /lib/**/* /CONTRIBUTING.md /scripts/frameworks-tests/vite-project/** +/src/frameworks/docs/** diff --git a/src/frameworks/docs/angular.md b/src/frameworks/docs/angular.md index 9c917ce04f1..6cbc3358898 100644 --- a/src/frameworks/docs/angular.md +++ b/src/frameworks/docs/angular.md @@ -1,5 +1,5 @@ -Project: /docs/hosting/\_project.yaml -Book: /docs/\_book.yaml +Project: /docs/hosting/_project.yaml +Book: /docs/_book.yaml page_type: guide {% include "_shared/apis/console/_local_variables.html" %} @@ -13,13 +13,13 @@ page_type: guide With the Firebase framework-aware {{cli}}, you can deploy your Angular application to Firebase and serve dynamic content to your users. -<<\_includes/\_preview-disclaimer.md>> +<<_includes/_preview-disclaimer.md>> -<<\_includes/\_before-you-begin.md>> +<<_includes/_before-you-begin.md>> - Optional: AngularFire -<<\_includes/\_initialize-firebase.md>> +<<_includes/_initialize-firebase.md>> 1. Choose your hosting source directory; this could be an existing Angular app. 1. Choose "Dynamic web hosting with web framework." diff --git a/src/frameworks/docs/express.md b/src/frameworks/docs/express.md index 6b7f55da7b6..b0d4cb9929a 100644 --- a/src/frameworks/docs/express.md +++ b/src/frameworks/docs/express.md @@ -1,5 +1,5 @@ -Project: /docs/hosting/\_project.yaml -Book: /docs/\_book.yaml +Project: /docs/hosting/_project.yaml +Book: /docs/_book.yaml page_type: guide {% include "_shared/apis/console/_local_variables.html" %} @@ -14,14 +14,14 @@ With some additional configuration, you can build on the basic framework-aware {{cli}} functionality to extend integration support to frameworks other than Angular and Next.js. -<<\_includes/\_preview-disclaimer.md>> +<<_includes/_preview-disclaimer.md>> -<<\_includes/\_before-you-begin.md>> +<<_includes/_before-you-begin.md>> - Optional: Billing enabled on your Firebase project (required if you plan to use SSR) -<<\_includes/\_initialize-firebase.md>> +<<_includes/_initialize-firebase.md>> 1. Choose your hosting source directory; this could be an existing web app. 1. Choose "Dynamic web hosting with web framework." diff --git a/src/frameworks/docs/nextjs.md b/src/frameworks/docs/nextjs.md index b56a05e25d3..9f0cfbd651d 100644 --- a/src/frameworks/docs/nextjs.md +++ b/src/frameworks/docs/nextjs.md @@ -1,5 +1,5 @@ -Project: /docs/hosting/\_project.yaml -Book: /docs/\_book.yaml +Project: /docs/hosting/_project.yaml +Book: /docs/_book.yaml page_type: guide {% include "_shared/apis/console/_local_variables.html" %} @@ -16,16 +16,16 @@ translates them to Firebase settings with zero or minimal extra configuration on your part. If your app includes dynamic server-side logic, the {{cli}} deploys that logic to {{cloud_functions_full}}.ß -<<\_includes/\_preview-disclaimer.md>> +<<_includes/_preview-disclaimer.md>> -<<\_includes/\_before-you-begin.md>> +<<_includes/_before-you-begin.md>> - Optional: Billing enabled on your Firebase project (required if you plan to use SSR) - Optional: use the experimental ReactFire library to benefit from its Firebase-friendly features -<<\_includes/\_initialize-firebase.md>> +<<_includes/_initialize-firebase.md>> 1. Choose your hosting source directory. If this an existing Next.js app, the {{cli}} process completes, and you can proceed to the next section. From dd843383f216f12266898576f0e9ea24285d2b31 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Mon, 3 Apr 2023 11:23:12 -0700 Subject: [PATCH 0853/1699] Adding overview file here (otherwise it is overwritten) plus a couple tweaks. (#5651) --- src/frameworks/docs/frameworks-overview.md | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/frameworks/docs/frameworks-overview.md diff --git a/src/frameworks/docs/frameworks-overview.md b/src/frameworks/docs/frameworks-overview.md new file mode 100644 index 00000000000..36bc7f83b58 --- /dev/null +++ b/src/frameworks/docs/frameworks-overview.md @@ -0,0 +1,54 @@ +Project: /docs/hosting/_project.yaml +Book: /docs/_book.yaml +page_type: guide + +{% include "_shared/apis/console/_local_variables.html" %} +{% include "_local_variables.html" %} +{% include "docs/hosting/_local_variables.html" %} + + + +# Integrate web frameworks with {{hosting}} + +{{firebase_hosting}} integrates with popular modern web frameworks including Angular +and Next.js. Using {{firebase_hosting}} and {{cloud_functions_full}} with these +frameworks, you can develop apps and microservices in your preferred framework +environment, and then deploy them in a managed, secure server environment. +Support during this early preview includes the following functionality: + +* Deploy Web apps comprised of static web content +* Deploy Web apps that use pre-rendering / Static Site Generation (SSG) +* Deploy Web apps that use server-side Rendering (SSR)—full server rendering on demand + +Firebase provides this functionality through the {{firebase_cli}}. When initializing +{{hosting}} on the command line, you provide information about your new or existing +Web project, and the {{cli}} sets up the right resources for your chosen Web +framework. + +<<_includes/_preview-disclaimer.md>> + +<<_includes/_before-you-begin.md>> + +## Serve locally + +You can test your integration locally by following these steps: + +1. Run `firebase emulators:start` from the terminal. This builds your app and + serves it using the {{firebase_cli}}. +2. Open your web app at the local URL returned by the {{cli}} (usually http://localhost:5000). + +## Deploy your app to {{firebase_hosting}} + +When you're ready to share your changes with the world, deploy your app to your +live site: + +1. Run `firebase deploy` from the terminal. +2. Check your website on: `SITE_ID.web.app` or `PROJECT_ID.web.app` (or your custom domain, if you set one up). + +## Next steps + +See the detailed guide for your preferred framework: + +* [Angular Universal](/docs/hosting/frameworks/angular) +* [Next.js] (/docs/hosting/frameworks/nextjs) +* [Frameworks with Express.js](/docs/hosting/frameworks/ßexpress) From ed23ae3bc2a03491f94eba6730c6898d52889e15 Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:11:06 -0700 Subject: [PATCH 0854/1699] Release firestore emulator v1.16.2. (#5652) --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37416e0d15..ff47bba45a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - The hosting emulator integration with web frameworks now has improved support for HMR and dev-tools. (#5582) - Fixes an issue where `init hosting:github` would hang if it could not access a repository's public key. (#5317) +- Release Firestore Emulator v1.16.2 which captures an HTTP1 header fix and requests monitor fix. (#TODO) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 1b41d302f78..5146c4e8c6b 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -33,9 +33,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet expectedChecksum: "311609538bd65666eb724ef47c2e6466", }, firestore: { - version: "1.16.1", - expectedSize: 64213741, - expectedChecksum: "befa5f6cbe258e787bf0bbb4eb9da2c8", + version: "1.16.2", + expectedSize: 64601019, + expectedChecksum: "83f379a5b3d367503a860497fea3a936", }, storage: { version: "1.1.3", From 3d85cfea472ee7b8715c9d72ebf384022dc0d494 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 4 Apr 2023 15:02:09 -0700 Subject: [PATCH 0855/1699] Clean up TODO in changelog (#5656) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff47bba45a0..2abd8c640dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,3 @@ - The hosting emulator integration with web frameworks now has improved support for HMR and dev-tools. (#5582) - Fixes an issue where `init hosting:github` would hang if it could not access a repository's public key. (#5317) -- Release Firestore Emulator v1.16.2 which captures an HTTP1 header fix and requests monitor fix. (#TODO) +- Release Firestore Emulator v1.16.2 which captures an HTTP1 header fix and requests monitor fix. From d8eca7a037592000456ed1800ba7c51653a846ca Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Wed, 5 Apr 2023 09:10:53 -0700 Subject: [PATCH 0856/1699] Emulator suite UI version 1.11.5 (#5657) * Version bump * Readme * me again --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2abd8c640dc..781297d3ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - The hosting emulator integration with web frameworks now has improved support for HMR and dev-tools. (#5582) - Fixes an issue where `init hosting:github` would hang if it could not access a repository's public key. (#5317) - Release Firestore Emulator v1.16.2 which captures an HTTP1 header fix and requests monitor fix. +- Release Emulator Suite UI v1.11.5 which addresses an issue where displaying over 10k documents was crashing the emulator. (#5657) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 5146c4e8c6b..1b920108a31 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -45,9 +45,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet ui: experiments.isEnabled("emulatoruisnapshot") ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" } : { - version: "1.11.4", - expectedSize: 3062916, - expectedChecksum: "1773926323b07fdb9602d882a7682882", + version: "1.11.5", + expectedSize: 3063444, + expectedChecksum: "4045fef65cf71fb9d83b01fb8b160141", }, pubsub: { version: "0.7.1", From 228de9465f62f9e6ed5709ec8f66ec5d91775301 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 5 Apr 2023 18:12:21 +0000 Subject: [PATCH 0857/1699] 11.25.3 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f6f0e7ade3f..7ae69451e28 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.25.2", + "version": "11.25.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.25.2", + "version": "11.25.3", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index d654404e930..e8b3ce51051 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.25.2", + "version": "11.25.3", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 0a4a4c2a3ed8a0b63cd82ac6a1a6834bb558c933 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 5 Apr 2023 18:12:34 +0000 Subject: [PATCH 0858/1699] [firebase-release] Removed change log and reset repo after 11.25.3 release --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 781297d3ae7..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +0,0 @@ -- The hosting emulator integration with web frameworks now has improved support for HMR and dev-tools. (#5582) -- Fixes an issue where `init hosting:github` would hang if it could not access a repository's public key. (#5317) -- Release Firestore Emulator v1.16.2 which captures an HTTP1 header fix and requests monitor fix. -- Release Emulator Suite UI v1.11.5 which addresses an issue where displaying over 10k documents was crashing the emulator. (#5657) From 8976456eebf75ab9ab2a1299c0d6561f324db7f8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 5 Apr 2023 19:48:23 -0700 Subject: [PATCH 0859/1699] Fix bug where functions shell failed to invoke event triggered functions in debug mode. (#5609) When running functions shell with `--inspect-functions` flag (aka debug mode), the shell command wasn't preparing the emulated process to execute the trigger invoked by the user. Including a small refactoring to add a property to the FunctionsEmulator class to denote whether it's in debug mode or not for clarity. I recognize that there isn't any test here. Sadly, there is no existing test suite for the functions shell command, and frankly I understand why - it's tricky to setup a test that requires interactive input. I'm intentionally not taking up the challenge to set up a test suite here since the bug is fairly small, but I do hope that we'll have time in the future to pay this tech debt (famous last words?). Fixes https://github.com/firebase/firebase-tools/issues/5567 --- CHANGELOG.md | 1 + src/emulator/functionsEmulator.ts | 34 +++++++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..71f271c9bb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fix bug where functions shell failed to invoke event triggered functions in debug mode. (#5609) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index acd04b190ef..556a3995c77 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -212,6 +212,8 @@ export class FunctionsEmulator implements EmulatorInstance { private blockingFunctionsConfig: BlockingFunctionsConfig = {}; + debugMode = false; + constructor(private args: FunctionsEmulatorArgs) { // TODO: Would prefer not to have static state but here we are! EmulatorLogger.verbosity = this.args.quiet ? Verbosity.QUIET : Verbosity.DEBUG; @@ -220,13 +222,12 @@ export class FunctionsEmulator implements EmulatorInstance { if (this.args.debugPort) { this.args.disabledRuntimeFeatures = this.args.disabledRuntimeFeatures || {}; this.args.disabledRuntimeFeatures.timeout = true; + this.debugMode = true; } this.adminSdkConfig = { ...this.args.adminSdkConfig, projectId: this.args.projectId }; - const mode = this.args.debugPort - ? FunctionsExecutionMode.SEQUENTIAL - : FunctionsExecutionMode.AUTO; + const mode = this.debugMode ? FunctionsExecutionMode.SEQUENTIAL : FunctionsExecutionMode.AUTO; this.workerPools = {}; for (const backend of this.args.emulatableBackends) { const pool = new RuntimeWorkerPool(mode); @@ -373,6 +374,12 @@ export class FunctionsEmulator implements EmulatorInstance { } } const worker = pool.getIdleWorker(trigger.id)!; + if (this.debugMode) { + await worker.sendDebugMsg({ + functionTarget: trigger.entryPoint, + functionSignature: getSignatureType(trigger), + }); + } const reqBody = JSON.stringify(body); const headers = { "Content-Type": "application/json", @@ -689,7 +696,7 @@ export class FunctionsEmulator implements EmulatorInstance { } // In debug mode, we eagerly start the runtime processes to allow debuggers to attach // before invoking a function. - if (this.args.debugPort) { + if (this.debugMode) { if (!emulatableBackend.runtime?.startsWith("node")) { this.logger.log("WARN", "--inspect-functions only supported for Node.js runtimes."); } else { @@ -1134,7 +1141,7 @@ export class FunctionsEmulator implements EmulatorInstance { } setEnvVarsForEmulators(envs, emulatorInfos); - if (this.args.debugPort) { + if (this.debugMode) { // Start runtime in debug mode to allow triggers to share single runtime process. envs["FUNCTION_DEBUG_MODE"] = "true"; } @@ -1241,7 +1248,7 @@ export class FunctionsEmulator implements EmulatorInstance { envs: Record ): Promise { const args = [path.join(__dirname, "functionsEmulatorRuntime")]; - if (this.args.debugPort) { + if (this.debugMode) { if (process.env.FIREPIT_VERSION) { this.logger.log( "WARN", @@ -1304,7 +1311,7 @@ export class FunctionsEmulator implements EmulatorInstance { ): Promise { const args = ["functions-framework"]; - if (this.args.debugPort) { + if (this.debugMode) { this.logger.log("WARN", "--inspect-functions not supported for Python functions. Ignored."); } @@ -1515,12 +1522,13 @@ export class FunctionsEmulator implements EmulatorInstance { return; } } - const debugBundle = this.args.debugPort - ? { - functionTarget: trigger.entryPoint, - functionSignature: getSignatureType(trigger), - } - : undefined; + let debugBundle; + if (this.debugMode) { + debugBundle = { + functionTarget: trigger.entryPoint, + functionSignature: getSignatureType(trigger), + }; + } await pool.submitRequest( trigger.id, { From d28d77a5c65af166b8165417da9bc7620ade092b Mon Sep 17 00:00:00 2001 From: Jeff <3759507+jhuleatt@users.noreply.github.com> Date: Thu, 6 Apr 2023 13:55:29 -0400 Subject: [PATCH 0860/1699] Remove extra character from Next.js guide (#5663) --- src/frameworks/docs/nextjs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frameworks/docs/nextjs.md b/src/frameworks/docs/nextjs.md index 9f0cfbd651d..7a14e0d6147 100644 --- a/src/frameworks/docs/nextjs.md +++ b/src/frameworks/docs/nextjs.md @@ -14,7 +14,7 @@ Using the {{firebase_cli}}, you can deploy your Next.js Web apps to Firebase and serve them with {{firebase_hosting}}. The {{cli}} respects your Next.js settings and translates them to Firebase settings with zero or minimal extra configuration on your part. If your app includes dynamic server-side logic, the {{cli}} deploys that -logic to {{cloud_functions_full}}.ß +logic to {{cloud_functions_full}}. <<_includes/_preview-disclaimer.md>> From 38413e0a8faa97a74f7364ab89b2a0613e5f3c8f Mon Sep 17 00:00:00 2001 From: Austin Crim Date: Thu, 6 Apr 2023 13:44:52 -0500 Subject: [PATCH 0861/1699] Add experimental support for SvelteKit codebases (#5525) Co-authored-by: James Daniels --- CHANGELOG.md | 2 + src/frameworks/index.ts | 2 +- src/frameworks/sveltekit/index.ts | 54 ++++++++++++++++++++++++++ src/frameworks/sveltekit/interfaces.ts | 8 ++++ src/frameworks/utils.ts | 4 +- src/frameworks/vite/index.ts | 14 +++++-- 6 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 src/frameworks/sveltekit/index.ts create mode 100644 src/frameworks/sveltekit/interfaces.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f271c9bb2..7056971f7df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ - Fix bug where functions shell failed to invoke event triggered functions in debug mode. (#5609) +- Fixed bug with the web frameworks proxy that could see unexpected 404 errors while emulating. (#5525) +- Added experimental support for SvelteKit codebases. (#5525) diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 60d28f41b84..3352682085e 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -106,7 +106,7 @@ const SupportLevelWarnings = { ), }; -export const FIREBASE_FRAMEWORKS_VERSION = "^0.6.0"; +export const FIREBASE_FRAMEWORKS_VERSION = "^0.7.0"; export const FIREBASE_FUNCTIONS_VERSION = "^3.23.0"; export const FIREBASE_ADMIN_VERSION = "^11.0.1"; export const NODE_VERSION = parseInt(process.versions.node, 10).toString(); diff --git a/src/frameworks/sveltekit/index.ts b/src/frameworks/sveltekit/index.ts new file mode 100644 index 00000000000..436bb30b7a1 --- /dev/null +++ b/src/frameworks/sveltekit/index.ts @@ -0,0 +1,54 @@ +import { copy, pathExists, readFile } from "fs-extra"; +import { join } from "path"; +import { FrameworkType, SupportLevel } from ".."; +import { viteDiscoverWithNpmDependency, build as viteBuild } from "../vite"; +import { SvelteKitConfig } from "./interfaces"; +import { fileExistsSync } from "../../fsutils"; + +const { dynamicImport } = require(true && "../../dynamicImport"); + +export const name = "SvelteKit"; +export const support = SupportLevel.Experimental; +export const type = FrameworkType.MetaFramework; +export const discover = viteDiscoverWithNpmDependency("@sveltejs/kit"); +export { getDevModeHandle } from "../vite"; + +export async function build(root: string) { + const config = await getConfig(root); + const wantsBackend = config.kit.adapter?.name !== "@sveltejs/adapter-static"; + await viteBuild(root); + return { wantsBackend }; +} + +export async function ɵcodegenPublicDirectory(root: string, dest: string) { + const config = await getConfig(root); + const output = join(root, config.kit.outDir, "output"); + await copy(join(output, "client"), dest); + + const prerenderedPath = join(output, "prerendered", "pages"); + if (await pathExists(prerenderedPath)) { + await copy(prerenderedPath, dest); + } +} + +export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) { + const packageJsonBuffer = await readFile(join(sourceDir, "package.json")); + const packageJson = JSON.parse(packageJsonBuffer.toString()); + packageJson.dependencies ||= {}; + packageJson.dependencies["@sveltejs/kit"] ??= packageJson.devDependencies["@sveltejs/kit"]; + + const config = await getConfig(sourceDir); + await copy(join(sourceDir, config.kit.outDir, "output", "server"), destDir); + + return { packageJson, frameworksEntry: "sveltekit" }; +} + +async function getConfig(root: string): Promise { + const configPath = ["svelte.config.js", "svelte.config.mjs"] + .map((filename) => join(root, filename)) + .find(fileExistsSync); + const config = configPath ? (await dynamicImport(configPath)).default : {}; + config.kit ||= {}; + config.kit.outDir ||= ".svelte-kit"; + return config; +} diff --git a/src/frameworks/sveltekit/interfaces.ts b/src/frameworks/sveltekit/interfaces.ts new file mode 100644 index 00000000000..3e371d60e10 --- /dev/null +++ b/src/frameworks/sveltekit/interfaces.ts @@ -0,0 +1,8 @@ +export interface SvelteKitConfig { + kit: { + outDir: string; + adapter?: { + name: string; + }; + }; +} diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index 7857b3e20c7..30cacb05a18 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -59,8 +59,8 @@ export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { return next(); } if (typeof hostOrRequestHandler === "string") { - const host = hostOrRequestHandler; - const { hostname, port, protocol, username, password } = new URL(host); + const { hostname, port, protocol, username, password } = new URL(hostOrRequestHandler); + const host = `${hostname}:${port}`; const auth = username || password ? `${username}:${password}` : undefined; const opts = { agent, diff --git a/src/frameworks/vite/index.ts b/src/frameworks/vite/index.ts index caefbe5958b..f4675b34585 100644 --- a/src/frameworks/vite/index.ts +++ b/src/frameworks/vite/index.ts @@ -49,7 +49,7 @@ export async function discover(dir: string, plugin?: string, npmDependency?: str if (!existsSync(join(dir, "package.json"))) return; // If we're not searching for a vite plugin, depth has to be zero const additionalDep = - npmDependency && findDependency(npmDependency, { cwd: dir, depth: 0, omitDev: true }); + npmDependency && findDependency(npmDependency, { cwd: dir, depth: 0, omitDev: false }); const depth = plugin ? undefined : 0; const configFilesExist = await Promise.all([ pathExists(join(dir, "vite.config.js")), @@ -67,8 +67,11 @@ export async function build(root: string) { const { build } = relativeRequire(root, "vite"); await warnIfCustomBuildScript(root, name, DEFAULT_BUILD_SCRIPT); - + // SvelteKit uses process.cwd() unfortunately, chdir + const cwd = process.cwd(); + process.chdir(root); await build({ root }); + process.chdir(cwd); } export async function ɵcodegenPublicDirectory(root: string, dest: string) { @@ -96,5 +99,10 @@ export async function getDevModeHandle(dir: string) { async function getConfig(root: string) { const { resolveConfig } = relativeRequire(root, "vite"); - return await resolveConfig({ root }, "build", "production"); + // SvelteKit uses process.cwd() unfortunately, we should be defensive here + const cwd = process.cwd(); + process.chdir(root); + const config = await resolveConfig({ root }, "build", "production"); + process.chdir(cwd); + return config; } From b0798fb1fe96499e1404d6fea6c181735e3a8f11 Mon Sep 17 00:00:00 2001 From: ybourgery Date: Thu, 6 Apr 2023 22:10:37 +0200 Subject: [PATCH 0862/1699] Handle deletion of objects in the storage emulator for the API v1 (#5598) * Handle deletion of objects in the storage emulator for the API v1 * Fix linting issues --------- Co-authored-by: Tony Huang Co-authored-by: joehan --- src/emulator/storage/apis/gcloud.ts | 35 ++++++++++++++++------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/emulator/storage/apis/gcloud.ts b/src/emulator/storage/apis/gcloud.ts index a51070e35b2..1cdafc9b4fe 100644 --- a/src/emulator/storage/apis/gcloud.ts +++ b/src/emulator/storage/apis/gcloud.ts @@ -160,23 +160,26 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { }); }); - gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => { - try { - await adminStorageLayer.deleteObject({ - bucketId: req.params.bucketId, - decodedObjectId: req.params.objectId, - }); - } catch (err) { - if (err instanceof NotFoundError) { - return sendObjectNotFound(req, res); - } - if (err instanceof ForbiddenError) { - return res.sendStatus(403); + gcloudStorageAPI.delete( + ["/b/:bucketId/o/:objectId", "/storage/v1/b/:bucketId/o/:objectId"], + async (req, res) => { + try { + await adminStorageLayer.deleteObject({ + bucketId: req.params.bucketId, + decodedObjectId: req.params.objectId, + }); + } catch (err) { + if (err instanceof NotFoundError) { + return sendObjectNotFound(req, res); + } + if (err instanceof ForbiddenError) { + return res.sendStatus(403); + } + throw err; } - throw err; + return res.sendStatus(204); } - return res.sendStatus(204); - }); + ); gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => { if (!req.query.upload_id) { @@ -301,7 +304,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router { let dataRaw: Buffer; try { ({ metadataRaw, dataRaw } = parseObjectUploadMultipartRequest( - contentTypeHeader!, + contentTypeHeader, await reqBodyToBuffer(req) )); } catch (err) { From 63c9bd84156caa626ab1031b04e584dfac083508 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 6 Apr 2023 19:18:45 -0700 Subject: [PATCH 0863/1699] Fix bug where eagerly initializing UA failed function deployment that imported firebase-tools as a library. (#5666) Instead, we lazily initialize the UA agent to prevent CF3 deploy from failing when building function source that uses firebase-tools as a library. Not sure why this suddenly became a problem in nodejs18 - my guess is that the builder image for nodejs18 runtime have stricter permission on filesystem directories that configstore needs. Fixes https://github.com/firebase/firebase-tools/issues/5631. --- CHANGELOG.md | 1 + src/track.ts | 46 +++++++++++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7056971f7df..14704caab9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Fix bug where functions shell failed to invoke event triggered functions in debug mode. (#5609) - Fixed bug with the web frameworks proxy that could see unexpected 404 errors while emulating. (#5525) - Added experimental support for SvelteKit codebases. (#5525) +- Fix bug where eagerly initializing UA failed function deployment that imported firebase-tools as a library. (#5666) diff --git a/src/track.ts b/src/track.ts index e5e196e167a..65d3bcdd817 100644 --- a/src/track.ts +++ b/src/track.ts @@ -12,8 +12,13 @@ const pkg = require("../package.json"); export const EMULATOR_GA4_MEASUREMENT_ID = process.env.FIREBASE_EMULATOR_GA4_MEASUREMENT_ID || "G-KYP2JMPFC0"; +/** + * UA is enabled only if: + * 1) Entrypoint to the code is Firebase CLI (not require("firebase-tools")). + * 2) User opted-in. + */ export function usageEnabled(): boolean { - return !!configstore.get("usage"); + return !!process.env.IS_FIREBASE_CLI && !!configstore.get("usage"); } // The Tracking ID for the Universal Analytics property for all of the CLI @@ -23,25 +28,32 @@ export function usageEnabled(): boolean { // https://support.google.com/analytics/answer/11583528 const FIREBASE_ANALYTICS_UA = process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3"; -// Identifier for the client (UUID) in the CLI UA. -let anonId = configstore.get("analytics-uuid"); -if (!anonId) { - anonId = uuidV4(); - configstore.set("analytics-uuid", anonId); -} +let visitor: ua.Visitor; + +function ensureUAVisitor(): void { + if (!visitor) { + // Identifier for the client (UUID) in the CLI UA. + let anonId = configstore.get("analytics-uuid") as string; + if (!anonId) { + anonId = uuidV4(); + configstore.set("analytics-uuid", anonId); + } -const visitor = ua(FIREBASE_ANALYTICS_UA, anonId, { - strictCidFormat: false, - https: true, -}); + visitor = ua(FIREBASE_ANALYTICS_UA, anonId, { + strictCidFormat: false, + https: true, + }); -visitor.set("cd1", process.platform); // Platform -visitor.set("cd2", process.version); // NodeVersion -visitor.set("cd3", process.env.FIREPIT_VERSION || "none"); // FirepitVersion + visitor.set("cd1", process.platform); // Platform + visitor.set("cd2", process.version); // NodeVersion + visitor.set("cd3", process.env.FIREPIT_VERSION || "none"); // FirepitVersion + } +} -export function track(action: string, label: string, duration: number = 0): Promise { +export function track(action: string, label: string, duration = 0): Promise { + ensureUAVisitor(); return new Promise((resolve) => { - if (configstore.get("tokens") && usageEnabled()) { + if (usageEnabled() && configstore.get("tokens")) { visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(() => { // we could handle errors here, but we won't resolve(); @@ -105,7 +117,7 @@ export interface AnalyticsParams { * length <= 40, alpha-numeric characters and underscores only * (*no spaces*), and must start with an alphabetic character) * @param params custom and standard parameters attached to the event - * @returns a Promise fulfilled when the event reaches the server or fails + * @return a Promise fulfilled when the event reaches the server or fails * (never rejects unless `emulatorSession().validateOnly` is set) * * Note: On performance or latency critical paths, the returned Promise may be From 6eaf24ae7e16ec28802d4eaf63045df1db5eae0c Mon Sep 17 00:00:00 2001 From: James Daniels Date: Fri, 7 Apr 2023 12:32:29 -0400 Subject: [PATCH 0864/1699] Vite and NPM workspace fixes (#5640) * Better error message if a framework isn't detected * Handle NPM workspaces for both Vite and Angular * Error if the node_module binary isn't found * Force vite build to production mode ------ Co-authored-by: Tatsuya Yamamoto --- .eslintrc.js | 2 ++ CHANGELOG.md | 2 ++ src/frameworks/angular/index.ts | 14 +++++++------- src/frameworks/index.ts | 29 +++++++++++++++++++++++------ src/frameworks/vite/index.ts | 14 +++++--------- src/test/frameworks/utils.spec.ts | 23 +++++++++++++++++++++-- 6 files changed, 60 insertions(+), 24 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 923fd5158d3..466fcbb9463 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -125,5 +125,7 @@ module.exports = { "scripts/webframeworks-deploy-tests/hosting/**", "scripts/frameworks-tests/vite-project/**", "/src/frameworks/docs/**", + // This file is taking a very long time to lint, 2-4m + "src/emulator/auth/schema.ts", ], }; diff --git a/CHANGELOG.md b/CHANGELOG.md index 14704caab9e..6b368a4a4ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ - Fix bug where functions shell failed to invoke event triggered functions in debug mode. (#5609) - Fixed bug with the web frameworks proxy that could see unexpected 404 errors while emulating. (#5525) - Added experimental support for SvelteKit codebases. (#5525) +- Allow usage of Angular and Vite within an NPM workspace. (#5640) +- Force Vite to build the production bundle when deploying to Hosting. (#5640) - Fix bug where eagerly initializing UA failed function deployment that imported firebase-tools as a library. (#5666) diff --git a/src/frameworks/angular/index.ts b/src/frameworks/angular/index.ts index 8f2c18f26ac..f234c0d9bc1 100644 --- a/src/frameworks/angular/index.ts +++ b/src/frameworks/angular/index.ts @@ -10,6 +10,7 @@ import { Discovery, findDependency, FrameworkType, + getNodeModuleBin, relativeRequire, SupportLevel, } from ".."; @@ -20,7 +21,6 @@ export const name = "Angular"; export const support = SupportLevel.Experimental; export const type = FrameworkType.Framework; -const CLI_COMMAND = join("node_modules", ".bin", process.platform === "win32" ? "ng.cmd" : "ng"); const DEFAULT_BUILD_SCRIPT = ["ng build"]; export async function discover(dir: string): Promise { @@ -70,7 +70,8 @@ export async function build(dir: string): Promise { if (prerenderTarget) { // TODO there is a bug here. Spawn for now. // await scheduleTarget(prerenderTarget); - execSync(`${CLI_COMMAND} run ${targetStringFromTarget(prerenderTarget)}`, { + const cli = getNodeModuleBin("ng", dir); + execSync(`${cli} run ${targetStringFromTarget(prerenderTarget)}`, { cwd: dir, stdio: "inherit", }); @@ -91,11 +92,10 @@ export async function getDevModeHandle(dir: string) { const host = new Promise((resolve) => { // Can't use scheduleTarget since that—like prerender—is failing on an ESM bug // will just grep for the hostname - const serve = spawn( - CLI_COMMAND, - ["run", targetStringFromTarget(serveTarget), "--host", "localhost"], - { cwd: dir } - ); + const cli = getNodeModuleBin("ng", dir); + const serve = spawn(cli, ["run", targetStringFromTarget(serveTarget), "--host", "localhost"], { + cwd: dir, + }); serve.stdout.on("data", (data: any) => { process.stdout.write(data); const match = data.toString().match(/(http:\/\/localhost:\d+)/); diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 3352682085e..2e5f0b84913 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -28,6 +28,7 @@ import { HostingRewrites } from "../firebaseConfig"; import * as experiments from "../experiments"; import { ensureTargeted } from "../functions/ensureTargeted"; import { implicitInit } from "../hosting/implicitInit"; +import { fileExistsSync } from "../fsutils"; // Use "true &&"" to keep typescript from compiling this file and rewriting // the import statement into a require @@ -124,8 +125,6 @@ const DEFAULT_FIND_DEP_OPTIONS: FindDepOptions = { omitDev: true, }; -const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm"; - export const WebFrameworks: Record = Object.fromEntries( readdirSync(__dirname) .filter((path) => statSync(join(__dirname, path)).isDirectory()) @@ -233,15 +232,30 @@ function scanDependencyTree(searchingFor: string, dependencies = {}): any { return; } +export function getNodeModuleBin(name: string, cwd: string) { + const cantFindExecutable = new FirebaseError(`Could not find the ${name} executable.`); + const npmBin = spawnSync("npm", ["bin"], { cwd }).stdout?.toString().trim(); + if (!npmBin) { + throw cantFindExecutable; + } + const path = join(npmBin, name); + if (!fileExistsSync(path)) { + throw cantFindExecutable; + } + return path; +} + /** * */ export function findDependency(name: string, options: Partial = {}) { - const { cwd, depth, omitDev } = { ...DEFAULT_FIND_DEP_OPTIONS, ...options }; + const { cwd: dir, depth, omitDev } = { ...DEFAULT_FIND_DEP_OPTIONS, ...options }; + const cwd = spawnSync("npm", ["root"], { cwd: dir }).stdout?.toString().trim(); + if (!cwd) return; const env: any = Object.assign({}, process.env); delete env.NODE_ENV; const result = spawnSync( - NPM_COMMAND, + "npm", [ "list", name, @@ -395,7 +409,10 @@ export async function prepareFrameworks( process.env.__FIREBASE_DEFAULTS__ = JSON.stringify(firebaseDefaults); } const results = await discover(getProjectPath()); - if (!results) throw new Error("Epic fail."); + if (!results) + throw new FirebaseError( + "Unable to detect the web framework in use, check firebase-debug.log for more info." + ); const { framework, mayWantBackend, publicDirectory } = results; const { build, @@ -569,7 +586,7 @@ ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\ await Promise.all(envs.map((path) => copyFile(path, join(functionsDist, basename(path))))); - execSync(`${NPM_COMMAND} i --omit dev --no-audit`, { + execSync(`npm i --omit dev --no-audit`, { cwd: functionsDist, stdio: "inherit", }); diff --git a/src/frameworks/vite/index.ts b/src/frameworks/vite/index.ts index f4675b34585..e1e4785ef4c 100644 --- a/src/frameworks/vite/index.ts +++ b/src/frameworks/vite/index.ts @@ -3,7 +3,7 @@ import { spawn } from "cross-spawn"; import { existsSync } from "fs"; import { copy, pathExists } from "fs-extra"; import { join } from "path"; -import { findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; +import { findDependency, FrameworkType, getNodeModuleBin, relativeRequire, SupportLevel } from ".."; import { promptOnce } from "../../prompt"; import { simpleProxy, warnIfCustomBuildScript } from "../utils"; @@ -11,12 +11,6 @@ export const name = "Vite"; export const support = SupportLevel.Experimental; export const type = FrameworkType.Toolchain; -const CLI_COMMAND = join( - "node_modules", - ".bin", - process.platform === "win32" ? "vite.cmd" : "vite" -); - export const DEFAULT_BUILD_SCRIPT = ["vite build", "tsc && vite build"]; export const initViteTemplate = (template: string) => async (setup: any, config: any) => @@ -67,10 +61,11 @@ export async function build(root: string) { const { build } = relativeRequire(root, "vite"); await warnIfCustomBuildScript(root, name, DEFAULT_BUILD_SCRIPT); + // SvelteKit uses process.cwd() unfortunately, chdir const cwd = process.cwd(); process.chdir(root); - await build({ root }); + await build({ root, mode: "production" }); process.chdir(cwd); } @@ -84,7 +79,8 @@ export async function getDevModeHandle(dir: string) { const host = new Promise((resolve) => { // Can't use scheduleTarget since that—like prerender—is failing on an ESM bug // will just grep for the hostname - const serve = spawn(CLI_COMMAND, [], { cwd: dir }); + const cli = getNodeModuleBin("vite", dir); + const serve = spawn(cli, [], { cwd: dir }); serve.stdout.on("data", (data: any) => { process.stdout.write(data); const match = data.toString().match(/(http:\/\/.+:\d+)/); diff --git a/src/test/frameworks/utils.spec.ts b/src/test/frameworks/utils.spec.ts index ebb675aadbc..821485578ab 100644 --- a/src/test/frameworks/utils.spec.ts +++ b/src/test/frameworks/utils.spec.ts @@ -1,11 +1,30 @@ import { expect } from "chai"; import * as sinon from "sinon"; import * as fs from "fs"; +import { resolve, join } from "path"; -import { warnIfCustomBuildScript } from "../../frameworks/utils"; -import { isUrl } from "../../frameworks/utils"; +import { warnIfCustomBuildScript, isUrl } from "../../frameworks/utils"; +import { getNodeModuleBin } from "../../frameworks"; describe("Frameworks utils", () => { + describe("getNodeModuleBin", () => { + it("should return expected tsc path", () => { + expect(getNodeModuleBin("tsc", __dirname)).to.equal( + resolve(join(__dirname, "..", "..", "..", "node_modules", ".bin", "tsc")) + ); + }); + it("should throw when npm root not found", () => { + expect(() => { + getNodeModuleBin("tsc", "/"); + }).to.throw("Could not find the tsc executable."); + }); + it("should throw when executable not found", () => { + expect(() => { + getNodeModuleBin("xxxxx", __dirname); + }).to.throw("Could not find the xxxxx executable."); + }); + }); + describe("isUrl", () => { it("should identify http URL", () => { expect(isUrl("http://firebase.google.com")).to.be.true; From 811dbaca9dd45639a0fd998dca95c0ee504a87a0 Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Mon, 10 Apr 2023 09:55:52 -0700 Subject: [PATCH 0865/1699] Added support for publishing Extension versions from remote repo (#5160) * Added function to publish from remote repo. * Adde fetching and validating of repo archive. * Added comments and displaying of release notes. * Added note for changing default. * Formatting. :-) * More formatting. * Continue root prompt if getExtensionVersion() fails for any reason. * Added prompt for release stage. * Formatting. * Added pre-validation to show helpful root directory message. * Formatting. * Improved text formatting. * Formatting. * Update src/extensions/extensionsHelper.ts Co-authored-by: joehan * Refactored code to be more readable. * Wording. * Formatting. * Typo. * Update src/extensions/extensionsHelper.ts Co-authored-by: joehan * Require ref if repo or root are set. * Default to publish from repo if any corresponding arguments are set. * Fixed repo regex. * Added changelog. * Formats --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + src/commands/ext-dev-publish.ts | 50 ++- src/extensions/extensionsApi.ts | 32 +- src/extensions/extensionsHelper.ts | 428 ++++++++++++++++++---- src/extensions/localHelper.ts | 2 +- src/extensions/types.ts | 2 + src/test/extensions/extensionsApi.spec.ts | 38 +- 7 files changed, 432 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b368a4a4ff..adc80068ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - Allow usage of Angular and Vite within an NPM workspace. (#5640) - Force Vite to build the production bundle when deploying to Hosting. (#5640) - Fix bug where eagerly initializing UA failed function deployment that imported firebase-tools as a library. (#5666) +- Added ability to publish extension versions directly from GitHub. (#5160) diff --git a/src/commands/ext-dev-publish.ts b/src/commands/ext-dev-publish.ts index bd37163ef53..f402480887a 100644 --- a/src/commands/ext-dev-publish.ts +++ b/src/commands/ext-dev-publish.ts @@ -3,7 +3,11 @@ import { marked } from "marked"; import * as TerminalRenderer from "marked-terminal"; import { Command } from "../command"; -import { publishExtensionVersionFromLocalSource, logPrefix } from "../extensions/extensionsHelper"; +import { + logPrefix, + publishExtensionVersionFromLocalSource, + publishExtensionVersionFromRemoteRepo, +} from "../extensions/extensionsHelper"; import * as refs from "../extensions/refs"; import { findExtensionYaml } from "../extensions/localHelper"; import { consoleInstallLink } from "../extensions/publishHelpers"; @@ -20,7 +24,16 @@ marked.setOptions({ */ export const command = new Command("ext:dev:publish ") .description(`publish a new version of an extension`) - .option(`-s, --stage `, `release stage (supports "rc", "alpha", "beta", and "stable")`) + .option(`-s, --stage `, `release stage (supports "alpha", "beta", "rc", and "stable")`) + .option( + `--repo `, + `Public Git repo URI (only required for first version from repo, cannot be changed)` + ) + .option(`--ref `, `commit hash, branch, or tag to build from the repo (defaults to HEAD)`) + .option( + `--root `, + `root directory that contains this Extension (defaults to previous version's root or root of repo if none set)` + ) .withForce() .help( "if you have not previously published a version of this extension, this will " + @@ -44,15 +57,30 @@ export const command = new Command("ext:dev:publish ") )}'. Please use the format '${clc.bold("/")}'.` ); } - const extensionYamlDirectory = findExtensionYaml(process.cwd()); - const res = await publishExtensionVersionFromLocalSource({ - publisherId, - extensionId, - rootDirectory: extensionYamlDirectory, - nonInteractive: options.nonInteractive, - force: options.force, - stage: options.stage ?? "stable", - }); + let res; + // TODO: Default to this path instead of local source in a major version. + if (options.repo || options.root || options.ref) { + res = await publishExtensionVersionFromRemoteRepo({ + publisherId, + extensionId, + repoUri: options.repo, + sourceRef: options.ref, + extensionRoot: options.root, + nonInteractive: options.nonInteractive, + force: options.force, + stage: options.stage, + }); + } else { + const extensionYamlDirectory = findExtensionYaml(process.cwd()); + res = await publishExtensionVersionFromLocalSource({ + publisherId, + extensionId, + rootDirectory: extensionYamlDirectory, + nonInteractive: options.nonInteractive, + force: options.force, + stage: options.stage ?? "stable", + }); + } if (res) { utils.logLabeledBullet(logPrefix, marked(`[Install Link](${consoleInstallLink(res.ref)})`)); } diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index b0afd26cd20..8791be988d1 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -620,25 +620,37 @@ export async function undeprecateExtensionVersion(extensionRef: string): Promise * @param extensionVersionRef user-friendly identifier for the ExtensionVersion (publisher-id/extension-id@1.0.0) * @param extensionRoot directory location of extension.yaml in the archived package, defaults to "/". */ -export async function publishExtensionVersion( - extensionVersionRef: string, - packageUri: string, - extensionRoot?: string -): Promise { - const ref = refs.parse(extensionVersionRef); +export async function publishExtensionVersion(args: { + extensionVersionRef: string; + packageUri?: string; + extensionRoot?: string; + repoUri?: string; + sourceRef?: string; +}): Promise { + const ref = refs.parse(args.extensionVersionRef); if (!ref.version) { - throw new FirebaseError(`ExtensionVersion ref "${extensionVersionRef}" must supply a version.`); + throw new FirebaseError( + `ExtensionVersion ref "${args.extensionVersionRef}" must supply a version.` + ); } // TODO(b/185176470): Publishing an extension with a previously deleted name will return 409. // Need to surface a better error, potentially by calling getExtension. const publishRes = await apiClient.post< - { versionId: string; packageUri: string; extensionRoot: string }, + { + versionId: string; + packageUri: string; + extensionRoot: string; + repoUri: string; + sourceRef: string; + }, ExtensionVersion >(`/${refs.toExtensionName(ref)}/versions:publish`, { versionId: ref.version, - packageUri, - extensionRoot: extensionRoot ?? "/", + packageUri: args.packageUri ?? "", + extensionRoot: args.extensionRoot ?? "/", + repoUri: args.repoUri ?? "", + sourceRef: args.sourceRef ?? "", }); const pollRes = await operationPoller.pollOperation({ apiOrigin: extensionsOrigin, diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index f08b3c7506c..ced13ec72b6 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -1,6 +1,11 @@ import * as clc from "colorette"; import * as ora from "ora"; import * as semver from "semver"; +import * as tmp from "tmp"; +import * as fs from "fs-extra"; +import * as unzipper from "unzipper"; +import fetch from "node-fetch"; +import * as path from "path"; import { marked } from "marked"; const TerminalRenderer = require("marked-terminal"); @@ -23,13 +28,14 @@ import { getProjectId } from "../projectUtils"; import { createSource, getExtension, + getExtensionVersion, getInstance, listExtensionVersions, publishExtensionVersion, } from "./extensionsApi"; -import { ExtensionSource, ExtensionVersion, Param } from "./types"; +import { Extension, ExtensionSource, ExtensionSpec, ExtensionVersion, Param } from "./types"; import * as refs from "./refs"; -import { getLocalExtensionSpec } from "./localHelper"; +import { EXTENSIONS_SPEC_FILE, readFile, getLocalExtensionSpec } from "./localHelper"; import { promptOnce } from "../prompt"; import { logger } from "../logger"; import { envOverride } from "../utils"; @@ -84,7 +90,12 @@ export const AUTOPOULATED_PARAM_PLACEHOLDERS = { DATABASE_INSTANCE: "project-id-default-rtdb", DATABASE_URL: "https://project-id-default-rtdb.firebaseio.com", }; -export type ReleaseStage = "stable" | "alpha" | "beta" | "rc"; +export const resourceTypeToNiceName: Record = { + "firebaseextensions.v1beta.function": "Cloud Function", +}; +export type ReleaseStage = "alpha" | "beta" | "rc" | "stable"; +const repoRegex = new RegExp(`^https:\/\/github\.com\/[^\/]+\/[^\/]+$`); +const stageOptions = ["alpha", "beta", "rc", "stable"]; /** * Turns database URLs (e.g. https://my-db.firebaseio.com) into database instance names @@ -368,6 +379,26 @@ export async function promptForValidInstanceId(instanceId: string): Promise { + let repoIsValid = false; + let extensionRoot = ""; + while (!repoIsValid) { + extensionRoot = await promptOnce({ + type: "input", + message: "Enter the GitHub repo URI where this Extension's source code is located:", + }); + if (!repoRegex.test(extensionRoot)) { + logger.info("Repo URI must follow this format: https://github.com//"); + } else { + repoIsValid = true; + } + } + return extensionRoot; +} + export async function ensureExtensionsApiEnabled(options: any): Promise { const projectId = getProjectId(options); if (!projectId) { @@ -407,7 +438,6 @@ export async function incrementPrereleaseVersion( extensionVersion: string, stage: ReleaseStage ): Promise { - const stageOptions = ["stable", "alpha", "beta", "rc"]; if (!stageOptions.includes(stage)) { throw new FirebaseError(`--stage flag only supports the following values: ${stageOptions}`); } @@ -436,19 +466,23 @@ export async function incrementPrereleaseVersion( } /** + * Validates the Extension spec. + * + * @param publisherId the ID of the Publisher + * @param extensionId the ID of the Extension + * @param rootDirectory the directory with the Extension's source + * @param latestVersion the latest version of the Extension (if any) + * @param stage the release stage * - * @param publisherId the publisher profile to publish this extension under. - * @param extensionId the ID of the extension. This must match the `name` field of extension.yaml. - * @param rootDirectory the directory containing extension.yaml */ -export async function publishExtensionVersionFromLocalSource(args: { +async function validateExtensionSpec(args: { publisherId: string; extensionId: string; rootDirectory: string; - nonInteractive: boolean; - force: boolean; + latestVersion?: string; stage: ReleaseStage; -}): Promise { +}): Promise<{ extensionSpec: ExtensionSpec; notes: string }> { + const extensionRef = `${args.publisherId}/${args.extensionId}`; const extensionSpec = await getLocalExtensionSpec(args.rootDirectory); if (extensionSpec.name !== args.extensionId) { throw new FirebaseError( @@ -465,19 +499,14 @@ export async function publishExtensionVersionFromLocalSource(args: { AUTOPOULATED_PARAM_PLACEHOLDERS ); validateSpec(subbedSpec); + + // Increment the pre-release annotation if it is not a stable version. extensionSpec.version = await incrementPrereleaseVersion( - `${args.publisherId}/${args.extensionId}`, + extensionRef, extensionSpec.version, args.stage ); - let extension; - try { - extension = await getExtension(`${args.publisherId}/${args.extensionId}`); - } catch (err: any) { - // Silently fail and continue the publish flow if extension not found. - } - let notes: string; try { const changes = getLocalChangelog(args.rootDirectory); @@ -491,9 +520,8 @@ export async function publishExtensionVersionFromLocalSource(args: { ) ); } - // Skip this check for prerelease versions - if (!notes && !semver.prerelease(extensionSpec.version) && extension) { - // If this is not the first version of this extension, we require release notes + // Notes are required for all stable versions after the initial release. + if (!notes && !semver.prerelease(extensionSpec.version) && args.latestVersion) { throw new FirebaseError( `No entry for version ${extensionSpec.version} found in CHANGELOG.md. ` + "Please add one so users know what has changed in this version. " + @@ -502,52 +530,281 @@ export async function publishExtensionVersionFromLocalSource(args: { ) ); } - displayReleaseNotes(args.publisherId, args.extensionId, extensionSpec.version, notes); - if ( - !(await confirm({ + + if (args.latestVersion) { + if (semver.lt(extensionSpec.version, args.latestVersion)) { + throw new FirebaseError( + `The version you are trying to publish (${clc.bold( + extensionSpec.version + )}) is lower than the current version (${clc.bold( + args.latestVersion + )}) for the extension '${clc.bold( + extensionRef + )}'. Please make sure this version is greater than the current version (${clc.bold( + args.latestVersion + )}) inside of extension.yaml.\n`, + { exit: 104 } + ); + } else if (semver.eq(extensionSpec.version, args.latestVersion)) { + throw new FirebaseError( + `The version you are trying to publish (${clc.bold( + extensionSpec.version + )}) already exists for Extension '${clc.bold( + extensionRef + )}'. Please increment the version inside of extension.yaml.\n`, + { exit: 103 } + ); + } + } + + return { extensionSpec, notes }; +} + +/** + * Publishes an Extension version from a remote repo. + * + * @param publisherId the ID of the Publisher this Extension will be published under + * @param extensionId the ID of the Extension to be published + * @param repoUri the URI of the repo where this Extension's source exists + * @param sourceRef the commit hash, branch, or tag name in the repo to publish from + * @param extensionRoot the root directory that contains this Extension's source + * @param stage the release stage to publish + * @param nonInteractive whether to display prompts + * @param force whether to force confirmations + */ +export async function publishExtensionVersionFromRemoteRepo(args: { + publisherId: string; + extensionId: string; + repoUri: string; + sourceRef: string; + extensionRoot: string; + stage: ReleaseStage; + nonInteractive: boolean; + force: boolean; +}): Promise { + const extensionRef = `${args.publisherId}/${args.extensionId}`; + + // Check if Extension already exists. + let extension: Extension | undefined; + try { + extension = await getExtension(extensionRef); + } catch (err: any) { + // Silently fail and continue the publish flow if Extension not found. + } + + // Prompt for repo URI and validate that it hasn't changed if previously set. + if (args.repoUri && !repoRegex.test(args.repoUri)) { + throw new FirebaseError("Repo URI must follow this format: https://github.com//"); + } + let repoUri = args.repoUri || extension?.repoUri; + if (!repoUri) { + if (!args.nonInteractive) { + repoUri = await promptForValidRepoURI(); + } else { + throw new FirebaseError("Repo URI is required but not currently set."); + } + } + if (extension?.repoUri && extension.repoUri !== repoUri) { + throw new FirebaseError( + `Repo URI '${clc.bold(args.repoUri)}' does not match repo URI '${clc.bold( + extension.repoUri! + )}' already associated with Extension ${clc.bold(extensionRef)}. Repo URI cannot be changed.` + ); + } + if (!extension?.repoUri) { + logger.info( + `\n${clc.red("Warning:")} You are about to associate repo URI ${clc.bold( + repoUri + )} with Extension ${clc.bold( + extensionRef + )}. This cannot be changed. All future verifiable versions must be published from this repo. ` + + `You can continue publishing unverifiable versions from local source.` + ); + const confirmed = await confirm({ nonInteractive: args.nonInteractive, force: args.force, default: false, - })) - ) { - return; + }); + if (!confirmed) { + return; + } + } else { + logger.info( + `Extension ${clc.bold(extensionRef)} is published from ${clc.bold(extension?.repoUri)}.` + ); + } + + let extensionRoot = args.extensionRoot; + let defaultRoot = "/"; + if (!extensionRoot) { + // Try to get the cached root only if the root hasn't already been passed in. + if (extension) { + try { + const extensionVersionRef = `${extensionRef}@${extension.latestVersion}`; + const extensionVersion = await getExtensionVersion(extensionVersionRef); + if (extensionVersion.extensionRoot) { + defaultRoot = extensionVersion.extensionRoot; + } + } catch (err: any) { + // Not a critical error so continue with default root. + } + } + extensionRoot = defaultRoot; + if (!args.nonInteractive) { + extensionRoot = await promptOnce({ + type: "input", + message: + "Enter this Extension's root directory in the repo (defaults to previous root if set):", + default: defaultRoot, + }); + } } - if ( - extension && - extension.latestVersion && - semver.lt(extensionSpec.version, extension.latestVersion) - ) { - // publisher's version is less than current latest version. + // Prompt for source ref and default to HEAD. + let sourceRef = args.sourceRef; + const defaultSourceRef = "HEAD"; + if (!sourceRef) { + if (!args.nonInteractive) { + sourceRef = await promptOnce({ + type: "input", + message: "Enter the commit hash, branch, or tag name to build from in the repo:", + default: defaultSourceRef, + }); + } else { + sourceRef = defaultSourceRef; + } + } + + // Prompt for release stage. + let stage = args.stage; + const defaultStage = "rc"; + if (!stage) { + if (!args.nonInteractive) { + stage = await promptOnce({ + type: "list", + message: "Choose the release stage (pre-release annotations will be auto-incremented):", + choices: stageOptions, + default: defaultStage, + }); + } else { + stage = defaultStage; + } + } + + // Fetch and validate Extension from remote repo. + logger.info("Downloading and validating source code..."); + const archiveUri = `${repoUri}/archive/${sourceRef}.zip`; + const tempDirectory = tmp.dirSync({ unsafeCleanup: true }); + try { + const response = await fetch(archiveUri); + if (response.ok) { + await response.body.pipe(unzipper.Extract({ path: tempDirectory.name })).promise(); // eslint-disable-line new-cap + } + } catch (err: any) { throw new FirebaseError( - `The version you are trying to publish (${clc.bold( - extensionSpec.version - )}) is lower than the current version (${clc.bold( - extension.latestVersion - )}) for the extension '${clc.bold( - `${args.publisherId}/${args.extensionId}` - )}'. Please make sure this version is greater than the current version (${clc.bold( - extension.latestVersion - )}) inside of extension.yaml.\n`, - { exit: 104 } + `Failed to fetch Extension archive ${archiveUri}. Please check the repo URI and source ref. ${err}` ); - } else if ( - extension && - extension.latestVersion && - semver.eq(extensionSpec.version, extension.latestVersion) - ) { - // publisher's version is equal to the current latest version. + } + const archiveName = fs.readdirSync(tempDirectory.name)[0]; + const rootDirectory = path.join(tempDirectory.name, archiveName, extensionRoot); + // Pre-validation to show a more useful error message in the context of a temp directory. + try { + readFile(path.resolve(rootDirectory, EXTENSIONS_SPEC_FILE)); + } catch (err: any) { throw new FirebaseError( - `The version you are trying to publish (${clc.bold( - extensionSpec.version - )}) already exists for the extension '${clc.bold( - `${args.publisherId}/${args.extensionId}` - )}'. Please increment the version inside of extension.yaml.\n`, - { exit: 103 } + `Failed to find ${clc.bold(EXTENSIONS_SPEC_FILE)} in directory ${clc.bold( + extensionRoot + )}. Please verify the root and try again.` ); } + const { extensionSpec, notes } = await validateExtensionSpec({ + publisherId: args.publisherId, + extensionId: args.extensionId, + rootDirectory: rootDirectory, + latestVersion: extension?.latestVersion, + stage: stage, + }); + + const sourceUri = path.join(repoUri, "tree", sourceRef, extensionRoot); + displayReleaseNotes(extensionRef, extensionSpec.version, notes, sourceUri); + const confirmed = await confirm({ + nonInteractive: args.nonInteractive, + force: args.force, + default: false, + }); + if (!confirmed) { + return; + } + + // Publish the Extension version. + const extensionVersionRef = `${extensionRef}@${extensionSpec.version}`; + const publishSpinner = ora(`Publishing ${clc.bold(extensionVersionRef)}`); + let res; + try { + publishSpinner.start(); + res = await publishExtensionVersion({ + extensionVersionRef, + packageUri: "", + extensionRoot, + repoUri, + sourceRef: args.sourceRef, + }); + publishSpinner.succeed(` Successfully published ${clc.bold(extensionRef)}`); + } catch (err: any) { + publishSpinner.fail(); + if (err.status === 404) { + throw getMissingPublisherError(args.publisherId); + } + throw err; + } + return res; +} - const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`; +/** + * Publishes an Extension version from local source. + * + * @param publisherId the ID of the Publisher this Extension will be published under + * @param extensionId the ID of the Extension to be published + * @param rootDirectory the root directory that contains this Extension's source + * @param stage the release stage to publish + * @param nonInteractive whether to display prompts + * @param force whether to force confirmations + */ +export async function publishExtensionVersionFromLocalSource(args: { + publisherId: string; + extensionId: string; + rootDirectory: string; + stage: ReleaseStage; + nonInteractive: boolean; + force: boolean; +}): Promise { + let extension; + try { + extension = await getExtension(`${args.publisherId}/${args.extensionId}`); + } catch (err: any) { + // Silently fail and continue the publish flow if extension not found. + } + + const { extensionSpec, notes } = await validateExtensionSpec({ + publisherId: args.publisherId, + extensionId: args.extensionId, + rootDirectory: args.rootDirectory, + latestVersion: extension?.latestVersion, + stage: args.stage, + }); + + const extensionRef = `${args.publisherId}/${args.extensionId}`; + displayReleaseNotes(extensionRef, extensionSpec.version, notes); + const confirmed = await confirm({ + nonInteractive: args.nonInteractive, + force: args.force, + default: false, + }); + if (!confirmed) { + return; + } + + const extensionVersionRef = `${extensionRef}@${extensionSpec.version}`; let packageUri: string; let objectPath = ""; const uploadSpinner = ora(" Archiving and uploading extension source code"); @@ -562,22 +819,16 @@ export async function publishExtensionVersionFromLocalSource(args: { original: err, }); } - const publishSpinner = ora(`Publishing ${clc.bold(ref)}`); + const publishSpinner = ora(`Publishing ${clc.bold(extensionVersionRef)}`); let res; try { publishSpinner.start(); - res = await publishExtensionVersion(ref, packageUri); - publishSpinner.succeed(` Successfully published ${clc.bold(ref)}`); + res = await publishExtensionVersion({ extensionVersionRef, packageUri }); + publishSpinner.succeed(` Successfully published ${clc.bold(extensionVersionRef)}`); } catch (err: any) { publishSpinner.fail(); if (err.status === 404) { - throw new FirebaseError( - marked( - `Couldn't find publisher ID '${clc.bold( - args.publisherId - )}'. Please ensure that you have registered this ID. To register as a publisher, you can check out the [Firebase documentation](https://firebase.google.com/docs/extensions/alpha/share#register_as_an_extensions_publisher) for step-by-step instructions.` - ) - ); + throw getMissingPublisherError(args.publisherId); } throw err; } @@ -585,6 +836,16 @@ export async function publishExtensionVersionFromLocalSource(args: { return res; } +function getMissingPublisherError(publisherId: string): FirebaseError { + return new FirebaseError( + marked( + `Couldn't find publisher ID '${clc.bold( + publisherId + )}'. Please ensure that you have registered this ID. To register as a publisher, you can check out the [Firebase documentation](https://firebase.google.com/docs/extensions/alpha/share#register_as_an_extensions_publisher) for step-by-step instructions.` + ) + ); +} + /** * Creates a source from a local path or URL. If a local path is given, it will be zipped * and uploaded to EXTENSIONS_BUCKET_NAME, and then deleted after the source is created. @@ -652,26 +913,33 @@ export function getPublisherProjectFromName(publisherName: string): number { } /** - * Confirm the version number in extension.yaml with the user . + * Displays the release notes and confirmation message for an Extension about to be published. * - * @param publisherId the publisher ID of the extension being installed - * @param extensionId the extension ID of the extension being installed - * @param versionId the version ID of the extension being installed + * @param extensionRef the ref of the Extension being published + * @param versionId the version ID of the Extension being published + * @param releaseNotes the release notes for the version being published (if any) + * @param sourceUri the repo URI + root from which the Extension will be published + * @param sourceRef the source ref in the repo from which the Extension will be published */ export function displayReleaseNotes( - publisherId: string, - extensionId: string, + extensionRef: string, versionId: string, - releaseNotes?: string + releaseNotes?: string, + sourceUri?: string ): void { + const source = sourceUri || "local source"; const releaseNotesMessage = releaseNotes - ? ` Release notes for this version:\n${marked(releaseNotes)}\n` - : "\n"; + ? `${clc.bold("Release notes:")}\n${marked(releaseNotes)}` + : ""; + const metadataMessage = + `${clc.bold("Extension:")} ${extensionRef}\n` + + `${clc.bold("Version:")} ${clc.bold(clc.green(versionId))}\n` + + `${clc.bold("Source:")} ${source}\n`; const message = - `You are about to publish version ${clc.green(versionId)} of ${clc.green( - `${publisherId}/${extensionId}` - )} to Firebase's registry of extensions.${releaseNotesMessage}` + - "Once an extension version is published, it cannot be changed. If you wish to make changes after publishing, you will need to publish a new version.\n\n"; + `\nYou are about to publish a new version to Firebase's registry of Extensions.\n\n` + + metadataMessage + + releaseNotesMessage + + `\nOnce an Extension version is published, it cannot be changed. If you wish to make changes after publishing, you will need to publish a new version.\n`; logger.info(message); } diff --git a/src/extensions/localHelper.ts b/src/extensions/localHelper.ts index 5946ab5b3b9..bf9e9fcad4b 100644 --- a/src/extensions/localHelper.ts +++ b/src/extensions/localHelper.ts @@ -7,7 +7,7 @@ import { FirebaseError } from "../error"; import { ExtensionSpec } from "./types"; import { logger } from "../logger"; -const EXTENSIONS_SPEC_FILE = "extension.yaml"; +export const EXTENSIONS_SPEC_FILE = "extension.yaml"; const EXTENSIONS_PREINSTALL_FILE = "PREINSTALL.md"; /** diff --git a/src/extensions/types.ts b/src/extensions/types.ts index e5bc4b5481b..8d61a0454b5 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -24,6 +24,7 @@ export interface Extension { createTime: string; latestVersion?: string; latestVersionCreateTime?: string; + repoUri?: string; } export interface ExtensionVersion { @@ -36,6 +37,7 @@ export interface ExtensionVersion { releaseNotes?: string; createTime?: string; deprecationMessage?: string; + extensionRoot?: string; } export interface PublisherProfile { diff --git a/src/test/extensions/extensionsApi.spec.ts b/src/test/extensions/extensionsApi.spec.ts index c2a623b1479..1edf3ae3328 100644 --- a/src/test/extensions/extensionsApi.spec.ts +++ b/src/test/extensions/extensionsApi.spec.ts @@ -689,10 +689,10 @@ describe("publishExtensionVersion", () => { response: TEST_EXT_VERSION_3, }); - const res = await extensionsApi.publishExtensionVersion( - TEST_EXT_VERSION_3.ref, - "www.google.com/test-extension.zip" - ); + const res = await extensionsApi.publishExtensionVersion({ + extensionVersionRef: TEST_EXT_VERSION_3.ref, + packageUri: "www.google.com/test-extension.zip", + }); expect(res).to.deep.equal(TEST_EXT_VERSION_3); expect(nock.isDone()).to.be.true; }); @@ -703,11 +703,11 @@ describe("publishExtensionVersion", () => { .reply(500); await expect( - extensionsApi.publishExtensionVersion( - `${PUBLISHER_ID}/${EXTENSION_ID}@${EXTENSION_VERSION}`, - "www.google.com/test-extension.zip", - "/" - ) + extensionsApi.publishExtensionVersion({ + extensionVersionRef: `${PUBLISHER_ID}/${EXTENSION_ID}@${EXTENSION_VERSION}`, + packageUri: "www.google.com/test-extension.zip", + extensionRoot: "/", + }) ).to.be.rejectedWith(FirebaseError, "HTTP Error: 500, Unknown Error"); expect(nock.isDone()).to.be.true; }); @@ -719,22 +719,22 @@ describe("publishExtensionVersion", () => { nock(api.extensionsOrigin).get(`/${VERSION}/operations/abc123`).reply(502, {}); await expect( - extensionsApi.publishExtensionVersion( - `${PUBLISHER_ID}/${EXTENSION_ID}@${EXTENSION_VERSION}`, - "www.google.com/test-extension.zip", - "/" - ) + extensionsApi.publishExtensionVersion({ + extensionVersionRef: `${PUBLISHER_ID}/${EXTENSION_ID}@${EXTENSION_VERSION}`, + packageUri: "www.google.com/test-extension.zip", + extensionRoot: "/", + }) ).to.be.rejectedWith(FirebaseError, "HTTP Error: 502, Unknown Error"); expect(nock.isDone()).to.be.true; }); it("should throw an error for an invalid ref", async () => { await expect( - extensionsApi.publishExtensionVersion( - `${PUBLISHER_ID}/${EXTENSION_ID}`, - "www.google.com/test-extension.zip", - "/" - ) + extensionsApi.publishExtensionVersion({ + extensionVersionRef: `${PUBLISHER_ID}/${EXTENSION_ID}`, + packageUri: "www.google.com/test-extension.zip", + extensionRoot: "/", + }) ).to.be.rejectedWith(FirebaseError, "ExtensionVersion ref"); }); }); From f0e696958d223ed42e5b9b148b5acf5a858d35c1 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 10 Apr 2023 12:55:03 -0700 Subject: [PATCH 0866/1699] Removes experimental warning from ext-install and deploy (#5678) * Removes experimental warning from ext-install and deploy * Fix tests --- src/commands/ext-install.ts | 7 +--- src/extensions/warnings.ts | 39 ++------------------ src/test/extensions/warnings.spec.ts | 53 +++------------------------- 3 files changed, 8 insertions(+), 91 deletions(-) diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 9b144099c15..c03125256ff 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -165,13 +165,8 @@ async function infoExtensionVersion(args: { extensionVersion: ExtensionVersion; }): Promise { const ref = refs.parse(args.extensionName); - const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref)); await displayExtInfo(args.extensionName, ref.publisherId, args.extensionVersion.spec, true); - await displayWarningPrompts( - ref.publisherId, - extension.registryLaunchStage, - args.extensionVersion - ); + await displayWarningPrompts(ref.publisherId, args.extensionVersion); } interface InstallExtensionOptions { diff --git a/src/extensions/warnings.ts b/src/extensions/warnings.ts index 2d347b0ee69..055815a5ca6 100644 --- a/src/extensions/warnings.ts +++ b/src/extensions/warnings.ts @@ -1,13 +1,12 @@ import { marked } from "marked"; import * as clc from "colorette"; -import { ExtensionVersion, RegistryLaunchStage } from "./types"; +import { ExtensionVersion } from "./types"; import { printSourceDownloadLink } from "./displayExtensionInfo"; import { logPrefix } from "./extensionsHelper"; import { getTrustedPublishers } from "./resolveSource"; import { humanReadable } from "../deploy/extensions/deploymentSummary"; import { InstanceSpec, getExtension } from "../deploy/extensions/planner"; -import { partition } from "../functional"; import * as utils from "../utils"; import { logger } from "../logger"; @@ -30,23 +29,11 @@ function displayEAPWarning({ printSourceDownloadLink(sourceDownloadUri); } -function displayExperimentalWarning() { - utils.logLabeledBullet( - logPrefix, - marked( - `${clc.yellow(clc.bold("Important"))}: This extension is ${clc.bold( - "experimental" - )} and may not be production-ready. Its functionality might change in backward-incompatible ways before its official release, or it may be discontinued.` - ) - ); -} - /** * Show warning if extension is experimental or developed by 3P. */ export async function displayWarningPrompts( publisherId: string, - launchStage: RegistryLaunchStage, extensionVersion: ExtensionVersion ): Promise { const trustedPublishers = await getTrustedPublishers(); @@ -56,8 +43,6 @@ export async function displayWarningPrompts( sourceDownloadUri: extensionVersion.sourceDownloadUri, githubLink: extensionVersion.spec.sourceUrl, }); - } else if (launchStage === RegistryLaunchStage.EXPERIMENTAL) { - displayExperimentalWarning(); } else { // Otherwise, this is an official extension and requires no warning prompts. return; @@ -85,27 +70,9 @@ export async function displayWarningsForDeploy(instancesToCreate: InstanceSpec[] await getExtension(i); } - const [eapExtensions, nonEapExtensions] = partition( - publishedExtensionInstances, + const eapExtensions = publishedExtensionInstances.filter( (i) => !trustedPublishers.includes(i.ref?.publisherId ?? "") ); - // Only mark non-eap extensions as experimental. - const experimental = nonEapExtensions.filter( - (i) => i.extension!.registryLaunchStage === RegistryLaunchStage.EXPERIMENTAL - ); - - if (experimental.length) { - const humanReadableList = experimental.map((i) => `\t${humanReadable(i)}`).join("\n"); - utils.logLabeledBullet( - logPrefix, - marked( - `The following are instances of ${clc.bold( - "experimental" - )} extensions.They may not be production-ready. Their functionality may change in backward-incompatible ways before their official release, or they may be discontinued.\n${humanReadableList}\n`, - { gfm: false } - ) - ); - } if (eapExtensions.length) { const humanReadableList = eapExtensions.map(toListEntry).join("\n"); @@ -119,7 +86,7 @@ export async function displayWarningsForDeploy(instancesToCreate: InstanceSpec[] ) ); } - return experimental.length > 0 || eapExtensions.length > 0; + return eapExtensions.length > 0; } export function outOfBandChangesWarning(instanceIds: string[]) { diff --git a/src/test/extensions/warnings.spec.ts b/src/test/extensions/warnings.spec.ts index d114d073736..204efeb96ce 100644 --- a/src/test/extensions/warnings.spec.ts +++ b/src/test/extensions/warnings.spec.ts @@ -72,38 +72,18 @@ describe("displayWarningPrompts", () => { logLabeledStub.restore(); }); - it("should not warn if from trusted publisher and not experimental", async () => { + it("should not warn if from trusted publisher", async () => { const publisherId = "firebase"; - await warnings.displayWarningPrompts( - publisherId, - RegistryLaunchStage.BETA, - testExtensionVersion - ); + await warnings.displayWarningPrompts(publisherId, testExtensionVersion); expect(logLabeledStub).to.not.have.been.called; }); - it("should warn if experimental", async () => { - const publisherId = "firebase"; - - await warnings.displayWarningPrompts( - publisherId, - RegistryLaunchStage.EXPERIMENTAL, - testExtensionVersion - ); - - expect(logLabeledStub).to.have.been.calledWithMatch("extensions", "experimental"); - }); - it("should warn if the publisher is not on the approved publisher list", async () => { const publisherId = "pubby-mcpublisher"; - await warnings.displayWarningPrompts( - publisherId, - RegistryLaunchStage.BETA, - testExtensionVersion - ); + await warnings.displayWarningPrompts(publisherId, testExtensionVersion); expect(logLabeledStub).to.have.been.calledWithMatch("extensions", "Early Access Program"); }); @@ -124,7 +104,7 @@ describe("displayWarningsForDeploy", () => { logLabeledStub.restore(); }); - it("should not warn or prompt if from trusted publisher and not experimental", async () => { + it("should not warn or prompt if from trusted publisher", async () => { const toCreate = [ testInstanceSpec("firebase", "ext-id-1", RegistryLaunchStage.GA), testInstanceSpec("firebase", "ext-id-2", RegistryLaunchStage.GA), @@ -136,18 +116,6 @@ describe("displayWarningsForDeploy", () => { expect(logLabeledStub).to.not.have.been.called; }); - it("should prompt if experimental", async () => { - const toCreate = [ - testInstanceSpec("firebase", "ext-id-1", RegistryLaunchStage.EXPERIMENTAL), - testInstanceSpec("firebase", "ext-id-2", RegistryLaunchStage.EXPERIMENTAL), - ]; - - const warned = await warnings.displayWarningsForDeploy(toCreate); - - expect(warned).to.be.true; - expect(logLabeledStub).to.have.been.calledWithMatch("extensions", "experimental"); - }); - it("should prompt if the publisher is not on the approved publisher list", async () => { const toCreate = [ testInstanceSpec("pubby-mcpublisher", "ext-id-1", RegistryLaunchStage.GA), @@ -159,17 +127,4 @@ describe("displayWarningsForDeploy", () => { expect(warned).to.be.true; expect(logLabeledStub).to.have.been.calledWithMatch("extensions", "Early Access Program"); }); - - it("should show multiple warnings at once if triggered", async () => { - const toCreate = [ - testInstanceSpec("pubby-mcpublisher", "ext-id-1", RegistryLaunchStage.GA), - testInstanceSpec("firebase", "ext-id-2", RegistryLaunchStage.EXPERIMENTAL), - ]; - - const warned = await warnings.displayWarningsForDeploy(toCreate); - - expect(warned).to.be.true; - expect(logLabeledStub).to.have.been.calledWithMatch("extensions", "Early Access Program"); - expect(logLabeledStub).to.have.been.calledWithMatch("extensions", "experimental"); - }); }); From a103ba71b6253c76541d7b26ef0451466c692455 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 11 Apr 2023 20:56:40 +0000 Subject: [PATCH 0867/1699] 11.26.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7ae69451e28..63b028bb71a 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.25.3", + "version": "11.26.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.25.3", + "version": "11.26.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index e8b3ce51051..c1ca887fcec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.25.3", + "version": "11.26.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 11f7108d36924e08bdbde244d3c2def966de7f2f Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 11 Apr 2023 20:56:53 +0000 Subject: [PATCH 0868/1699] [firebase-release] Removed change log and reset repo after 11.26.0 release --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adc80068ca2..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +0,0 @@ -- Fix bug where functions shell failed to invoke event triggered functions in debug mode. (#5609) -- Fixed bug with the web frameworks proxy that could see unexpected 404 errors while emulating. (#5525) -- Added experimental support for SvelteKit codebases. (#5525) -- Allow usage of Angular and Vite within an NPM workspace. (#5640) -- Force Vite to build the production bundle when deploying to Hosting. (#5640) -- Fix bug where eagerly initializing UA failed function deployment that imported firebase-tools as a library. (#5666) -- Added ability to publish extension versions directly from GitHub. (#5160) From 1793e5be13178f42bac516057c0f478f31e7ef68 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 11 Apr 2023 23:20:30 -0700 Subject: [PATCH 0869/1699] Increases timeout of flaky tests to 5000ms (#5674) Co-authored-by: James Daniels --- src/test/frameworks/utils.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/frameworks/utils.spec.ts b/src/test/frameworks/utils.spec.ts index 821485578ab..483903b36fe 100644 --- a/src/test/frameworks/utils.spec.ts +++ b/src/test/frameworks/utils.spec.ts @@ -12,17 +12,17 @@ describe("Frameworks utils", () => { expect(getNodeModuleBin("tsc", __dirname)).to.equal( resolve(join(__dirname, "..", "..", "..", "node_modules", ".bin", "tsc")) ); - }); + }).timeout(5000); it("should throw when npm root not found", () => { expect(() => { getNodeModuleBin("tsc", "/"); }).to.throw("Could not find the tsc executable."); - }); + }).timeout(5000); it("should throw when executable not found", () => { expect(() => { getNodeModuleBin("xxxxx", __dirname); }).to.throw("Could not find the xxxxx executable."); - }); + }).timeout(5000); }); describe("isUrl", () => { From 820717038193dc2dca7ba4f8b5b163d793f32e7a Mon Sep 17 00:00:00 2001 From: Leonardo Ortiz Date: Wed, 12 Apr 2023 11:38:27 -0300 Subject: [PATCH 0870/1699] Nuxt dev mode support (#5551) Co-authored-by: Claudio F Co-authored-by: James Daniels --- CHANGELOG.md | 1 + src/frameworks/nuxt/index.ts | 60 ++++++++++---- src/frameworks/nuxt/interfaces.ts | 11 ++- src/frameworks/nuxt/utils.ts | 9 +++ src/frameworks/nuxt2/index.ts | 18 ++--- src/test/frameworks/nuxt/index.spec.ts | 107 +++++++++++++++++++++++++ src/test/frameworks/nuxt/utils.spec.ts | 44 ---------- 7 files changed, 173 insertions(+), 77 deletions(-) create mode 100644 src/test/frameworks/nuxt/index.spec.ts delete mode 100644 src/test/frameworks/nuxt/utils.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..46597d9421d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Default emulators:start to use fast dev-mode for Nuxt3 applications (#5551) diff --git a/src/frameworks/nuxt/index.ts b/src/frameworks/nuxt/index.ts index c92fcb03a32..0d62a76e2af 100644 --- a/src/frameworks/nuxt/index.ts +++ b/src/frameworks/nuxt/index.ts @@ -1,39 +1,42 @@ import { copy, pathExists } from "fs-extra"; import { readFile } from "fs/promises"; import { join } from "path"; -import { gte } from "semver"; -import { findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; -import { warnIfCustomBuildScript } from "../utils"; +import { lt } from "semver"; +import { spawn } from "cross-spawn"; +import { FrameworkType, getNodeModuleBin, relativeRequire, SupportLevel } from ".."; +import { simpleProxy, warnIfCustomBuildScript } from "../utils"; +import { getNuxtVersion } from "./utils"; export const name = "Nuxt"; export const support = SupportLevel.Experimental; export const type = FrameworkType.Toolchain; -import { NuxtDependency } from "./interfaces"; import { nuxtConfigFilesExist } from "./utils"; +import type { NuxtOptions } from "./interfaces"; -const DEFAULT_BUILD_SCRIPT = ["nuxt build"]; +const DEFAULT_BUILD_SCRIPT = ["nuxt build", "nuxi build"]; /** * * @param dir current directory - * @return undefined if project is not Nuxt 2, {mayWantBackend: true } otherwise + * @return undefined if project is not Nuxt 2, { mayWantBackend: true, publicDirectory: string } otherwise */ -export async function discover(dir: string): Promise<{ mayWantBackend: true } | undefined> { +export async function discover( + dir: string +): Promise<{ mayWantBackend: true; publicDirectory: string } | undefined> { if (!(await pathExists(join(dir, "package.json")))) return; - const nuxtDependency = findDependency("nuxt", { - cwd: dir, - depth: 0, - omitDev: false, - }) as NuxtDependency; - const version = nuxtDependency?.version; const anyConfigFileExists = await nuxtConfigFilesExist(dir); - if (!anyConfigFileExists && !nuxtDependency) return; - if (version && gte(version, "3.0.0-0")) return { mayWantBackend: true }; + const nuxtVersion = getNuxtVersion(dir); + if (!anyConfigFileExists && !nuxtVersion) return; + if (nuxtVersion && lt(nuxtVersion, "3.0.0-0")) return; - return; + const { + dir: { public: publicDirectory }, + } = await getConfig(dir); + + return { publicDirectory, mayWantBackend: true }; } export async function build(root: string) { @@ -75,3 +78,28 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: st await copy(join(sourceDir, ".output", "server"), destDir); return { packageJson: { ...packageJson, ...outputPackageJson }, frameworksEntry: "nuxt3" }; } + +export async function getDevModeHandle(cwd: string) { + const host = new Promise((resolve) => { + const cli = getNodeModuleBin("nuxt", cwd); + const serve = spawn(cli, ["dev"], { cwd: cwd }); + + serve.stdout.on("data", (data: any) => { + process.stdout.write(data); + const match = data.toString().match(/(http:\/\/.+:\d+)/); + + if (match) resolve(match[1]); + }); + + serve.stderr.on("data", (data: any) => { + process.stderr.write(data); + }); + }); + + return simpleProxy(await host); +} + +export async function getConfig(dir: string): Promise { + const { loadNuxtConfig } = await relativeRequire(dir, "@nuxt/kit"); + return await loadNuxtConfig(dir); +} diff --git a/src/frameworks/nuxt/interfaces.ts b/src/frameworks/nuxt/interfaces.ts index 6dcd0ea6844..5e78aef2803 100644 --- a/src/frameworks/nuxt/interfaces.ts +++ b/src/frameworks/nuxt/interfaces.ts @@ -1,5 +1,8 @@ -export interface NuxtDependency { - version?: string; - resolved?: string; - overridden?: boolean; +// TODO: define more fields as needed +// The NuxtOptions interface is huge and depends on multiple external types +// and packages. For now only the fields that are being used are defined. +export interface NuxtOptions { + dir: { + public: string; + }; } diff --git a/src/frameworks/nuxt/utils.ts b/src/frameworks/nuxt/utils.ts index b9ce20da85c..87ced391a2a 100644 --- a/src/frameworks/nuxt/utils.ts +++ b/src/frameworks/nuxt/utils.ts @@ -1,5 +1,14 @@ import { pathExists } from "fs-extra"; import { join } from "path"; +import { findDependency } from ".."; + +export function getNuxtVersion(cwd: string): string | undefined { + return findDependency("nuxt", { + cwd, + depth: 0, + omitDev: false, + })?.version; +} /** * diff --git a/src/frameworks/nuxt2/index.ts b/src/frameworks/nuxt2/index.ts index 9392a3e17b4..0919d03ad7d 100644 --- a/src/frameworks/nuxt2/index.ts +++ b/src/frameworks/nuxt2/index.ts @@ -2,10 +2,9 @@ import { copy, pathExists } from "fs-extra"; import { readFile } from "fs/promises"; import { join } from "path"; import { lt } from "semver"; -import { findDependency, FrameworkType, relativeRequire, SupportLevel } from ".."; +import { FrameworkType, relativeRequire, SupportLevel } from ".."; -import { NuxtDependency } from "../nuxt/interfaces"; -import { nuxtConfigFilesExist } from "../nuxt/utils"; +import { nuxtConfigFilesExist, getNuxtVersion } from "../nuxt/utils"; export const name = "Nuxt"; export const support = SupportLevel.Experimental; @@ -18,19 +17,12 @@ export const type = FrameworkType.MetaFramework; */ export async function discover(dir: string): Promise<{ mayWantBackend: true } | undefined> { if (!(await pathExists(join(dir, "package.json")))) return; - const nuxtDependency = findDependency("nuxt", { - cwd: dir, - depth: 0, - omitDev: false, - }); - const version = nuxtDependency?.version; + const nuxtVersion = getNuxtVersion(dir); const anyConfigFileExists = await nuxtConfigFilesExist(dir); - if (!anyConfigFileExists && !nuxtDependency) return; - if (version && lt(version, "3.0.0-0")) return { mayWantBackend: true }; - - return; + if (!anyConfigFileExists && !nuxtVersion) return; + if (nuxtVersion && lt(nuxtVersion, "3.0.0-0")) return { mayWantBackend: true }; } /** diff --git a/src/test/frameworks/nuxt/index.spec.ts b/src/test/frameworks/nuxt/index.spec.ts new file mode 100644 index 00000000000..16fabd35281 --- /dev/null +++ b/src/test/frameworks/nuxt/index.spec.ts @@ -0,0 +1,107 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import { EventEmitter } from "events"; +import type { ChildProcess } from "child_process"; +import { Readable, Writable } from "stream"; +import * as fsExtra from "fs-extra"; +import * as crossSpawn from "cross-spawn"; + +import * as frameworksFunctions from "../../../frameworks"; +import { discover as discoverNuxt2 } from "../../../frameworks/nuxt2"; +import { discover as discoverNuxt3, getDevModeHandle } from "../../../frameworks/nuxt"; +import type { NuxtOptions } from "../../../frameworks/nuxt/interfaces"; + +describe("Nuxt 2 utils", () => { + describe("nuxtAppDiscovery", () => { + const discoverNuxtDir = "."; + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should find a Nuxt 2 app", async () => { + sandbox.stub(fsExtra, "pathExists").resolves(true); + sandbox.stub(frameworksFunctions, "findDependency").returns({ + version: "2.15.8", + resolved: "https://registry.npmjs.org/nuxt/-/nuxt-2.15.8.tgz", + overridden: false, + }); + + expect(await discoverNuxt2(discoverNuxtDir)).to.deep.equal({ + mayWantBackend: true, + }); + }); + + it("should find a Nuxt 3 app", async () => { + sandbox.stub(fsExtra, "pathExists").resolves(true); + sandbox.stub(frameworksFunctions, "findDependency").returns({ + version: "3.0.0", + resolved: "https://registry.npmjs.org/nuxt/-/nuxt-3.0.0.tgz", + overridden: false, + }); + sandbox + .stub(frameworksFunctions, "relativeRequire") + .withArgs(discoverNuxtDir, "@nuxt/kit") + .resolves({ + loadNuxtConfig: async function (): Promise { + return Promise.resolve({ + dir: { + public: "public", + }, + }); + }, + }); + + expect(await discoverNuxt3(discoverNuxtDir)).to.deep.equal({ + mayWantBackend: true, + publicDirectory: "public", + }); + }); + }); + + describe("getDevModeHandle", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should resolve with initial Nuxt 3 dev server output", async () => { + const process = new EventEmitter() as ChildProcess; + process.stdin = new Writable(); + process.stdout = new EventEmitter() as Readable; + process.stderr = new EventEmitter() as Readable; + + const cli = Math.random().toString(36).split(".")[1]; + sandbox.stub(frameworksFunctions, "getNodeModuleBin").withArgs("nuxt", ".").returns(cli); + sandbox.stub(crossSpawn, "spawn").withArgs(cli, ["dev"], { cwd: "." }).returns(process); + + const devModeHandle = getDevModeHandle("."); + + process.stdout.emit( + "data", + `Nuxi 3.0.0 + + WARN Changing NODE_ENV from production to development, to avoid unintended behavior. + + Nuxt 3.0.0 with Nitro 1.0.0 + + > Local: http://localhost:3000/ + > Network: http://0.0.0.0:3000/ + > Network: http://[some:ipv6::::::]:3000/ + > Network: http://[some:other:ipv6:::::]:3000/` + ); + + await expect(devModeHandle).eventually.be.fulfilled; + }); + }); +}); diff --git a/src/test/frameworks/nuxt/utils.spec.ts b/src/test/frameworks/nuxt/utils.spec.ts deleted file mode 100644 index 8339fdd0620..00000000000 --- a/src/test/frameworks/nuxt/utils.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { expect } from "chai"; -// import * as fs from "fs"; -import * as fsExtra from "fs-extra"; -import * as sinon from "sinon"; -import * as frameworksFunctions from "../../../frameworks"; - -import { discover as discoverNuxt2 } from "../../../frameworks/nuxt2"; -import { discover as discoverNuxt3 } from "../../../frameworks/nuxt"; - -describe("Nuxt 2 utils", () => { - describe("nuxtAppDiscovery", () => { - let sandbox: sinon.SinonSandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it("should find a Nuxt 2 app", async () => { - sandbox.stub(fsExtra, "pathExists").resolves(true); - sandbox.stub(frameworksFunctions, "findDependency").returns({ - version: "2.15.8", - resolved: "https://registry.npmjs.org/nuxt/-/nuxt-2.15.8.tgz", - overridden: false, - }); - - expect(await discoverNuxt2(".")).to.deep.equal({ mayWantBackend: true }); - }); - - it("should find a Nuxt 3 app", async () => { - sandbox.stub(fsExtra, "pathExists").resolves(true); - sandbox.stub(frameworksFunctions, "findDependency").returns({ - version: "3.0.0", - resolved: "https://registry.npmjs.org/nuxt/-/nuxt-3.0.0.tgz", - overridden: false, - }); - - expect(await discoverNuxt3(".")).to.deep.equal({ mayWantBackend: true }); - }); - }); -}); From 8370680e9b796c0804b132579ec31f1711045c1c Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 12 Apr 2023 18:03:15 -0700 Subject: [PATCH 0871/1699] Prevent GCF from automatically running the build script. (#5687) GCF will now run `npm run build` script as part of function deploys: https://cloud.google.com/functions/docs/release-notes#April_11_2023 This is a breaking change. We want to shield Cloud Functions for Firebase user from the breaking change and manually disable the behavior in CF3 deploys. We will enable the GCF feature in the next major CLI release. --- CHANGELOG.md | 1 + src/gcp/cloudfunctions.ts | 18 ++++++++++++++++-- src/gcp/cloudfunctionsv2.ts | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46597d9421d..1f647e6b97a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Default emulators:start to use fast dev-mode for Nuxt3 applications (#5551) +- Disable GCF breaking change to automatically run npm build scripts as part of function deploy (#5687) diff --git a/src/gcp/cloudfunctions.ts b/src/gcp/cloudfunctions.ts index af2a1de2985..ff743a0608b 100644 --- a/src/gcp/cloudfunctions.ts +++ b/src/gcp/cloudfunctions.ts @@ -205,6 +205,12 @@ export async function createFunction( // the API is a POST to the collection that owns the function name. const apiPath = cloudFunction.name.substring(0, cloudFunction.name.lastIndexOf("/")); const endpoint = `/${apiPath}`; + cloudFunction.buildEnvironmentVariables = { + ...cloudFunction.buildEnvironmentVariables, + // Disable GCF from automatically running npm run build script + // https://cloud.google.com/functions/docs/release-notes + GOOGLE_NODE_RUN_SCRIPTS: "", + }; try { const res = await client.post, CloudFunction>( @@ -352,8 +358,8 @@ export async function updateFunction( cloudFunction: Omit ): Promise { const endpoint = `/${cloudFunction.name}`; - // Keys in labels and environmentVariables and secretEnvironmentVariables are user defined, so we don't recurse - // for field masks. + // Keys in labels and environmentVariables and secretEnvironmentVariables are user defined, + // so we don't recurse for field masks. const fieldMasks = proto.fieldMasks( cloudFunction, /* doNotRecurseIn...=*/ "labels", @@ -361,6 +367,14 @@ export async function updateFunction( "secretEnvironmentVariables" ); + cloudFunction.buildEnvironmentVariables = { + ...cloudFunction.buildEnvironmentVariables, + // Disable GCF from automatically running npm run build script + // https://cloud.google.com/functions/docs/release-notes + GOOGLE_NODE_RUN_SCRIPTS: "", + }; + fieldMasks.push("buildEnvironmentVariables"); + // Failure policy is always an explicit policy and is only signified by the presence or absence of // a protobuf.Empty value, so we have to manually add it in the missing case. try { diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index 7884cf520ac..d419b3d0eef 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -299,6 +299,13 @@ export async function createFunction( const components = cloudFunction.name.split("/"); const functionId = components.splice(-1, 1)[0]; + cloudFunction.buildConfig.environmentVariables = { + ...cloudFunction.buildConfig.environmentVariables, + // Disable GCF from automatically running npm run build script + // https://cloud.google.com/functions/docs/release-notes + GOOGLE_NODE_RUN_SCRIPTS: "", + }; + try { const res = await client.post( components.join("/"), @@ -390,6 +397,14 @@ export async function updateFunction( "serviceConfig.environmentVariables", "serviceConfig.secretEnvironmentVariables" ); + + cloudFunction.buildConfig.environmentVariables = { + ...cloudFunction.buildConfig.environmentVariables, + // Disable GCF from automatically running npm run build script + // https://cloud.google.com/functions/docs/release-notes + GOOGLE_NODE_RUN_SCRIPTS: "", + }; + fieldMasks.push("buildConfig.buildEnvironmentVariables"); try { const queryParams = { updateMask: fieldMasks.join(","), From cc4d85c8dcdfa3949cebb5a2c13bf5eb31a65638 Mon Sep 17 00:00:00 2001 From: Austin Crim Date: Thu, 13 Apr 2023 00:16:10 -0500 Subject: [PATCH 0872/1699] Add Astro web framework support (#5527) Co-authored-by: James Daniels --- CHANGELOG.md | 1 + npm-shrinkwrap.json | 12141 ++++++++++++++++------ package.json | 4 +- src/frameworks/astro/index.ts | 81 + src/frameworks/astro/utils.ts | 24 + src/test/frameworks/astro/index.spec.ts | 351 + 6 files changed, 9431 insertions(+), 3171 deletions(-) create mode 100644 src/frameworks/astro/index.ts create mode 100644 src/frameworks/astro/utils.ts create mode 100644 src/test/frameworks/astro/index.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f647e6b97a..2098d17044f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Default emulators:start to use fast dev-mode for Nuxt3 applications (#5551) - Disable GCF breaking change to automatically run npm build scripts as part of function deploy (#5687) +- Add experimental support for deploying Astro applications to Hosting (#5527) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 63b028bb71a..5d0420a0236 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -92,6 +92,7 @@ "@types/express-serve-static-core": "^4.17.8", "@types/fs-extra": "^9.0.13", "@types/glob": "^7.1.1", + "@types/html-escaper": "^3.0.0", "@types/inquirer": "^8.1.3", "@types/js-yaml": "^3.12.2", "@types/jsonwebtoken": "^8.3.8", @@ -128,6 +129,7 @@ "@types/ws": "^7.2.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", + "astro": "^2.2.3", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "eslint": "^8.6.0", @@ -158,7 +160,7 @@ "ts-node": "^10.4.0", "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1", - "vite": "^3.1.0" + "vite": "^4.2.1" }, "engines": { "node": "^14.18.0 || >=16.4.0" @@ -296,6 +298,191 @@ "js-yaml": "^3.13.1" } }, + "node_modules/@astrojs/compiler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-1.3.1.tgz", + "integrity": "sha512-xV/3r+Hrfpr4ECfJjRjeaMkJvU73KiOADowHjhkqidfNPVAWPzbqw1KePXuMK1TjzMvoAVE7E163oqfH3lDwSw==", + "dev": true + }, + "node_modules/@astrojs/language-server": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-0.28.3.tgz", + "integrity": "sha512-fPovAX/X46eE2w03jNRMpQ7W9m2mAvNt4Ay65lD9wl1Z5vIQYxlg7Enp9qP225muTr4jSVB5QiLumFJmZMAaVA==", + "dev": true, + "dependencies": { + "@vscode/emmet-helper": "^2.8.4", + "events": "^3.3.0", + "prettier": "^2.7.1", + "prettier-plugin-astro": "^0.7.0", + "source-map": "^0.7.3", + "vscode-css-languageservice": "^6.0.1", + "vscode-html-languageservice": "^5.0.0", + "vscode-languageserver": "^8.0.1", + "vscode-languageserver-protocol": "^3.17.1", + "vscode-languageserver-textdocument": "^1.0.4", + "vscode-languageserver-types": "^3.17.1", + "vscode-uri": "^3.0.3" + }, + "bin": { + "astro-ls": "bin/nodeServer.js" + } + }, + "node_modules/@astrojs/language-server/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-2.1.3.tgz", + "integrity": "sha512-Di8Qbit9p7L7eqKklAJmiW9nVD+XMsNHpaNzCLduWjOonDu9fVgEzdjeDrTVCDtgrvkfhpAekuNXrp5+w4F91g==", + "dev": true, + "dependencies": { + "@astrojs/prism": "^2.1.0", + "github-slugger": "^1.4.0", + "import-meta-resolve": "^2.1.0", + "rehype-raw": "^6.1.1", + "rehype-stringify": "^9.0.3", + "remark-gfm": "^3.0.1", + "remark-parse": "^10.0.1", + "remark-rehype": "^10.1.0", + "remark-smartypants": "^2.0.0", + "shiki": "^0.11.1", + "unified": "^10.1.2", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.2" + }, + "peerDependencies": { + "astro": "^2.2.0" + } + }, + "node_modules/@astrojs/markdown-remark/node_modules/github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "dev": true + }, + "node_modules/@astrojs/prism": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-2.1.1.tgz", + "integrity": "sha512-Gnwnlb1lGJzCQEg89r4/WqgfCGPNFC7Kuh2D/k289Cbdi/2PD7Lrdstz86y1itDvcb2ijiRqjqWnJ5rsfu/QOA==", + "dev": true, + "dependencies": { + "prismjs": "^1.28.0" + }, + "engines": { + "node": ">=16.12.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-2.1.0.tgz", + "integrity": "sha512-P3gXNNOkRJM8zpnasNoi5kXp3LnFt0smlOSUXhkynfJpTJMIDrcMbKpNORN0OYbqpKt9JPdgRN7nsnGWpbH1ww==", + "dev": true, + "dependencies": { + "ci-info": "^3.3.1", + "debug": "^4.3.4", + "dlv": "^1.1.3", + "dset": "^3.1.2", + "is-docker": "^3.0.0", + "is-wsl": "^2.2.0", + "undici": "^5.20.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": ">=16.12.0" + } + }, + "node_modules/@astrojs/telemetry/node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/@astrojs/telemetry/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@astrojs/telemetry/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@astrojs/telemetry/node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@astrojs/telemetry/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@astrojs/webapi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/webapi/-/webapi-2.1.0.tgz", + "integrity": "sha512-sbF44s/uU33jAdefzKzXZaENPeXR0sR3ptLs+1xp9xf5zIBhedH2AfaFB5qTEv9q5udUVoKxubZGT3G1nWs6rA==", + "dev": true, + "dependencies": { + "undici": "5.20.0" + } + }, + "node_modules/@astrojs/webapi/node_modules/undici": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.20.0.tgz", + "integrity": "sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==", + "dev": true, + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=12.18" + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -386,6 +573,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", @@ -478,6 +677,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-simple-access": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", @@ -503,18 +711,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -558,9 +766,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -568,6 +776,40 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz", + "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/template": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", @@ -627,13 +869,13 @@ "dev": true }, "node_modules/@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" }, "engines": { @@ -671,6 +913,30 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emmetio/abbreviation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.1.tgz", + "integrity": "sha512-QXgYlXZGprqb6aCBJPPWVBN/Jb69khJF73GGJkOk//PoMgSbPGuaHn1hCRolctnzlBHjCIC6Om97Pw46/1A23g==", + "dev": true, + "dependencies": { + "@emmetio/scanner": "^1.0.2" + } + }, + "node_modules/@emmetio/css-abbreviation": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.6.tgz", + "integrity": "sha512-bvuPogt0OvwcILRg+ZD/oej1H72xwOhUDPWOmhCWLJrZZ8bMTazsWnvw8a8noaaVqUhOE9PsC0tYgGVv5N7fsw==", + "dev": true, + "dependencies": { + "@emmetio/scanner": "^1.0.2" + } + }, + "node_modules/@emmetio/scanner": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.2.tgz", + "integrity": "sha512-1ESCGgXRgn1r29hRmz8K0G4Ywr5jDWezMgRnICComBCWmg3znLWU8+tmakuM1og1Vn4W/sauvlABl/oq2pve8w==", + "dev": true + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.36.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", @@ -686,9 +952,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.16.tgz", + "integrity": "sha512-baLqRpLe4JnKrUXLJChoTN0iXZH7El/mu58GE3WIA6/H834k0XWvLRmGLG8y8arTRS9hJJibPnF0tiGhmWeZgw==", "cpu": [ "arm" ], @@ -701,103 +967,423 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "node_modules/@esbuild/android-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.16.tgz", + "integrity": "sha512-QX48qmsEZW+gcHgTmAj+x21mwTz8MlYQBnzF6861cNdQGvj2jzzFjqH0EBabrIa/WVZ2CHolwMoqxVryqKt8+Q==", "cpu": [ - "loong64" + "arm64" ], "dev": true, "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">=12" } }, - "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "node_modules/@esbuild/android-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.16.tgz", + "integrity": "sha512-G4wfHhrrz99XJgHnzFvB4UwwPxAWZaZBOFXh+JH1Duf1I4vIVfuYY9uVLpx4eiV2D/Jix8LJY+TAdZ3i40tDow==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.16.tgz", + "integrity": "sha512-/Ofw8UXZxuzTLsNFmz1+lmarQI6ztMZ9XktvXedTbt3SNWDn0+ODTwxExLYQ/Hod91EZB4vZPQJLoqLF0jvEzA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "ms": "2.1.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.16.tgz", + "integrity": "sha512-SzBQtCV3Pdc9kyizh36Ol+dNVhkDyIrGb/JXZqFq8WL37LIyrXU0gUpADcNV311sCOhvY+f2ivMhb5Tuv8nMOQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.16.tgz", + "integrity": "sha512-ZqftdfS1UlLiH1DnS2u3It7l4Bc3AskKeu+paJSfk7RNOMrOxmeFDhLTMQqMxycP1C3oj8vgkAT6xfAuq7ZPRA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { - "version": "3.1.1", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.16.tgz", + "integrity": "sha512-rHV6zNWW1tjgsu0dKQTX9L0ByiJHHLvQKrWtnz8r0YYJI27FU3Xu48gpK2IBj1uCSYhJ+pEk6Y0Um7U3rIvV8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.16.tgz", + "integrity": "sha512-n4O8oVxbn7nl4+m+ISb0a68/lcJClIbaGAoXwqeubj/D1/oMMuaAXmJVfFlRjJLu/ZvHkxoiFJnmbfp4n8cdSw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.16.tgz", + "integrity": "sha512-8yoZhGkU6aHu38WpaM4HrRLTFc7/VVD9Q2SvPcmIQIipQt2I/GMTZNdEHXoypbbGao5kggLcxg0iBKjo0SQYKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.16.tgz", + "integrity": "sha512-9ZBjlkdaVYxPNO8a7OmzDbOH9FMQ1a58j7Xb21UfRU29KcEEU3VTHk+Cvrft/BNv0gpWJMiiZ/f4w0TqSP0gLA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.16.tgz", + "integrity": "sha512-TIZTRojVBBzdgChY3UOG7BlPhqJz08AL7jdgeeu+kiObWMFzGnQD7BgBBkWRwOtKR1i2TNlO7YK6m4zxVjjPRQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.16.tgz", + "integrity": "sha512-UPeRuFKCCJYpBbIdczKyHLAIU31GEm0dZl1eMrdYeXDH+SJZh/i+2cAmD3A1Wip9pIc5Sc6Kc5cFUrPXtR0XHA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.16.tgz", + "integrity": "sha512-io6yShgIEgVUhExJejJ21xvO5QtrbiSeI7vYUnr7l+v/O9t6IowyhdiYnyivX2X5ysOVHAuyHW+Wyi7DNhdw6Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.16.tgz", + "integrity": "sha512-WhlGeAHNbSdG/I2gqX2RK2gfgSNwyJuCiFHMc8s3GNEMMHUI109+VMBfhVqRb0ZGzEeRiibi8dItR3ws3Lk+cA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.16.tgz", + "integrity": "sha512-gHRReYsJtViir63bXKoFaQ4pgTyah4ruiMRQ6im9YZuv+gp3UFJkNTY4sFA73YDynmXZA6hi45en4BGhNOJUsw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.16.tgz", + "integrity": "sha512-mfiiBkxEbUHvi+v0P+TS7UnA9TeGXR48aK4XHkTj0ZwOijxexgMF01UDFaBX7Q6CQsB0d+MFNv9IiXbIHTNd4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.16.tgz", + "integrity": "sha512-n8zK1YRDGLRZfVcswcDMDM0j2xKYLNXqei217a4GyBxHIuPMGrrVuJ+Ijfpr0Kufcm7C1k/qaIrGy6eG7wvgmA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.16.tgz", + "integrity": "sha512-lEEfkfsUbo0xC47eSTBqsItXDSzwzwhKUSsVaVjVji07t8+6KA5INp2rN890dHZeueXJAI8q0tEIfbwVRYf6Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.16.tgz", + "integrity": "sha512-jlRjsuvG1fgGwnE8Afs7xYDnGz0dBgTNZfgCK6TlvPH3Z13/P5pi6I57vyLE8qZYLrGVtwcm9UbUx1/mZ8Ukag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.16.tgz", + "integrity": "sha512-TzoU2qwVe2boOHl/3KNBUv2PNUc38U0TNnzqOAcgPiD/EZxT2s736xfC2dYQbszAwo4MKzzwBV0iHjhfjxMimg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.16.tgz", + "integrity": "sha512-B8b7W+oo2yb/3xmwk9Vc99hC9bNolvqjaTZYEfMQhzdpBsjTvZBlXQ/teUE55Ww6sg//wlcDjOaqldOKyigWdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.16.tgz", + "integrity": "sha512-xJ7OH/nanouJO9pf03YsL9NAFQBHd8AqfrQd7Pf5laGyyTt/gToul6QYOA/i5i/q8y9iaM5DQFNTgpi995VkOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@eslint/eslintrc/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, @@ -2209,6 +2795,12 @@ "node": ">=v12.0.0" } }, + "node_modules/@ljharb/has-package-exports-patterns": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@ljharb/has-package-exports-patterns/-/has-package-exports-patterns-0.0.2.tgz", + "integrity": "sha512-4/RWEeXDO6bocPONheFe6gX/oQdP/bEpv0oL4HqjPP5DCenBSt0mHgahppY49N0CpsaqffdwPq+TlX9CYOq2Dw==", + "dev": true + }, "node_modules/@next/env": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.2.tgz", @@ -2527,6 +3119,76 @@ "node": ">=8.12.0" } }, + "node_modules/@pkgr/utils": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", + "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "is-glob": "^4.0.3", + "open": "^8.4.0", + "picocolors": "^1.0.0", + "tiny-glob": "^0.2.9", + "tslib": "^2.4.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pkgr/utils/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pkgr/utils/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@pkgr/utils/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pkgr/utils/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + }, "node_modules/@pnpm/network.ca-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", @@ -2752,6 +3414,47 @@ "integrity": "sha512-Z93wDSYW9aMgPR5t+7ouwTvy91Vp3M0Snh4Pd3tf+caSAq5bXZaGnnH9CDbjrwgmfDkRIX0Dx8GvSDgwuoaxoA==", "dev": true }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": 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.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, "node_modules/@types/body-parser": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", @@ -2837,6 +3540,15 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", @@ -2845,6 +3557,12 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, "node_modules/@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -2894,6 +3612,21 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/html-escaper": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/html-escaper/-/html-escaper-3.0.0.tgz", + "integrity": "sha512-OcJcvP3Yk8mjYwf/IdXZtTE1tb/u0WF0qa29ER07ZHCYUBZXSN29Z1mBS+/96+kNMGTFUAbSz9X+pHmHpZrTCw==", + "dev": true + }, "node_modules/@types/inquirer": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.5.tgz", @@ -2915,6 +3648,12 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.30.tgz", + "integrity": "sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==", + "dev": true + }, "node_modules/@types/jsonwebtoken": { "version": "8.3.8", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.3.8.tgz", @@ -2977,6 +3716,15 @@ "integrity": "sha512-ZgAr847Wl68W+B0sWH7F4fDPxTzerLnRuUXjUpp1n4NjGSs8hgPAjAp7NQIXblG34MXTrf5wWkAK8PVJ2LIlVg==", "dev": true }, + "node_modules/@types/mdast": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz", + "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mdurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", @@ -3015,6 +3763,12 @@ "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, "node_modules/@types/multer": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.4.tgz", @@ -3024,6 +3778,15 @@ "@types/express": "*" } }, + "node_modules/@types/nlcst": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-1.0.0.tgz", + "integrity": "sha512-3TGCfOcy8R8mMQ4CNSNOe3PG66HttvjcLzCoOpvXvDtfWOTi+uT/rxeOKm/qEwbM4SNe1O/PjdiBK2YcTjU4OQ==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/node": { "version": "14.18.36", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz", @@ -3065,6 +3828,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, "node_modules/@types/prettier": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", @@ -3153,6 +3922,12 @@ "node": ">= 0.12" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "node_modules/@types/retry": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", @@ -3307,6 +4082,12 @@ "integrity": "sha512-tl34wMtk3q+fSdRSJ+N83f47IyXLXPPuLjHm7cmAx0fE2Wml2TZCQV3FmQdSR5J6UEGV3qafG054e0cVVFCqPA==", "dev": true }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, "node_modules/@types/universal-analytics": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/@types/universal-analytics/-/universal-analytics-0.4.5.tgz", @@ -3347,6 +4128,12 @@ "@types/node": "*" } }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, "node_modules/@types/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", @@ -3357,20 +4144,219 @@ "@types/node": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin": { + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", + "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/type-utils": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", + "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", + "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", + "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/types": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", + "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", - "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", + "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/type-utils": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0", "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", + "globby": "^11.1.0", + "is-glob": "^4.0.3", "semver": "^7.3.7", "tsutils": "^3.21.0" }, @@ -3381,17 +4367,13 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", @@ -3408,13 +4390,13 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", @@ -3429,16 +4411,61 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/parser": { + "node_modules/@typescript-eslint/utils": { "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", - "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", + "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", "dev": true, "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", "@typescript-eslint/scope-manager": "5.51.0", "@typescript-eslint/types": "5.51.0", "@typescript-eslint/typescript-estree": "5.51.0", - "debug": "^4.3.4" + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", + "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.51.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3446,728 +4473,1052 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/@vscode/emmet-helper": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.8.6.tgz", + "integrity": "sha512-IIB8jbiKy37zN8bAIHx59YmnIelY78CGHtThnibD/d3tQOKRY83bYVi9blwmZVUZh6l9nfkYH3tvReaiNxY9EQ==", + "dev": true, + "dependencies": { + "emmet": "^2.3.0", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^2.1.2" + } + }, + "node_modules/@vscode/emmet-helper/node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true + }, + "node_modules/@vscode/emmet-helper/node_modules/vscode-uri": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==", + "dev": true + }, + "node_modules/@vscode/l10n": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.11.tgz", + "integrity": "sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "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==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "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==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "optional": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/agentkeepalive/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agentkeepalive/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "devOptional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "ajv": "^8.0.0" }, "peerDependenciesMeta": { - "typescript": { + "ajv": { "optional": true } } }, - "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@typescript-eslint/parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", - "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", "dependencies": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0" + "type-fest": "^1.0.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", - "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.51.0", - "@typescript-eslint/utils": "5.51.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { - "ms": "2.1.2" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=4" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, - "node_modules/@typescript-eslint/types": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", - "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": ">= 8" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", - "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "default-require-extensions": "^3.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, + "node_modules/archiver": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", + "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.3", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "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" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">= 6" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dependencies": { - "ms": "2.1.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "sprintf-js": "~1.0.2" } }, - "node_modules/@typescript-eslint/utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", - "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "node_modules/args": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", + "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0", - "semver": "^7.3.7" + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node": ">= 6.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "node_modules/args/node_modules/camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", - "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "node_modules/args/node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.51.0", - "eslint-visitor-keys": "^3.3.0" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=0.10.0" } }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "node_modules/abbrev": { + "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "optional": true + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "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==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "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==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "bin": { - "acorn": "bin/acorn" - }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node_modules/as-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/as-array/-/as-array-2.0.0.tgz", + "integrity": "sha1-TwSAXYf4/OjlEbwhCPjl46KH1Uc=" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "engines": { - "node": ">=0.4.0" + "node": ">=0.8" } }, - "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==", - "dependencies": { - "debug": "4" - }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, "engines": { - "node": ">= 6.0.0" + "node": "*" } }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dependencies": { - "ms": "2.1.2" + "tslib": "^2.0.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=4" } }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "node_modules/ast-types/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, - "node_modules/agentkeepalive": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", - "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", - "optional": true, - "dependencies": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" + "node_modules/astro": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/astro/-/astro-2.2.3.tgz", + "integrity": "sha512-Pd67ZBoYxqeyHCZ0UpdmDZYNgcs7JTwc0NMzUScrH4y2hjSY4S8iwmNUtd9pf65gkxMpEbqfvQj06kLzgi4HZg==", + "dev": true, + "dependencies": { + "@astrojs/compiler": "^1.3.1", + "@astrojs/language-server": "^0.28.3", + "@astrojs/markdown-remark": "^2.1.3", + "@astrojs/telemetry": "^2.1.0", + "@astrojs/webapi": "^2.1.0", + "@babel/core": "^7.18.2", + "@babel/generator": "^7.18.2", + "@babel/parser": "^7.18.4", + "@babel/plugin-transform-react-jsx": "^7.17.12", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.4", + "@types/babel__core": "^7.1.19", + "@types/yargs-parser": "^21.0.0", + "acorn": "^8.8.1", + "boxen": "^6.2.1", + "chokidar": "^3.5.3", + "ci-info": "^3.3.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^0.5.0", + "debug": "^4.3.4", + "deepmerge-ts": "^4.2.2", + "devalue": "^4.2.0", + "diff": "^5.1.0", + "es-module-lexer": "^1.1.0", + "estree-walker": "^3.0.1", + "execa": "^6.1.0", + "fast-glob": "^3.2.11", + "github-slugger": "^2.0.0", + "gray-matter": "^4.0.3", + "html-escaper": "^3.0.3", + "kleur": "^4.1.4", + "magic-string": "^0.27.0", + "mime": "^3.0.0", + "ora": "^6.1.0", + "path-to-regexp": "^6.2.1", + "preferred-pm": "^3.0.3", + "prompts": "^2.4.2", + "rehype": "^12.0.1", + "semver": "^7.3.8", + "server-destroy": "^1.0.1", + "shiki": "^0.11.1", + "slash": "^4.0.0", + "string-width": "^5.1.2", + "strip-ansi": "^7.0.1", + "supports-esm": "^1.0.0", + "tsconfig-resolver": "^3.0.1", + "typescript": "*", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.2", + "vite": "^4.2.1", + "vitefu": "^0.2.4", + "yargs-parser": "^21.0.1", + "zod": "^3.17.3" }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/agentkeepalive/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "optional": true, - "dependencies": { - "ms": "2.1.2" + "bin": { + "astro": "astro.js" }, "engines": { - "node": ">=6.0" + "node": ">=16.12.0", + "npm": ">=6.14.0" + }, + "peerDependencies": { + "sharp": "^0.31.3" }, "peerDependenciesMeta": { - "supports-color": { + "sharp": { "optional": true } } }, - "node_modules/agentkeepalive/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "optional": true - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "devOptional": true, + "node_modules/astro/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==", + "dev": true, "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "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" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/astro/node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "dev": true, "dependencies": { - "ajv": "^8.0.0" + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" }, - "peerDependencies": { - "ajv": "^8.0.0" + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "node_modules/astro/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "node_modules/astro/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, "dependencies": { - "string-width": "^4.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/astro/node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", - "dependencies": { - "type-fest": "^1.0.2" - }, + "node_modules/astro/node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/astro/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/astro/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==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=4" + "node": ">=7.0.0" } }, - "node_modules/ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" + "node_modules/astro/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==", + "dev": true }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "node_modules/astro/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "ms": "2.1.2" }, "engines": { - "node": ">= 8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "node_modules/astro/node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/astro/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/astro/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, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "optional": true + "node_modules/astro/node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "dev": true }, - "node_modules/archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.3", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" + "node_modules/astro/node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/astro/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, "engines": { - "node": ">= 10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "node_modules/astro/node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, "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" + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" }, "engines": { - "node": ">= 6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "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" + "node_modules/astro/node_modules/log-symbols/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/astro/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" } }, - "node_modules/archiver/node_modules/async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "node_modules/astro/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "optional": true, + "node_modules/astro/node_modules/ora": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.0.tgz", + "integrity": "sha512-1/D8uRFY0ay2kgBpmAwmSA404w4OoPVhHMqRqtjvrcK/dnzcEZxMJ+V4DUbyICu8IIVRclHcOf5wlD1tMY4GUQ==", + "dev": true, "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "node_modules/astro/node_modules/ora/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/astro/node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", "dev": true }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/astro/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, "dependencies": { - "sprintf-js": "~1.0.2" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/args": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", - "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "node_modules/astro/node_modules/semver": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", + "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", "dev": true, "dependencies": { - "camelcase": "5.0.0", - "chalk": "2.4.2", - "leven": "2.1.0", - "mri": "1.1.4" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">= 6.0.0" + "node": ">=10" } }, - "node_modules/args/node_modules/camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "node_modules/astro/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/args/node_modules/leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "node_modules/astro/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/astro/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "node_modules/astro/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, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/as-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/as-array/-/as-array-2.0.0.tgz", - "integrity": "sha1-TwSAXYf4/OjlEbwhCPjl46KH1Uc=" - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "node_modules/astro/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "node_modules/astro/node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, "dependencies": { - "safer-buffer": "~2.1.0" + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "node_modules/astro/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=0.8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "node_modules/astro/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "engines": { - "node": "*" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dependencies": { - "tslib": "^2.0.1" - }, + "node_modules/astro/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==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/ast-types/node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, "node_modules/async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -4228,6 +5579,16 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -4577,6 +5938,18 @@ "node": ">=0.2.0" } }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4810,6 +6183,16 @@ "node": ">= 10" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", @@ -4864,6 +6247,36 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -5103,6 +6516,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.0.1.tgz", @@ -5120,6 +6543,12 @@ "node": ">= 12.0.0" } }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "dev": true + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -5553,6 +6982,19 @@ "node": ">=0.10.0" } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dev": true, + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -5583,6 +7025,15 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "node_modules/deepmerge-ts": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-4.3.0.tgz", + "integrity": "sha512-if3ZYdkD2dClhnXR5reKtG98cwyaRT1NeugQoAPTTfsOpV9kqyeiBF9Qa5RHjemb3KzD5ulqygv6ED3t5j9eJw==", + "dev": true, + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", @@ -5603,6 +7054,15 @@ "clone": "^1.0.2" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/degenerator": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz", @@ -5640,6 +7100,15 @@ "node": ">= 0.6" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5649,6 +7118,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/devalue": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.0.tgz", + "integrity": "sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA==", + "dev": true + }, "node_modules/devtools-protocol": { "version": "0.0.1056733", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1056733.tgz", @@ -5696,6 +7171,12 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5708,6 +7189,15 @@ "node": ">=6.0.0" } }, + "node_modules/dset": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", + "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -5741,6 +7231,12 @@ "stream-shift": "^1.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -5758,478 +7254,174 @@ "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": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.256", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", - "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", - "dev": true - }, - "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==" - }, - "node_modules/enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "dependencies": { - "env-variable": "0.0.x" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true, - "optional": true - }, - "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/env-variable": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", - "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "optional": true - }, - "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, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", - "dev": true - }, - "node_modules/esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.15.18", - "@esbuild/linux-loong64": "0.15.18", - "esbuild-android-64": "0.15.18", - "esbuild-android-arm64": "0.15.18", - "esbuild-darwin-64": "0.15.18", - "esbuild-darwin-arm64": "0.15.18", - "esbuild-freebsd-64": "0.15.18", - "esbuild-freebsd-arm64": "0.15.18", - "esbuild-linux-32": "0.15.18", - "esbuild-linux-64": "0.15.18", - "esbuild-linux-arm": "0.15.18", - "esbuild-linux-arm64": "0.15.18", - "esbuild-linux-mips64le": "0.15.18", - "esbuild-linux-ppc64le": "0.15.18", - "esbuild-linux-riscv64": "0.15.18", - "esbuild-linux-s390x": "0.15.18", - "esbuild-netbsd-64": "0.15.18", - "esbuild-openbsd-64": "0.15.18", - "esbuild-sunos-64": "0.15.18", - "esbuild-windows-32": "0.15.18", - "esbuild-windows-64": "0.15.18", - "esbuild-windows-arm64": "0.15.18" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", - "cpu": [ - "mips64el" - ], + "node_modules/electron-to-chromium": { + "version": "1.4.256", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", + "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", + "dev": true + }, + "node_modules/emmet": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.2.tgz", + "integrity": "sha512-YgmsMkhUgzhJMgH5noGudfxqrQn1bapvF0y7C1e7A0jWFImsRrrvVslzyZz0919NED/cjFOpVWx7c973V+2S/w==", "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@emmetio/abbreviation": "^2.3.1", + "@emmetio/css-abbreviation": "^2.1.6" } }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "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==" + }, + "node_modules/enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "dependencies": { + "env-variable": "0.0.x" } }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "cpu": [ - "s390x" - ], - "dev": true, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "iconv-lite": "^0.6.2" } }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" + "node_modules/end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dependencies": { + "once": "^1.4.0" } }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "cpu": [ - "x64" - ], + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" + "optional": true + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/esbuild-windows-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", - "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", - "cpu": [ - "x64" - ], + "node_modules/env-variable": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", + "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, + "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, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "is-arrayish": "^0.2.1" } }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "cpu": [ - "arm64" - ], + "node_modules/es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.16.tgz", + "integrity": "sha512-aeSuUKr9aFVY9Dc8ETVELGgkj4urg5isYx8pLf4wlGgB0vTFjxJQdHnNH6Shmx4vYYrOTLCHtRI5i1XZ9l2Zcg==", "dev": true, - "optional": true, - "os": [ - "win32" - ], + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.16", + "@esbuild/android-arm64": "0.17.16", + "@esbuild/android-x64": "0.17.16", + "@esbuild/darwin-arm64": "0.17.16", + "@esbuild/darwin-x64": "0.17.16", + "@esbuild/freebsd-arm64": "0.17.16", + "@esbuild/freebsd-x64": "0.17.16", + "@esbuild/linux-arm": "0.17.16", + "@esbuild/linux-arm64": "0.17.16", + "@esbuild/linux-ia32": "0.17.16", + "@esbuild/linux-loong64": "0.17.16", + "@esbuild/linux-mips64el": "0.17.16", + "@esbuild/linux-ppc64": "0.17.16", + "@esbuild/linux-riscv64": "0.17.16", + "@esbuild/linux-s390x": "0.17.16", + "@esbuild/linux-x64": "0.17.16", + "@esbuild/netbsd-x64": "0.17.16", + "@esbuild/openbsd-x64": "0.17.16", + "@esbuild/sunos-x64": "0.17.16", + "@esbuild/win32-arm64": "0.17.16", + "@esbuild/win32-ia32": "0.17.16", + "@esbuild/win32-x64": "0.17.16" } }, "node_modules/escalade": { @@ -6837,6 +8029,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -6861,11 +8062,82 @@ "node": ">=6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/events-listener": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/events-listener/-/events-listener-1.1.0.tgz", "integrity": "sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g==" }, + "node_modules/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/exegesis": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/exegesis/-/exegesis-4.1.0.tgz", @@ -7049,6 +8321,18 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -7337,6 +8621,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root2": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", + "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", + "dev": true, + "dependencies": { + "micromatch": "^4.0.2", + "pkg-dir": "^4.2.0" + } + }, "node_modules/firebase": { "version": "9.16.0", "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.16.0.tgz", @@ -7810,6 +9104,18 @@ "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-uri": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", @@ -7868,6 +9174,12 @@ "assert-plus": "^1.0.0" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "dev": true + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -8060,92 +9372,356 @@ "minifyProtoJson": "build/tools/minify.js" }, "engines": { - "node": ">=12" + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/@grpc/proto-loader": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", + "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/google-gax/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/google-gax/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/google-gax/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/google-gax/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/google-gax/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==" + }, + "node_modules/google-gax/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/google-gax/node_modules/gaxios": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", + "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-gax/node_modules/google-auth-library": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", + "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/google-gax/node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/google-gax/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/google-gax/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==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-gax/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-gax/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==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-gax/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-gax/node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, + "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/google-gax/node_modules/@grpc/proto-loader": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.4.tgz", - "integrity": "sha512-MnWjkGwqQ3W8fx94/c1CwqLsNmHHv2t0CFn+9++6+cDphC1lolpg9M2OU0iebIjK//pBNX9e94ho+gjx6vz39w==", + "node_modules/google-gax/node_modules/protobufjs-cli": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", + "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" }, "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" }, "engines": { - "node": ">=6" + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" } }, - "node_modules/google-gax/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==", + "node_modules/google-gax/node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, + "node_modules/google-gax/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dependencies": { - "color-convert": "^2.0.1" + "lru-cache": "^6.0.0" }, - "engines": { - "node": ">=8" + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=10" } }, - "node_modules/google-gax/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/google-gax/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==", "dependencies": { - "balanced-match": "^1.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/google-gax/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/google-p12-pem": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "node-forge": "^1.0.0" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/google-gax/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==", + "node_modules/googleapis": { + "version": "105.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-105.0.0.tgz", + "integrity": "sha512-wH/jU/6QpqwsjTKj4vfKZz97ne7xT7BBbKwzQEwnbsG8iH9Seyw19P+AuLJcxNNrmgblwLqfr3LORg4Okat1BQ==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "google-auth-library": "^8.0.2", + "googleapis-common": "^6.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=12.0.0" } }, - "node_modules/google-gax/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==" - }, - "node_modules/google-gax/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/googleapis-common": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.3.tgz", + "integrity": "sha512-Xyb4FsQ6PQDu4tAE/M/ev4yzZhFe2Gc7+rKmuCX2ZGk1ajBKbafsGlVYpmzGqQOT93BRDe8DiTmQb6YSkbICrA==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^5.0.1", + "google-auth-library": "^8.0.2", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, "engines": { - "node": ">=4.0" + "node": ">=12.0.0" } }, - "node_modules/google-gax/node_modules/gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", + "node_modules/googleapis-common/node_modules/gaxios": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", + "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "dev": true, "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", @@ -8156,10 +9732,11 @@ "node": ">=12" } }, - "node_modules/google-gax/node_modules/gcp-metadata": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", - "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "node_modules/googleapis-common/node_modules/gcp-metadata": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", + "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "dev": true, "dependencies": { "gaxios": "^5.0.0", "json-bigint": "^1.0.0" @@ -8168,28 +9745,130 @@ "node": ">=12" } }, - "node_modules/google-gax/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/googleapis-common/node_modules/google-auth-library": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.5.1.tgz", + "integrity": "sha512-7jNMDRhenfw2HLfL9m0ZP/Jw5hzXygfSprzBdypG3rZ+q2gIUbVC/osrFB7y/Z5dkrUr1mnLoDNlerF+p6VXZA==", + "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.0.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/googleapis-common/node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dev": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common/node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dev": true, + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common/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==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/googleapis-common/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis-common/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==", + "dev": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/googleapis/node_modules/gaxios": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", + "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" }, "engines": { "node": ">=12" + } + }, + "node_modules/googleapis/node_modules/gcp-metadata": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", + "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "dev": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=12" } }, - "node_modules/google-gax/node_modules/google-auth-library": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", - "integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==", + "node_modules/googleapis/node_modules/google-auth-library": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.5.1.tgz", + "integrity": "sha512-7jNMDRhenfw2HLfL9m0ZP/Jw5hzXygfSprzBdypG3rZ+q2gIUbVC/osrFB7y/Z5dkrUr1mnLoDNlerF+p6VXZA==", + "dev": true, "dependencies": { "arrify": "^2.0.0", "base64-js": "^1.3.0", @@ -8205,10 +9884,11 @@ "node": ">=12" } }, - "node_modules/google-gax/node_modules/google-p12-pem": { + "node_modules/googleapis/node_modules/google-p12-pem": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dev": true, "dependencies": { "node-forge": "^1.3.1" }, @@ -8219,10 +9899,11 @@ "node": ">=12.0.0" } }, - "node_modules/google-gax/node_modules/gtoken": { + "node_modules/googleapis/node_modules/gtoken": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dev": true, "dependencies": { "gaxios": "^5.0.1", "google-p12-pem": "^4.0.0", @@ -8232,18 +9913,11 @@ "node": ">=12.0.0" } }, - "node_modules/google-gax/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/google-gax/node_modules/is-stream": { + "node_modules/googleapis/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==", + "dev": true, "engines": { "node": ">=8" }, @@ -8251,907 +9925,1158 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/google-gax/node_modules/jwa": { + "node_modules/googleapis/node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, - "node_modules/google-gax/node_modules/jws": { + "node_modules/googleapis/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==", + "dev": true, "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, - "node_modules/google-gax/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=6.0" } }, - "node_modules/google-gax/node_modules/protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "hasInstallScript": true, - "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" - }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, "engines": { - "node": ">=12.0.0" + "node": ">=4.x" } }, - "node_modules/google-gax/node_modules/protobufjs-cli": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.0.tgz", - "integrity": "sha512-VXMQn+z3yG2WbN2E+mx5vcyIHF7yJSg2jqyqfxcZLWNOSTqUzSSgAE5vu04/JEpwxTI04JGyrZRDHC36wr04uw==", + "node_modules/gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", "dependencies": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^4.0.0", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "protobufjs": "^7.0.0" + "node": ">=10" } }, - "node_modules/google-gax/node_modules/protobufjs/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } }, - "node_modules/google-gax/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "node_modules/gtoken/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==", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/google-gax/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==", + "node_modules/har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "deprecated": "this library is no longer supported", "dependencies": { - "has-flag": "^4.0.0" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/google-p12-pem": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", - "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dependencies": { - "node-forge": "^1.0.0" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" + "function-bind": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4.0" } }, - "node_modules/googleapis": { - "version": "105.0.0", - "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-105.0.0.tgz", - "integrity": "sha512-wH/jU/6QpqwsjTKj4vfKZz97ne7xT7BBbKwzQEwnbsG8iH9Seyw19P+AuLJcxNNrmgblwLqfr3LORg4Okat1BQ==", + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true, - "dependencies": { - "google-auth-library": "^8.0.2", - "googleapis-common": "^6.0.0" - }, "engines": { - "node": ">=12.0.0" + "node": ">=4" } }, - "node_modules/googleapis-common": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.3.tgz", - "integrity": "sha512-Xyb4FsQ6PQDu4tAE/M/ev4yzZhFe2Gc7+rKmuCX2ZGk1ajBKbafsGlVYpmzGqQOT93BRDe8DiTmQb6YSkbICrA==", + "node_modules/has-package-exports": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/has-package-exports/-/has-package-exports-1.3.0.tgz", + "integrity": "sha512-e9OeXPQnmPhYoJ63lXC4wWe34TxEGZDZ3OQX9XRqp2VwsfLl3bQBy7VehLnd34g3ef8CmYlBLGqEMKXuz8YazQ==", "dev": true, "dependencies": { - "extend": "^3.0.2", - "gaxios": "^5.0.1", - "google-auth-library": "^8.0.2", - "qs": "^6.7.0", - "url-template": "^2.0.8", - "uuid": "^9.0.0" + "@ljharb/has-package-exports-patterns": "^0.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "engines": { - "node": ">=12.0.0" + "node": ">=8" } }, - "node_modules/googleapis-common/node_modules/gaxios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", - "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "node_modules/hasha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", + "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", "dev": true, "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" + "type-fest": "^0.8.0" }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/googleapis-common/node_modules/gcp-metadata": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", - "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", + "node_modules/hasha/node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/googleapis-common/node_modules/google-auth-library": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.5.1.tgz", - "integrity": "sha512-7jNMDRhenfw2HLfL9m0ZP/Jw5hzXygfSprzBdypG3rZ+q2gIUbVC/osrFB7y/Z5dkrUr1mnLoDNlerF+p6VXZA==", + "node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", "dev": true, "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" }, - "engines": { - "node": ">=12" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/googleapis-common/node_modules/google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", "dev": true, "dependencies": { - "node-forge": "^1.3.1" + "@types/hast": "^2.0.0" }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" }, - "engines": { - "node": ">=12.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/googleapis-common/node_modules/gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "node_modules/hast-util-to-html": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", "dev": true, "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^7.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" }, - "engines": { - "node": ">=12.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/googleapis-common/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==", + "node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/googleapis-common/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", "dev": true, - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/googleapis-common/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==", + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", "dev": true, "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/googleapis-common/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "bin": { - "uuid": "dist/bin/uuid" + "he": "bin/he" } }, - "node_modules/googleapis/node_modules/gaxios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.1.tgz", - "integrity": "sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==", + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": 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 + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "node_modules/googleapis/node_modules/gcp-metadata": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.0.0.tgz", - "integrity": "sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA==", - "dev": true, + "node_modules/http-errors/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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "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==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=12" + "node": ">= 6" } }, - "node_modules/googleapis/node_modules/google-auth-library": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.5.1.tgz", - "integrity": "sha512-7jNMDRhenfw2HLfL9m0ZP/Jw5hzXygfSprzBdypG3rZ+q2gIUbVC/osrFB7y/Z5dkrUr1mnLoDNlerF+p6VXZA==", - "dev": true, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.0.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/googleapis/node_modules/google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "dev": true, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dependencies": { - "node-forge": "^1.3.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "dev": true + }, + "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==", + "dependencies": { + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 6" } }, - "node_modules/googleapis/node_modules/gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "dev": true, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=12.0.0" + "ms": "^2.1.1" } }, - "node_modules/googleapis/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==", + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", "dev": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12.20.0" } }, - "node_modules/googleapis/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dev": true, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "ms": "^2.0.0" } }, - "node_modules/googleapis/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==", - "dev": true, + "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==", "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", "dev": true }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, "engines": { - "node": ">=4.x" + "node": ">= 4" } }, - "node_modules/gtoken": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", - "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.1.3", - "jws": "^4.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gtoken/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "engines": { + "node": ">=4" } }, - "node_modules/gtoken/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==", - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" + "node_modules/import-meta-resolve": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.2.2.tgz", + "integrity": "sha512-f8KcQ1D80V7RnqVm+/lirO9zkOxjGxhaTC1IPrBGd3MEfNgmNG67tSUO9gTi2F3Blr2Az6g1vocaxzkVnWl9MA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "engines": { - "node": ">=4" + "node": ">=0.8.19" } }, - "node_modules/har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "deprecated": "this library is no longer supported", - "dependencies": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "devOptional": true, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inquirer": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, "engines": { - "node": ">=6" + "node": ">=12.0.0" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/inquirer/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==", "dependencies": { - "function-bind": "^1.1.1" + "type-fest": "^0.21.3" }, "engines": { - "node": ">= 0.4.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, + "node_modules/inquirer/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/has-unicode": { + "node_modules/inquirer/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "optional": true + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "node_modules/inquirer/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==" + }, + "node_modules/inquirer/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==", "engines": { "node": ">=8" } }, - "node_modules/hasha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", - "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", - "dev": true, + "node_modules/inquirer/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==", "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/hasha/node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, + "node_modules/inquirer/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==", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, + "node_modules/install-artifact-from-github": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.3.1.tgz", + "integrity": "sha512-3l3Bymg2eKDsN5wQuMfgGEj2x6l5MCAv0zPL6rxHESufFVlEAKW/6oY9F1aGgvY/EgWm5+eWGRjINveL4X7Hgg==", + "optional": true, "bin": { - "he": "bin/he" + "install-from-cache": "bin/install-from-cache.js", + "save-to-github-cache": "bin/save-to-github-cache.js" } }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true, + "node_modules/ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", "engines": { "node": ">=8" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "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==", + "engines": { + "node": ">= 0.10" + } }, - "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==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "optional": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/http-errors/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==", + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "engines": { - "node": ">= 0.8" + "node": ">=4" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" } }, - "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==", - "dev": true - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "has": "^1.0.3" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dependencies": { - "ms": "2.1.2" + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">=6.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" + "node": ">=0.10.0" } }, - "node_modules/http2-client": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", - "dev": true - }, - "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==", + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "dependencies": { - "agent-base": "6", - "debug": "4" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dependencies": { - "ms": "^2.1.1" + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" } }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "optional": true, - "dependencies": { - "ms": "^2.0.0" + "node_modules/is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, + "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==", "engines": { - "node": ">=0.10.0" + "node": ">=0.12.0" } }, - "node_modules/idb": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", - "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==", - "dev": true + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true, "engines": { - "node": ">= 4" + "node": ">=0.10.0" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "engines": { - "node": ">=6" + "node": ">=0.10.0" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "engines": { - "node": ">=4" - } + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "engines": { - "node": ">=0.8.19" + "node": ">=0.10.0" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "devOptional": true, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "optional": true + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/is2": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz", + "integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + }, + "engines": { + "node": ">=v0.10.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, - "node_modules/inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" - }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/isomorphic-fetch/node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, "engines": { - "node": ">=12.0.0" + "node": ">=8" } }, - "node_modules/inquirer/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==", + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, "dependencies": { - "type-fest": "^0.21.3" + "append-transform": "^2.0.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inquirer/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==", + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/inquirer/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==", + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/inquirer/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==" - }, - "node_modules/inquirer/node_modules/has-flag": { + "node_modules/istanbul-lib-report/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, "engines": { "node": ">=8" } }, - "node_modules/inquirer/node_modules/supports-color": { + "node_modules/istanbul-lib-report/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, "dependencies": { "has-flag": "^4.0.0" }, @@ -9159,811 +11084,940 @@ "node": ">=8" } }, - "node_modules/inquirer/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==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=" + }, + "node_modules/join-path": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/join-path/-/join-path-1.1.1.tgz", + "integrity": "sha1-EFNaEm0ky9Zff/zfFe8uYxB2tQU=", + "dependencies": { + "as-array": "^2.0.0", + "url-join": "0.0.1", + "valid-url": "^1" + } + }, + "node_modules/jose": { + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", + "integrity": "sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A==", + "dev": true, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/install-artifact-from-github": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.3.1.tgz", - "integrity": "sha512-3l3Bymg2eKDsN5wQuMfgGEj2x6l5MCAv0zPL6rxHESufFVlEAKW/6oY9F1aGgvY/EgWm5+eWGRjINveL4X7Hgg==", - "optional": true, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "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 + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, "bin": { - "install-from-cache": "bin/install-from-cache.js", - "save-to-github-cache": "bin/save-to-github-cache.js" + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dependencies": { + "xmlcreate": "^2.0.4" + } }, - "node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/jsdoc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", + "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", + "dependencies": { + "@babel/parser": "^7.9.4", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, "engines": { - "node": ">=8" + "node": ">=12.0.0" } }, - "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==", + "node_modules/jsdoc-type-pratt-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", + "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", + "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=12.0.0" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "node_modules/jsdoc/node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, + "node_modules/jsdoc/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==", "engines": { "node": ">=8" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dependencies": { - "ci-info": "^2.0.0" - }, + "node_modules/jsdoc/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "bin": { - "is-ci": "bin.js" + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "dependencies": { - "has": "^1.0.3" + "node_modules/jsdoc/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==", + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "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==", - "engines": { - "node": ">=8" + "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==", + "dependencies": { + "bignumber.js": "^9.0.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "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 + }, + "node_modules/json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" + "jju": "^1.1.0" } }, - "node_modules/is-installed-globally": { + "node_modules/json-ptr": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-ptr/-/json-ptr-3.0.1.tgz", + "integrity": "sha512-hrZ4tElT8huJUH3OwOK+d7F8PRqw09QnGM3Mm3GmqKWDyCCPCG8lGHxXOwQAj0VOxzLirOds07Kz10B5F8M8EA==" + }, + "node_modules/json-schema": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-compatibility": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/json-schema-compatibility/-/json-schema-compatibility-1.1.0.tgz", + "integrity": "sha1-GomBd4zaDDgYcpjZmdCJ5Rrygt8=", + "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "engines": { - "node": ">=8" + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "dependencies": { + "jsonify": "~0.0.0" } }, - "node_modules/is-lambda": { + "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "optional": true + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true }, - "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "engines": { - "node": ">=10" + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "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, + "bin": { + "json5": "lib/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "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==", - "engines": { - "node": ">=0.12.0" + "node_modules/jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "dev": true, + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs=", "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=0.4.0" } }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "node_modules/jsonpath/node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "dev": true + }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12", + "npm": ">=6" } }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "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==" }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, "engines": { "node": ">=10" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.6.0" } }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + "node_modules/just-extend": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "dev": true }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "engines": { - "node": ">=0.10.0" + "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==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "node_modules/jwks-rsa": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", + "dev": true, + "dependencies": { + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", + "debug": "^4.3.4", + "jose": "^4.10.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, "engines": { - "node": ">=4" + "node": ">=14" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + "node_modules/jwks-rsa/node_modules/@types/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } }, - "node_modules/is2": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz", - "integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==", + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" + "ms": "2.1.2" }, "engines": { - "node": ">=v0.10.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "node_modules/isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/isomorphic-fetch/node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/istanbul-lib-hook": { + "node_modules/klaw": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" + "graceful-fs": "^4.1.9" } }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "dependencies": { + "colornames": "^1.1.1" } }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", - "dev": true, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "readable-stream": "^2.0.5" }, "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" + "node": ">= 0.6.3" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/istanbul-lib-report/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, + "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==", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/istanbul-lib-report/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, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dependencies": { - "has-flag": "^4.0.0" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, + "node_modules/libsodium": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz", + "integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ==" + }, + "node_modules/libsodium-wrappers": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz", + "integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==", "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8" + "libsodium": "^0.7.0" } }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, + "node_modules/lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "dependencies": { - "ms": "^2.1.1" + "uc.micro": "^1.0.1" } }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" }, - "node_modules/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "node_modules/load-yaml-file": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", + "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", "dev": true, "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "graceful-fs": "^4.1.5", + "js-yaml": "^3.13.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/jju": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", - "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=" - }, - "node_modules/join-path": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/join-path/-/join-path-1.1.1.tgz", - "integrity": "sha1-EFNaEm0ky9Zff/zfFe8uYxB2tQU=", - "dependencies": { - "as-array": "^2.0.0", - "url-join": "0.0.1", - "valid-url": "^1" + "node_modules/load-yaml-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" } }, - "node_modules/jose": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", - "integrity": "sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/panva" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/js-sdsl": { + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=" + }, + "node_modules/lodash.camelcase": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, - "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==", + "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==", "dev": true }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "dependencies": { - "xmlcreate": "^2.0.4" - } + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" }, - "node_modules/jsdoc": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", - "integrity": "sha512-tzTgkklbWKrlaQL2+e3NNgLcZu3NaK2vsHRx7tyHQ+H5jcB9Gx0txSd2eJWlMC/xU1+7LQu4s58Ry0RkuaEQVg==", + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", "dependencies": { - "@babel/parser": "^7.9.4", - "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^12.2.3", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" + "lodash._objecttypes": "~2.4.1" } }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", - "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, - "node_modules/jsdoc/node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true }, - "node_modules/jsdoc/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==", - "engines": { - "node": ">=8" - } + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=" }, - "node_modules/jsdoc/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jsdoc/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==", + "node_modules/log-symbols/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "node_modules/log-symbols/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==", "dependencies": { - "bignumber.js": "^9.0.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.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 + "node_modules/log-symbols/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==" }, - "node_modules/json-parse-helpfulerror": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", - "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", - "dependencies": { - "jju": "^1.1.0" + "node_modules/log-symbols/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==", + "engines": { + "node": ">=8" } }, - "node_modules/json-ptr": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-ptr/-/json-ptr-3.0.1.tgz", - "integrity": "sha512-hrZ4tElT8huJUH3OwOK+d7F8PRqw09QnGM3Mm3GmqKWDyCCPCG8lGHxXOwQAj0VOxzLirOds07Kz10B5F8M8EA==" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "node_modules/json-schema-compatibility": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/json-schema-compatibility/-/json-schema-compatibility-1.1.0.tgz", - "integrity": "sha1-GomBd4zaDDgYcpjZmdCJ5Rrygt8=", - "dev": true, + "node_modules/log-symbols/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, + "node_modules/logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", "dependencies": { - "jsonify": "~0.0.0" + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" } }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "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, - "bin": { - "json5": "lib/cli.js" - }, + "node_modules/logform/node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "engines": { - "node": ">=6" + "node": ">=0.1.90" } }, - "node_modules/jsonc-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", - "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", - "dev": true + "node_modules/logform/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/jsonfile": { + "node_modules/long": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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==", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": "*" + "node": ">=10" } }, - "node_modules/jsonpath": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", - "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "node_modules/lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", "dev": true, "dependencies": { - "esprima": "1.2.2", - "static-eval": "2.0.2", - "underscore": "1.12.1" + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" } }, - "node_modules/jsonpath/node_modules/esprima": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", - "integrity": "sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs=", + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" } }, - "node_modules/jsonpath/node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true }, - "node_modules/jsonwebtoken": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", - "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, "dependencies": { - "jws": "^3.2.2", - "lodash": "^4.17.21", - "ms": "^2.1.1", - "semver": "^7.3.8" + "@jridgewell/sourcemap-codec": "^1.4.13" }, "engines": { - "node": ">=12", - "npm": ">=6" + "node": ">=12" } }, - "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==" - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "semver": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/just-extend": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "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==", - "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.0.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", - "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", - "dev": true, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "optional": true, "dependencies": { - "@types/express": "^4.17.14", - "@types/jsonwebtoken": "^9.0.0", - "debug": "^4.3.4", - "jose": "^4.10.4", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" }, "engines": { - "node": ">=14" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/jwks-rsa/node_modules/@types/jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", - "dev": true, - "dependencies": { - "@types/node": "*" + "node_modules/make-fetch-happen/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==", + "optional": true, + "engines": { + "node": ">= 10" } }, - "node_modules/jwks-rsa/node_modules/debug": { + "node_modules/make-fetch-happen/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "optional": true, "dependencies": { "ms": "2.1.2" }, @@ -9976,633 +12030,995 @@ } } }, - "node_modules/jwks-rsa/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "node_modules/make-fetch-happen/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==", + "optional": true, "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", + "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", + "optional": true, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "node_modules/make-fetch-happen/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "optional": true, "dependencies": { - "graceful-fs": "^4.1.9" + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "node_modules/map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", "dev": true, "engines": { - "node": ">=6" - } - }, - "node_modules/kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "dependencies": { - "colornames": "^1.1.1" + "node": ">=8" } }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "dependencies": { - "readable-stream": "^2.0.5" + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" }, - "engines": { - "node": ">= 0.6.3" + "bin": { + "markdown-it": "bin/markdown-it.js" } }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "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" + "node_modules/markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" } }, - "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==", - "engines": { - "node": ">=6" + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "node_modules/marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "bin": { + "marked": "bin/marked.js" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 12" } }, - "node_modules/libsodium": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz", - "integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ==" - }, - "node_modules/libsodium-wrappers": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz", - "integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==", + "node_modules/marked-terminal": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", + "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", "dependencies": { - "libsodium": "^0.7.0" + "ansi-escapes": "^5.0.0", + "cardinal": "^2.1.1", + "chalk": "^5.0.0", + "cli-table3": "^0.6.1", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.2.0" + }, + "engines": { + "node": ">=14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" } }, - "node_modules/limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", - "dev": true - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dev": true, "dependencies": { - "uc.micro": "^1.0.1" + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/mdast-util-find-and-replace": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", "dev": true, "dependencies": { - "p-locate": "^5.0.0" + "@types/mdast": "^3.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "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==", - "dev": true - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true + "node_modules/mdast-util-from-markdown": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz", + "integrity": "sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "node_modules/mdast-util-gfm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", + "dev": true, "dependencies": { - "lodash._objecttypes": "~2.4.1" + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", - "dev": true - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=" - }, - "node_modules/lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", + "dev": true, "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/log-symbols/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==", + "node_modules/mdast-util-gfm-footnote": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", + "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/mdast-util-gfm-strikethrough": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" }, - "engines": { - "node": ">=10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/log-symbols/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==", + "node_modules/mdast-util-gfm-task-list-item": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/log-symbols/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==" + "node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/log-symbols/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==", - "engines": { - "node": ">=8" + "node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/log-symbols/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==", + "node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/logform": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", - "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dev": true, "dependencies": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", - "ms": "^2.1.1", - "triple-beam": "^1.3.0" + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/logform/node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "engines": { - "node": ">=0.1.90" + "node": ">= 0.6" } }, - "node_modules/logform/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "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 }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", + "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", + "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", + "dev": true, + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" }, - "bin": { - "loose-envify": "cli.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "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==", + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", + "dev": true, "dependencies": { - "yallist": "^4.0.0" + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, - "engines": { - "node": ">=10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lru-memoizer": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", - "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.0.tgz", + "integrity": "sha512-RWYce7j8+c0n7Djzv5NzGEGitNNYO3uj+h/XYMdS/JinH1Go+/Qkomg/rfxExFzYTiydaV6GLeffGO5qcJbMPA==", "dev": true, "dependencies": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lru-memoizer/node_modules/lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.5.tgz", + "integrity": "sha512-X0oI5eYYQVARhiNfbETy7BfLSmSilzN1eOuoRnrf9oUNsPRrWOAe9UqSizgw1vNxQBfOwL+n2610S3bYjVNi7w==", "dev": true, "dependencies": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lru-memoizer/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", + "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", + "dev": true, "dependencies": { - "semver": "^6.0.0" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", + "dev": true, + "dependencies": { + "micromark-util-types": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.4.tgz", + "integrity": "sha512-9XlIUUVnYXHsFF2HZ9jby4h3npfX10S1coXTnV035QGPgrtNYQq3J6IfIvcCIUAJrrqBVi5BqA/LmaOMJqPwMQ==", + "dev": true, + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "node_modules/micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } }, - "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "optional": true, + "node_modules/micromark-factory-label": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, - "node_modules/make-fetch-happen/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==", - "optional": true, - "engines": { - "node": ">= 10" + "node_modules/micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/make-fetch-happen/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "optional": true, + "node_modules/micromark-factory-title": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/make-fetch-happen/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==", - "optional": true, + "node_modules/micromark-util-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", - "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", - "optional": true, - "engines": { - "node": ">=12" + "node_modules/micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/make-fetch-happen/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "optional": true - }, - "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "optional": true, + "node_modules/micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "node_modules/micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", "dev": true, - "engines": { - "node": ">=8" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/markdown-it-anchor": { - "version": "8.6.6", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", - "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" + "node_modules/micromark-util-decode-string": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/markdown-it/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "node_modules/micromark-util-encode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, - "node_modules/marked": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", - "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } + "node_modules/micromark-util-html-tag-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, - "node_modules/marked-terminal": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", - "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", + "node_modules/micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "ansi-escapes": "^5.0.0", - "cardinal": "^2.1.1", - "chalk": "^5.0.0", - "cli-table3": "^0.6.1", - "node-emoji": "^1.11.0", - "supports-hyperlinks": "^2.2.0" - }, - "engines": { - "node": ">=14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node_modules/micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" } }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + "node_modules/micromark-util-sanitize-uri": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", + "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" + "node_modules/micromark-util-subtokenize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, - "node_modules/merge-descriptors": { + "node_modules/micromark-util-symbol": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/micromark-util-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", "dev": true, - "engines": { - "node": ">= 8" - } + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "node_modules/micromark/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">= 0.6" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/micromark/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -11152,6 +13568,19 @@ "isarray": "0.0.1" } }, + "node_modules/nlcst-to-string": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz", + "integrity": "sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==", + "dev": true, + "dependencies": { + "@types/nlcst": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/nock": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.5.tgz", @@ -11386,6 +13815,33 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", @@ -12243,6 +14699,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-latin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-5.0.1.tgz", + "integrity": "sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==", + "dev": true, + "dependencies": { + "nlcst-to-string": "^3.0.0", + "unist-util-modify-children": "^3.0.0", + "unist-util-visit-children": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -12333,6 +14810,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": 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", @@ -12459,6 +14945,21 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/preferred-pm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz", + "integrity": "sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0", + "find-yarn-workspace-root2": "1.2.16", + "path-exists": "^4.0.0", + "which-pm": "2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -12494,6 +14995,37 @@ "node": ">=6.0.0" } }, + "node_modules/prettier-plugin-astro": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.7.2.tgz", + "integrity": "sha512-mmifnkG160BtC727gqoimoxnZT/dwr8ASxpoGGl6EHevhfblSOeu+pwH1LAm5Qu1MynizktztFujHHaijLCkww==", + "dev": true, + "dependencies": { + "@astrojs/compiler": "^0.31.3", + "prettier": "^2.7.1", + "sass-formatter": "^0.7.5", + "synckit": "^0.8.4" + }, + "engines": { + "node": "^14.15.0 || >=16.0.0", + "pnpm": ">=7.14.0" + } + }, + "node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler": { + "version": "0.31.4", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-0.31.4.tgz", + "integrity": "sha512-6bBFeDTtPOn4jZaiD3p0f05MEGQL9pw2Zbfj546oFETNmjJFWO3nzHz6/m+P53calknCvyVzZ5YhoBLIvzn5iw==", + "dev": true + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -12552,6 +15084,28 @@ "node": ">= 4" } }, + "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, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/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, + "engines": { + "node": ">=6" + } + }, "node_modules/propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", @@ -12561,6 +15115,16 @@ "node": ">= 8" } }, + "node_modules/property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -13170,6 +15734,68 @@ "node": ">=8" } }, + "node_modules/rehype": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-12.0.1.tgz", + "integrity": "sha512-ey6kAqwLM3X6QnMDILJthGvG1m1ULROS9NT4uG9IDCuv08SFyLlreSuvOa//DgEvbXx62DS6elGVqusWhRUbgw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "rehype-parse": "^8.0.0", + "rehype-stringify": "^9.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz", + "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^7.0.0", + "parse5": "^6.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", + "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-raw": "^7.2.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.3.tgz", + "integrity": "sha512-kWiZ1bgyWlgOxpqD5HnxShKAdXtb2IUljn3hQAhySeak6IOQPPt6DeGnsIh4ixm7yKJWzm8TXFuC/lPfcWHJqw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-to-html": "^8.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -13182,6 +15808,67 @@ "node": ">=4" } }, + "node_modules/remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-2.0.0.tgz", + "integrity": "sha512-Rc0VDmr/yhnMQIz8n2ACYXlfw/P/XZev884QU1I5u+5DgJls32o97Vc1RbK3pfumLsJomS2yy8eT4Fxj/2MDVA==", + "dev": true, + "dependencies": { + "retext": "^8.1.0", + "retext-smartypants": "^5.1.0", + "unist-util-visit": "^4.1.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -13311,6 +15998,69 @@ "node": ">=8" } }, + "node_modules/retext": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-8.1.0.tgz", + "integrity": "sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==", + "dev": true, + "dependencies": { + "@types/nlcst": "^1.0.0", + "retext-latin": "^3.0.0", + "retext-stringify": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-3.1.0.tgz", + "integrity": "sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==", + "dev": true, + "dependencies": { + "@types/nlcst": "^1.0.0", + "parse-latin": "^5.0.0", + "unherit": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-5.2.0.tgz", + "integrity": "sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==", + "dev": true, + "dependencies": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-3.1.0.tgz", + "integrity": "sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==", + "dev": true, + "dependencies": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -13377,15 +16127,16 @@ } }, "node_modules/rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.2.tgz", + "integrity": "sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==", "dev": true, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=10.0.0" + "node": ">=14.18.0", + "npm": ">=8.0.0" }, "optionalDependencies": { "fsevents": "~2.3.2" @@ -13457,6 +16208,24 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, + "node_modules/s.color": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz", + "integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==", + "dev": true + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -13467,6 +16236,15 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sass-formatter": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.6.tgz", + "integrity": "sha512-hXdxU6PCkiV3XAiSnX+XLqz2ohHoEnVUlrd8LEVMAI80uB1+OTScIkH9n6qQwImZpTye1r1WG1rbGUteHNhoHg==", + "dev": true, + "dependencies": { + "suf-log": "^2.5.3" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -13477,6 +16255,19 @@ "loose-envify": "^1.1.0" } }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -13593,6 +16384,12 @@ "node": ">= 0.8.0" } }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -13628,6 +16425,17 @@ "node": ">=0.10.0" } }, + "node_modules/shiki": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.11.1.tgz", + "integrity": "sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "^6.0.0" + } + }, "node_modules/should": { "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", @@ -13771,6 +16579,12 @@ "node": ">=8" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -13869,6 +16683,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -13999,6 +16823,56 @@ "node": ">= 0.6" } }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stdin-discarder/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/stdin-discarder/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/stream-chain": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.4.tgz", @@ -14027,6 +16901,15 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -14048,6 +16931,20 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dev": true, + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -14076,6 +16973,27 @@ "node": ">=8" } }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -14126,6 +17044,15 @@ } } }, + "node_modules/suf-log": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz", + "integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==", + "dev": true, + "dependencies": { + "s.color": "0.0.15" + } + }, "node_modules/superagent": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.3.tgz", @@ -14288,6 +17215,15 @@ "node": ">=4" } }, + "node_modules/supports-esm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-esm/-/supports-esm-1.0.0.tgz", + "integrity": "sha512-96Am8CDqUaC0I2+C/swJ0yEvM8ZnGn4unoers/LSdE4umhX7mELzqyLzx3HnZAluq5PXIsGMKqa7NkqaeHMPcg==", + "dev": true, + "dependencies": { + "has-package-exports": "^1.1.0" + } + }, "node_modules/supports-hyperlinks": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", @@ -14408,6 +17344,28 @@ "node": ">=12" } }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + }, "node_modules/tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", @@ -14735,6 +17693,16 @@ "node": "*" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -14749,6 +17717,16 @@ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-is-present": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ts-is-present/-/ts-is-present-1.1.5.tgz", @@ -14807,6 +17785,35 @@ "node": ">=0.3.1" } }, + "node_modules/tsconfig-resolver": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tsconfig-resolver/-/tsconfig-resolver-3.0.1.tgz", + "integrity": "sha512-ZHqlstlQF449v8glscGRXzL6l2dZvASPCdXJRWG4gHEZlUVx2Jtmr+a2zeVG4LCsKhDXKRj5R3h0C/98UcVAQg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.30", + "@types/resolve": "^1.17.0", + "json5": "^2.1.3", + "resolve": "^1.17.0", + "strip-bom": "^4.0.0", + "type-fest": "^0.13.1" + }, + "funding": { + "url": "https://github.com/sponsors/ifiokjr" + } + }, + "node_modules/tsconfig-resolver/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -15003,6 +18010,59 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" }, + "node_modules/undici": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.2.tgz", + "integrity": "sha512-f6pTQ9RF4DQtwoWSaC42P/NKlUjvezVvd9r155ohqkwFNRyBKM3f3pcty3ouusefNRyM25XhIQEbeQ46sZDJfQ==", + "dev": true, + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=12.18" + } + }, + "node_modules/unherit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-3.0.1.tgz", + "integrity": "sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -15027,6 +18087,111 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-3.1.1.tgz", + "integrity": "sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-2.0.2.tgz", + "integrity": "sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universal-analytics": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.5.3.tgz", @@ -15346,6 +18511,24 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -15388,16 +18571,60 @@ "extsprintf": "^1.2.0" } }, + "node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.3.tgz", - "integrity": "sha512-/3XWiktaopByM5bd8dqvHxRt5EEgRikevnnrpND0gRfNkrMrPaGGexhtLCzv15RcCMtV2CLw+BPas8YFeSG0KA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==", "dev": true, "dependencies": { - "esbuild": "^0.15.6", - "postcss": "^8.4.16", + "esbuild": "^0.17.5", + "postcss": "^8.4.21", "resolve": "^1.22.1", - "rollup": "~2.78.0" + "rollup": "^3.18.0" }, "bin": { "vite": "bin/vite.js" @@ -15409,12 +18636,17 @@ "fsevents": "~2.3.2" }, "peerDependencies": { + "@types/node": ">= 14", "less": "*", "sass": "*", "stylus": "*", + "sugarss": "*", "terser": "^5.4.0" }, "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, "less": { "optional": true }, @@ -15424,16 +18656,25 @@ "stylus": { "optional": true }, + "sugarss": { + "optional": true + }, "terser": { "optional": true } } }, "node_modules/vite/node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -15442,9 +18683,9 @@ } }, "node_modules/vite/node_modules/postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "dev": true, "funding": [ { @@ -15465,6 +18706,20 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/vitefu": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", + "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", + "dev": true, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vm2": { "version": "3.9.11", "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.11.tgz", @@ -15474,12 +18729,97 @@ "acorn-walk": "^8.2.0" }, "bin": { - "vm2": "bin/vm2" - }, - "engines": { - "node": ">=6.0" + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/vscode-css-languageservice": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.2.4.tgz", + "integrity": "sha512-9UG0s3Ss8rbaaPZL1AkGzdjrGY8F+P+Ne9snsrvD9gxltDGhsn8C2dQpqQewHrMW37OvlqJoI8sUU2AWDb+qNw==", + "dev": true, + "dependencies": { + "@vscode/l10n": "^0.0.11", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3", + "vscode-uri": "^3.0.7" + } + }, + "node_modules/vscode-html-languageservice": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.0.4.tgz", + "integrity": "sha512-tvrySfpglu4B2rQgWGVO/IL+skvU7kBkQotRlxA7ocSyRXOZUd6GA13XHkxo8LPe07KWjeoBlN1aVGqdfTK4xA==", + "dev": true, + "dependencies": { + "@vscode/l10n": "^0.0.11", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.2", + "vscode-uri": "^3.0.7" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz", + "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz", + "integrity": "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==", + "dev": true, + "dependencies": { + "vscode-languageserver-protocol": "3.17.3" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz", + "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==", + "dev": true, + "dependencies": { + "vscode-jsonrpc": "8.1.0", + "vscode-languageserver-types": "3.17.3" } }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", + "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==", + "dev": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==", + "dev": true + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz", + "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", + "dev": true + }, + "node_modules/vscode-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==", + "dev": true + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -15488,6 +18828,16 @@ "defaults": "^1.0.3" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -15542,6 +18892,28 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "node_modules/which-pm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", + "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", + "dev": true, + "dependencies": { + "load-yaml-file": "^0.2.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8.15" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -15866,6 +19238,25 @@ "engines": { "node": ">= 10" } + }, + "node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } }, "dependencies": { @@ -15970,6 +19361,154 @@ "js-yaml": "^3.13.1" } }, + "@astrojs/compiler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-1.3.1.tgz", + "integrity": "sha512-xV/3r+Hrfpr4ECfJjRjeaMkJvU73KiOADowHjhkqidfNPVAWPzbqw1KePXuMK1TjzMvoAVE7E163oqfH3lDwSw==", + "dev": true + }, + "@astrojs/language-server": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-0.28.3.tgz", + "integrity": "sha512-fPovAX/X46eE2w03jNRMpQ7W9m2mAvNt4Ay65lD9wl1Z5vIQYxlg7Enp9qP225muTr4jSVB5QiLumFJmZMAaVA==", + "dev": true, + "requires": { + "@vscode/emmet-helper": "^2.8.4", + "events": "^3.3.0", + "prettier": "^2.7.1", + "prettier-plugin-astro": "^0.7.0", + "source-map": "^0.7.3", + "vscode-css-languageservice": "^6.0.1", + "vscode-html-languageservice": "^5.0.0", + "vscode-languageserver": "^8.0.1", + "vscode-languageserver-protocol": "^3.17.1", + "vscode-languageserver-textdocument": "^1.0.4", + "vscode-languageserver-types": "^3.17.1", + "vscode-uri": "^3.0.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + } + } + }, + "@astrojs/markdown-remark": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-2.1.3.tgz", + "integrity": "sha512-Di8Qbit9p7L7eqKklAJmiW9nVD+XMsNHpaNzCLduWjOonDu9fVgEzdjeDrTVCDtgrvkfhpAekuNXrp5+w4F91g==", + "dev": true, + "requires": { + "@astrojs/prism": "^2.1.0", + "github-slugger": "^1.4.0", + "import-meta-resolve": "^2.1.0", + "rehype-raw": "^6.1.1", + "rehype-stringify": "^9.0.3", + "remark-gfm": "^3.0.1", + "remark-parse": "^10.0.1", + "remark-rehype": "^10.1.0", + "remark-smartypants": "^2.0.0", + "shiki": "^0.11.1", + "unified": "^10.1.2", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.2" + }, + "dependencies": { + "github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "dev": true + } + } + }, + "@astrojs/prism": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-2.1.1.tgz", + "integrity": "sha512-Gnwnlb1lGJzCQEg89r4/WqgfCGPNFC7Kuh2D/k289Cbdi/2PD7Lrdstz86y1itDvcb2ijiRqjqWnJ5rsfu/QOA==", + "dev": true, + "requires": { + "prismjs": "^1.28.0" + } + }, + "@astrojs/telemetry": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-2.1.0.tgz", + "integrity": "sha512-P3gXNNOkRJM8zpnasNoi5kXp3LnFt0smlOSUXhkynfJpTJMIDrcMbKpNORN0OYbqpKt9JPdgRN7nsnGWpbH1ww==", + "dev": true, + "requires": { + "ci-info": "^3.3.1", + "debug": "^4.3.4", + "dlv": "^1.1.3", + "dset": "^3.1.2", + "is-docker": "^3.0.0", + "is-wsl": "^2.2.0", + "undici": "^5.20.0", + "which-pm-runs": "^1.1.0" + }, + "dependencies": { + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + }, + "dependencies": { + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@astrojs/webapi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/webapi/-/webapi-2.1.0.tgz", + "integrity": "sha512-sbF44s/uU33jAdefzKzXZaENPeXR0sR3ptLs+1xp9xf5zIBhedH2AfaFB5qTEv9q5udUVoKxubZGT3G1nWs6rA==", + "dev": true, + "requires": { + "undici": "5.20.0" + }, + "dependencies": { + "undici": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.20.0.tgz", + "integrity": "sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==", + "dev": true, + "requires": { + "busboy": "^1.6.0" + } + } + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -16042,6 +19581,15 @@ "jsesc": "^2.5.1" } }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, "@babel/helper-compilation-targets": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", @@ -16112,6 +19660,12 @@ "@babel/types": "^7.19.0" } }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true + }, "@babel/helper-simple-access": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", @@ -16131,15 +19685,15 @@ } }, "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", "dev": true }, "@babel/helper-validator-option": { @@ -16171,9 +19725,31 @@ } }, "@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==" + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==" + }, + "@babel/plugin-syntax-jsx": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz", + "integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.21.0" + } }, "@babel/template": { "version": "7.18.10", @@ -16222,13 +19798,13 @@ } }, "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" } }, @@ -16259,6 +19835,30 @@ } } }, + "@emmetio/abbreviation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.1.tgz", + "integrity": "sha512-QXgYlXZGprqb6aCBJPPWVBN/Jb69khJF73GGJkOk//PoMgSbPGuaHn1hCRolctnzlBHjCIC6Om97Pw46/1A23g==", + "dev": true, + "requires": { + "@emmetio/scanner": "^1.0.2" + } + }, + "@emmetio/css-abbreviation": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.6.tgz", + "integrity": "sha512-bvuPogt0OvwcILRg+ZD/oej1H72xwOhUDPWOmhCWLJrZZ8bMTazsWnvw8a8noaaVqUhOE9PsC0tYgGVv5N7fsw==", + "dev": true, + "requires": { + "@emmetio/scanner": "^1.0.2" + } + }, + "@emmetio/scanner": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.2.tgz", + "integrity": "sha512-1ESCGgXRgn1r29hRmz8K0G4Ywr5jDWezMgRnICComBCWmg3znLWU8+tmakuM1og1Vn4W/sauvlABl/oq2pve8w==", + "dev": true + }, "@es-joy/jsdoccomment": { "version": "0.36.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", @@ -16271,16 +19871,156 @@ } }, "@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.16.tgz", + "integrity": "sha512-baLqRpLe4JnKrUXLJChoTN0iXZH7El/mu58GE3WIA6/H834k0XWvLRmGLG8y8arTRS9hJJibPnF0tiGhmWeZgw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.16.tgz", + "integrity": "sha512-QX48qmsEZW+gcHgTmAj+x21mwTz8MlYQBnzF6861cNdQGvj2jzzFjqH0EBabrIa/WVZ2CHolwMoqxVryqKt8+Q==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.16.tgz", + "integrity": "sha512-G4wfHhrrz99XJgHnzFvB4UwwPxAWZaZBOFXh+JH1Duf1I4vIVfuYY9uVLpx4eiV2D/Jix8LJY+TAdZ3i40tDow==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.16.tgz", + "integrity": "sha512-/Ofw8UXZxuzTLsNFmz1+lmarQI6ztMZ9XktvXedTbt3SNWDn0+ODTwxExLYQ/Hod91EZB4vZPQJLoqLF0jvEzA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.16.tgz", + "integrity": "sha512-SzBQtCV3Pdc9kyizh36Ol+dNVhkDyIrGb/JXZqFq8WL37LIyrXU0gUpADcNV311sCOhvY+f2ivMhb5Tuv8nMOQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.16.tgz", + "integrity": "sha512-ZqftdfS1UlLiH1DnS2u3It7l4Bc3AskKeu+paJSfk7RNOMrOxmeFDhLTMQqMxycP1C3oj8vgkAT6xfAuq7ZPRA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.16.tgz", + "integrity": "sha512-rHV6zNWW1tjgsu0dKQTX9L0ByiJHHLvQKrWtnz8r0YYJI27FU3Xu48gpK2IBj1uCSYhJ+pEk6Y0Um7U3rIvV8g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.16.tgz", + "integrity": "sha512-n4O8oVxbn7nl4+m+ISb0a68/lcJClIbaGAoXwqeubj/D1/oMMuaAXmJVfFlRjJLu/ZvHkxoiFJnmbfp4n8cdSw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.16.tgz", + "integrity": "sha512-8yoZhGkU6aHu38WpaM4HrRLTFc7/VVD9Q2SvPcmIQIipQt2I/GMTZNdEHXoypbbGao5kggLcxg0iBKjo0SQYKA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.16.tgz", + "integrity": "sha512-9ZBjlkdaVYxPNO8a7OmzDbOH9FMQ1a58j7Xb21UfRU29KcEEU3VTHk+Cvrft/BNv0gpWJMiiZ/f4w0TqSP0gLA==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.16.tgz", + "integrity": "sha512-TIZTRojVBBzdgChY3UOG7BlPhqJz08AL7jdgeeu+kiObWMFzGnQD7BgBBkWRwOtKR1i2TNlO7YK6m4zxVjjPRQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.16.tgz", + "integrity": "sha512-UPeRuFKCCJYpBbIdczKyHLAIU31GEm0dZl1eMrdYeXDH+SJZh/i+2cAmD3A1Wip9pIc5Sc6Kc5cFUrPXtR0XHA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.16.tgz", + "integrity": "sha512-io6yShgIEgVUhExJejJ21xvO5QtrbiSeI7vYUnr7l+v/O9t6IowyhdiYnyivX2X5ysOVHAuyHW+Wyi7DNhdw6Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.16.tgz", + "integrity": "sha512-WhlGeAHNbSdG/I2gqX2RK2gfgSNwyJuCiFHMc8s3GNEMMHUI109+VMBfhVqRb0ZGzEeRiibi8dItR3ws3Lk+cA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.16.tgz", + "integrity": "sha512-gHRReYsJtViir63bXKoFaQ4pgTyah4ruiMRQ6im9YZuv+gp3UFJkNTY4sFA73YDynmXZA6hi45en4BGhNOJUsw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.16.tgz", + "integrity": "sha512-mfiiBkxEbUHvi+v0P+TS7UnA9TeGXR48aK4XHkTj0ZwOijxexgMF01UDFaBX7Q6CQsB0d+MFNv9IiXbIHTNd4g==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.16.tgz", + "integrity": "sha512-n8zK1YRDGLRZfVcswcDMDM0j2xKYLNXqei217a4GyBxHIuPMGrrVuJ+Ijfpr0Kufcm7C1k/qaIrGy6eG7wvgmA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.16.tgz", + "integrity": "sha512-lEEfkfsUbo0xC47eSTBqsItXDSzwzwhKUSsVaVjVji07t8+6KA5INp2rN890dHZeueXJAI8q0tEIfbwVRYf6Ew==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.16.tgz", + "integrity": "sha512-jlRjsuvG1fgGwnE8Afs7xYDnGz0dBgTNZfgCK6TlvPH3Z13/P5pi6I57vyLE8qZYLrGVtwcm9UbUx1/mZ8Ukag==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.16.tgz", + "integrity": "sha512-TzoU2qwVe2boOHl/3KNBUv2PNUc38U0TNnzqOAcgPiD/EZxT2s736xfC2dYQbszAwo4MKzzwBV0iHjhfjxMimg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.16.tgz", + "integrity": "sha512-B8b7W+oo2yb/3xmwk9Vc99hC9bNolvqjaTZYEfMQhzdpBsjTvZBlXQ/teUE55Ww6sg//wlcDjOaqldOKyigWdA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.16.tgz", + "integrity": "sha512-xJ7OH/nanouJO9pf03YsL9NAFQBHd8AqfrQd7Pf5laGyyTt/gToul6QYOA/i5i/q8y9iaM5DQFNTgpi995VkOg==", "dev": true, "optional": true }, @@ -17579,6 +21319,12 @@ "lodash": "^4.17.21" } }, + "@ljharb/has-package-exports-patterns": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@ljharb/has-package-exports-patterns/-/has-package-exports-patterns-0.0.2.tgz", + "integrity": "sha512-4/RWEeXDO6bocPONheFe6gX/oQdP/bEpv0oL4HqjPP5DCenBSt0mHgahppY49N0CpsaqffdwPq+TlX9CYOq2Dw==", + "dev": true + }, "@next/env": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.2.tgz", @@ -17751,6 +21497,54 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.2.0.tgz", "integrity": "sha512-BNKB9fiYVghALJzCuWO3eNYfdTExPVK4ykrtmfNfy0A6UWYhOYjGMXifUmkunDJNL8ju9tBobo8jF0WR9zGy1Q==" }, + "@pkgr/utils": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", + "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "is-glob": "^4.0.3", + "open": "^8.4.0", + "picocolors": "^1.0.0", + "tiny-glob": "^0.2.9", + "tslib": "^2.4.0" + }, + "dependencies": { + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + } + } + }, "@pnpm/network.ca-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", @@ -17949,6 +21743,47 @@ "integrity": "sha512-Z93wDSYW9aMgPR5t+7ouwTvy91Vp3M0Snh4Pd3tf+caSAq5bXZaGnnH9CDbjrwgmfDkRIX0Dx8GvSDgwuoaxoA==", "dev": true }, + "@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, "@types/body-parser": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", @@ -18034,6 +21869,15 @@ "@types/node": "*" } }, + "@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dev": true, + "requires": { + "@types/ms": "*" + } + }, "@types/duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", @@ -18042,6 +21886,12 @@ "@types/node": "*" } }, + "@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -18091,6 +21941,21 @@ "@types/node": "*" } }, + "@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, + "@types/html-escaper": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/html-escaper/-/html-escaper-3.0.0.tgz", + "integrity": "sha512-OcJcvP3Yk8mjYwf/IdXZtTE1tb/u0WF0qa29ER07ZHCYUBZXSN29Z1mBS+/96+kNMGTFUAbSz9X+pHmHpZrTCw==", + "dev": true + }, "@types/inquirer": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.5.tgz", @@ -18112,6 +21977,12 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "@types/json5": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.30.tgz", + "integrity": "sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==", + "dev": true + }, "@types/jsonwebtoken": { "version": "8.3.8", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.3.8.tgz", @@ -18176,6 +22047,15 @@ } } }, + "@types/mdast": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz", + "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, "@types/mdurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", @@ -18214,6 +22094,12 @@ "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", "dev": true }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, "@types/multer": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.4.tgz", @@ -18223,6 +22109,15 @@ "@types/express": "*" } }, + "@types/nlcst": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-1.0.0.tgz", + "integrity": "sha512-3TGCfOcy8R8mMQ4CNSNOe3PG66HttvjcLzCoOpvXvDtfWOTi+uT/rxeOKm/qEwbM4SNe1O/PjdiBK2YcTjU4OQ==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, "@types/node": { "version": "14.18.36", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz", @@ -18263,6 +22158,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, "@types/prettier": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", @@ -18350,6 +22251,12 @@ } } }, + "@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "@types/retry": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", @@ -18504,6 +22411,12 @@ "integrity": "sha512-tl34wMtk3q+fSdRSJ+N83f47IyXLXPPuLjHm7cmAx0fE2Wml2TZCQV3FmQdSR5J6UEGV3qafG054e0cVVFCqPA==", "dev": true }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, "@types/universal-analytics": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/@types/universal-analytics/-/universal-analytics-0.4.5.tgz", @@ -18544,6 +22457,12 @@ "@types/node": "*" } }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, "@types/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", @@ -18762,6 +22681,39 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "@vscode/emmet-helper": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.8.6.tgz", + "integrity": "sha512-IIB8jbiKy37zN8bAIHx59YmnIelY78CGHtThnibD/d3tQOKRY83bYVi9blwmZVUZh6l9nfkYH3tvReaiNxY9EQ==", + "dev": true, + "requires": { + "emmet": "^2.3.0", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^2.1.2" + }, + "dependencies": { + "jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true + }, + "vscode-uri": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==", + "dev": true + } + } + }, + "@vscode/l10n": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.11.tgz", + "integrity": "sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -18786,9 +22738,9 @@ } }, "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" }, "acorn-jsx": { "version": "5.3.2", @@ -18928,6 +22880,12 @@ } } }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -19068,72 +23026,418 @@ "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", "dev": true }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + } + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, + "as-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/as-array/-/as-array-2.0.0.tgz", + "integrity": "sha1-TwSAXYf4/OjlEbwhCPjl46KH1Uc=" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "requires": { + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "astro": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/astro/-/astro-2.2.3.tgz", + "integrity": "sha512-Pd67ZBoYxqeyHCZ0UpdmDZYNgcs7JTwc0NMzUScrH4y2hjSY4S8iwmNUtd9pf65gkxMpEbqfvQj06kLzgi4HZg==", + "dev": true, + "requires": { + "@astrojs/compiler": "^1.3.1", + "@astrojs/language-server": "^0.28.3", + "@astrojs/markdown-remark": "^2.1.3", + "@astrojs/telemetry": "^2.1.0", + "@astrojs/webapi": "^2.1.0", + "@babel/core": "^7.18.2", + "@babel/generator": "^7.18.2", + "@babel/parser": "^7.18.4", + "@babel/plugin-transform-react-jsx": "^7.17.12", + "@babel/traverse": "^7.18.2", + "@babel/types": "^7.18.4", + "@types/babel__core": "^7.1.19", + "@types/yargs-parser": "^21.0.0", + "acorn": "^8.8.1", + "boxen": "^6.2.1", + "chokidar": "^3.5.3", + "ci-info": "^3.3.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^0.5.0", + "debug": "^4.3.4", + "deepmerge-ts": "^4.2.2", + "devalue": "^4.2.0", + "diff": "^5.1.0", + "es-module-lexer": "^1.1.0", + "estree-walker": "^3.0.1", + "execa": "^6.1.0", + "fast-glob": "^3.2.11", + "github-slugger": "^2.0.0", + "gray-matter": "^4.0.3", + "html-escaper": "^3.0.3", + "kleur": "^4.1.4", + "magic-string": "^0.27.0", + "mime": "^3.0.0", + "ora": "^6.1.0", + "path-to-regexp": "^6.2.1", + "preferred-pm": "^3.0.3", + "prompts": "^2.4.2", + "rehype": "^12.0.1", + "semver": "^7.3.8", + "server-destroy": "^1.0.1", + "shiki": "^0.11.1", + "slash": "^4.0.0", + "string-width": "^5.1.2", + "strip-ansi": "^7.0.1", + "supports-esm": "^1.0.0", + "tsconfig-resolver": "^3.0.1", + "typescript": "*", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.2", + "vite": "^4.2.1", + "vitefu": "^0.2.4", + "yargs-parser": "^21.0.1", + "zod": "^3.17.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "dev": true, + "requires": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true + }, + "cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "requires": { + "restore-cursor": "^4.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "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 + }, + "html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "dev": true + }, + "is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true + }, + "is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true + }, + "log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "requires": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "dependencies": { + "chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true + } + } + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "ora": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.0.tgz", + "integrity": "sha512-1/D8uRFY0ay2kgBpmAwmSA404w4OoPVhHMqRqtjvrcK/dnzcEZxMJ+V4DUbyICu8IIVRclHcOf5wlD1tMY4GUQ==", + "dev": true, + "requires": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true + } + } + }, + "path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true + }, + "restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "semver": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", + "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + }, + "widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "requires": { + "string-width": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + } + } + }, + "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==", "dev": true - } - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" - }, - "as-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/as-array/-/as-array-2.0.0.tgz", - "integrity": "sha1-TwSAXYf4/OjlEbwhCPjl46KH1Uc=" - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "requires": { - "tslib": "^2.0.1" - }, - "dependencies": { - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } }, @@ -19193,6 +23497,12 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -19441,6 +23751,15 @@ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -19609,6 +23928,12 @@ "lodash": "^4.17.15" } }, + "ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true + }, "chai": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", @@ -19651,6 +23976,24 @@ "supports-color": "^5.3.0" } }, + "character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true + }, + "character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true + }, + "character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -19832,6 +24175,12 @@ "delayed-stream": "~1.0.0" } }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true + }, "commander": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.0.1.tgz", @@ -19843,6 +24192,12 @@ "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", "dev": true }, + "common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -20180,6 +24535,15 @@ } } }, + "decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dev": true, + "requires": { + "character-entities": "^2.0.0" + } + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -20204,6 +24568,12 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "deepmerge-ts": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-4.3.0.tgz", + "integrity": "sha512-if3ZYdkD2dClhnXR5reKtG98cwyaRT1NeugQoAPTTfsOpV9kqyeiBF9Qa5RHjemb3KzD5ulqygv6ED3t5j9eJw==", + "dev": true + }, "default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", @@ -20221,6 +24591,12 @@ "clone": "^1.0.2" } }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, "degenerator": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz", @@ -20249,11 +24625,23 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "devOptional": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "devalue": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.0.tgz", + "integrity": "sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA==", + "dev": true + }, "devtools-protocol": { "version": "0.0.1056733", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1056733.tgz", @@ -20295,6 +24683,12 @@ "path-type": "^4.0.0" } }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -20304,6 +24698,12 @@ "esutils": "^2.0.2" } }, + "dset": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", + "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==", + "dev": true + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -20339,6 +24739,12 @@ "stream-shift": "^1.0.0" } }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -20367,6 +24773,16 @@ "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", "dev": true }, + "emmet": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.2.tgz", + "integrity": "sha512-YgmsMkhUgzhJMgH5noGudfxqrQn1bapvF0y7C1e7A0jWFImsRrrvVslzyZz0919NED/cjFOpVWx7c973V+2S/w==", + "dev": true, + "requires": { + "@emmetio/abbreviation": "^2.3.1", + "@emmetio/css-abbreviation": "^2.1.6" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -20410,228 +24826,94 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { - "once": "^1.4.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true, - "optional": true - }, - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" - }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "optional": true - }, - "env-variable": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", - "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "optional": true - }, - "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, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", - "dev": true - }, - "esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.15.18", - "@esbuild/linux-loong64": "0.15.18", - "esbuild-android-64": "0.15.18", - "esbuild-android-arm64": "0.15.18", - "esbuild-darwin-64": "0.15.18", - "esbuild-darwin-arm64": "0.15.18", - "esbuild-freebsd-64": "0.15.18", - "esbuild-freebsd-arm64": "0.15.18", - "esbuild-linux-32": "0.15.18", - "esbuild-linux-64": "0.15.18", - "esbuild-linux-arm": "0.15.18", - "esbuild-linux-arm64": "0.15.18", - "esbuild-linux-mips64le": "0.15.18", - "esbuild-linux-ppc64le": "0.15.18", - "esbuild-linux-riscv64": "0.15.18", - "esbuild-linux-s390x": "0.15.18", - "esbuild-netbsd-64": "0.15.18", - "esbuild-openbsd-64": "0.15.18", - "esbuild-sunos-64": "0.15.18", - "esbuild-windows-32": "0.15.18", - "esbuild-windows-64": "0.15.18", - "esbuild-windows-arm64": "0.15.18" - } - }, - "esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "dev": true, - "optional": true + "once": "^1.4.0" + } }, - "esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true, "optional": true }, - "esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "dev": true, - "optional": true + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" }, - "esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "dev": true, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "optional": true }, - "esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "dev": true, - "optional": true + "env-variable": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", + "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" }, - "esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "dev": true, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "optional": true }, - "esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "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, - "optional": true + "requires": { + "is-arrayish": "^0.2.1" + } }, - "esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "dev": true, - "optional": true + "es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true }, - "esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "dev": true, - "optional": true + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true }, - "esbuild-windows-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", - "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", - "dev": true, - "optional": true + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true }, - "esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "dev": true, - "optional": true + "esbuild": { + "version": "0.17.16", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.16.tgz", + "integrity": "sha512-aeSuUKr9aFVY9Dc8ETVELGgkj4urg5isYx8pLf4wlGgB0vTFjxJQdHnNH6Shmx4vYYrOTLCHtRI5i1XZ9l2Zcg==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.16", + "@esbuild/android-arm64": "0.17.16", + "@esbuild/android-x64": "0.17.16", + "@esbuild/darwin-arm64": "0.17.16", + "@esbuild/darwin-x64": "0.17.16", + "@esbuild/freebsd-arm64": "0.17.16", + "@esbuild/freebsd-x64": "0.17.16", + "@esbuild/linux-arm": "0.17.16", + "@esbuild/linux-arm64": "0.17.16", + "@esbuild/linux-ia32": "0.17.16", + "@esbuild/linux-loong64": "0.17.16", + "@esbuild/linux-mips64el": "0.17.16", + "@esbuild/linux-ppc64": "0.17.16", + "@esbuild/linux-riscv64": "0.17.16", + "@esbuild/linux-s390x": "0.17.16", + "@esbuild/linux-x64": "0.17.16", + "@esbuild/netbsd-x64": "0.17.16", + "@esbuild/openbsd-x64": "0.17.16", + "@esbuild/sunos-x64": "0.17.16", + "@esbuild/win32-arm64": "0.17.16", + "@esbuild/win32-ia32": "0.17.16", + "@esbuild/win32-x64": "0.17.16" + } }, "escalade": { "version": "3.1.1", @@ -21050,6 +25332,15 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" }, + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } + }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -21065,11 +25356,57 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, "events-listener": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/events-listener/-/events-listener-1.1.0.tgz", "integrity": "sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g==" }, + "execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "dependencies": { + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + } + } + }, "exegesis": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/exegesis/-/exegesis-4.1.0.tgz", @@ -21210,6 +25547,15 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -21434,6 +25780,16 @@ "path-exists": "^4.0.0" } }, + "find-yarn-workspace-root2": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", + "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", + "dev": true, + "requires": { + "micromatch": "^4.0.2", + "pkg-dir": "^4.2.0" + } + }, "firebase": { "version": "9.16.0", "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.16.0.tgz", @@ -21806,6 +26162,12 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "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 + }, "get-uri": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz", @@ -21852,6 +26214,12 @@ "assert-plus": "^1.0.0" } }, + "github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "dev": true + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -22460,6 +26828,18 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + } + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -22531,6 +26911,15 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-package-exports": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/has-package-exports/-/has-package-exports-1.3.0.tgz", + "integrity": "sha512-e9OeXPQnmPhYoJ63lXC4wWe34TxEGZDZ3OQX9XRqp2VwsfLl3bQBy7VehLnd34g3ef8CmYlBLGqEMKXuz8YazQ==", + "dev": true, + "requires": { + "@ljharb/has-package-exports-patterns": "^0.0.2" + } + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -22565,6 +26954,101 @@ } } }, + "hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + } + }, + "hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0" + } + }, + "hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + } + }, + "hast-util-to-html": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^7.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + } + }, + "hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + } + }, + "hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "dev": true + }, + "hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -22589,6 +27073,12 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "dev": true + }, "http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -22690,6 +27180,12 @@ } } }, + "human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true + }, "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -22739,6 +27235,12 @@ "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" }, + "import-meta-resolve": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.2.2.tgz", + "integrity": "sha512-f8KcQ1D80V7RnqVm+/lirO9zkOxjGxhaTC1IPrBGd3MEfNgmNG67tSUO9gTi2F3Blr2Az6g1vocaxzkVnWl9MA==", + "dev": true + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -22890,6 +27392,12 @@ "binary-extensions": "^2.0.0" } }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, "is-ci": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", @@ -22907,6 +27415,18 @@ "has": "^1.0.3" } }, + "is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -23607,6 +28127,26 @@ "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" }, + "load-yaml-file": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", + "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.13.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -23782,6 +28322,12 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -23828,6 +28374,15 @@ } } }, + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -23955,6 +28510,12 @@ "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", "requires": {} }, + "markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "dev": true + }, "marked": { "version": "4.2.12", "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", @@ -23965,19 +28526,191 @@ "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", "requires": { - "ansi-escapes": "^5.0.0", - "cardinal": "^2.1.1", - "chalk": "^5.0.0", - "cli-table3": "^0.6.1", - "node-emoji": "^1.11.0", - "supports-hyperlinks": "^2.2.0" - }, - "dependencies": { - "chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==" - } + "ansi-escapes": "^5.0.0", + "cardinal": "^2.1.1", + "chalk": "^5.0.0", + "cli-table3": "^0.6.1", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.2.0" + }, + "dependencies": { + "chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==" + } + } + }, + "mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-find-and-replace": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + } + } + }, + "mdast-util-from-markdown": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz", + "integrity": "sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + } + }, + "mdast-util-gfm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", + "dev": true, + "requires": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + } + }, + "mdast-util-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + } + }, + "mdast-util-gfm-footnote": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + } + }, + "mdast-util-gfm-strikethrough": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + } + }, + "mdast-util-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + } + }, + "mdast-util-gfm-task-list-item": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + } + }, + "mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + } + }, + "mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0" } }, "mdurl": { @@ -23995,6 +28728,12 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, + "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 + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -24006,6 +28745,350 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, + "micromark": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", + "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", + "dev": true, + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "micromark-core-commonmark": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "dev": true, + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", + "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", + "dev": true, + "requires": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-extension-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-footnote": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.0.tgz", + "integrity": "sha512-RWYce7j8+c0n7Djzv5NzGEGitNNYO3uj+h/XYMdS/JinH1Go+/Qkomg/rfxExFzYTiydaV6GLeffGO5qcJbMPA==", + "dev": true, + "requires": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-strikethrough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.5.tgz", + "integrity": "sha512-X0oI5eYYQVARhiNfbETy7BfLSmSilzN1eOuoRnrf9oUNsPRrWOAe9UqSizgw1vNxQBfOwL+n2610S3bYjVNi7w==", + "dev": true, + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-table": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", + "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", + "dev": true, + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-extension-gfm-task-list-item": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.4.tgz", + "integrity": "sha512-9XlIUUVnYXHsFF2HZ9jby4h3npfX10S1coXTnV035QGPgrtNYQq3J6IfIvcCIUAJrrqBVi5BqA/LmaOMJqPwMQ==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-label": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-title": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "dev": true, + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-decode-string": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "dev": true, + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-encode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "dev": true + }, + "micromark-util-html-tag-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", + "dev": true + }, + "micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "dev": true, + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", + "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-subtokenize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "dev": true, + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-util-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", + "dev": true + }, + "micromark-util-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", + "dev": true + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -24410,6 +29493,15 @@ } } }, + "nlcst-to-string": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz", + "integrity": "sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==", + "dev": true, + "requires": { + "@types/nlcst": "^1.0.0" + } + }, "nock": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.5.tgz", @@ -24583,6 +29675,23 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, + "npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + }, + "dependencies": { + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + } + } + }, "npmlog": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", @@ -25230,6 +30339,23 @@ "lines-and-columns": "^1.1.6" } }, + "parse-latin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-5.0.1.tgz", + "integrity": "sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==", + "dev": true, + "requires": { + "nlcst-to-string": "^3.0.0", + "unist-util-modify-children": "^3.0.0", + "unist-util-visit-children": "^2.0.0" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -25296,6 +30422,12 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -25388,6 +30520,18 @@ } } }, + "preferred-pm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz", + "integrity": "sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==", + "dev": true, + "requires": { + "find-up": "^5.0.0", + "find-yarn-workspace-root2": "1.2.16", + "path-exists": "^4.0.0", + "which-pm": "2.0.0" + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -25408,6 +30552,32 @@ "fast-diff": "^1.1.2" } }, + "prettier-plugin-astro": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.7.2.tgz", + "integrity": "sha512-mmifnkG160BtC727gqoimoxnZT/dwr8ASxpoGGl6EHevhfblSOeu+pwH1LAm5Qu1MynizktztFujHHaijLCkww==", + "dev": true, + "requires": { + "@astrojs/compiler": "^0.31.3", + "prettier": "^2.7.1", + "sass-formatter": "^0.7.5", + "synckit": "^0.8.4" + }, + "dependencies": { + "@astrojs/compiler": { + "version": "0.31.4", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-0.31.4.tgz", + "integrity": "sha512-6bBFeDTtPOn4jZaiD3p0f05MEGQL9pw2Zbfj546oFETNmjJFWO3nzHz6/m+P53calknCvyVzZ5YhoBLIvzn5iw==", + "dev": true + } + } + }, + "prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -25456,12 +30626,36 @@ } } }, + "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, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "dependencies": { + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + } + } + }, "propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, + "property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", + "dev": true + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -25923,16 +31117,108 @@ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", "requires": { - "rc": "^1.2.8" + "rc": "^1.2.8" + } + }, + "rehype": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-12.0.1.tgz", + "integrity": "sha512-ey6kAqwLM3X6QnMDILJthGvG1m1ULROS9NT4uG9IDCuv08SFyLlreSuvOa//DgEvbXx62DS6elGVqusWhRUbgw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "rehype-parse": "^8.0.0", + "rehype-stringify": "^9.0.0", + "unified": "^10.0.0" + } + }, + "rehype-parse": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz", + "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^7.0.0", + "parse5": "^6.0.0", + "unified": "^10.0.0" + } + }, + "rehype-raw": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", + "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "hast-util-raw": "^7.2.0", + "unified": "^10.0.0" + } + }, + "rehype-stringify": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.3.tgz", + "integrity": "sha512-kWiZ1bgyWlgOxpqD5HnxShKAdXtb2IUljn3hQAhySeak6IOQPPt6DeGnsIh4ixm7yKJWzm8TXFuC/lPfcWHJqw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "hast-util-to-html": "^8.0.0", + "unified": "^10.0.0" + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + } + }, + "remark-parse": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + } + }, + "remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" } }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "remark-smartypants": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-2.0.0.tgz", + "integrity": "sha512-Rc0VDmr/yhnMQIz8n2ACYXlfw/P/XZev884QU1I5u+5DgJls32o97Vc1RbK3pfumLsJomS2yy8eT4Fxj/2MDVA==", "dev": true, "requires": { - "es6-error": "^4.0.1" + "retext": "^8.1.0", + "retext-smartypants": "^5.1.0", + "unist-util-visit": "^4.1.0" } }, "request": { @@ -26034,6 +31320,53 @@ "signal-exit": "^3.0.2" } }, + "retext": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-8.1.0.tgz", + "integrity": "sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==", + "dev": true, + "requires": { + "@types/nlcst": "^1.0.0", + "retext-latin": "^3.0.0", + "retext-stringify": "^3.0.0", + "unified": "^10.0.0" + } + }, + "retext-latin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-3.1.0.tgz", + "integrity": "sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==", + "dev": true, + "requires": { + "@types/nlcst": "^1.0.0", + "parse-latin": "^5.0.0", + "unherit": "^3.0.0", + "unified": "^10.0.0" + } + }, + "retext-smartypants": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-5.2.0.tgz", + "integrity": "sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==", + "dev": true, + "requires": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "retext-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-3.1.0.tgz", + "integrity": "sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==", + "dev": true, + "requires": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0" + } + }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -26078,9 +31411,9 @@ } }, "rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.2.tgz", + "integrity": "sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==", "dev": true, "requires": { "fsevents": "~2.3.2" @@ -26136,6 +31469,21 @@ } } }, + "s.color": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz", + "integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==", + "dev": true + }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "requires": { + "mri": "^1.1.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -26146,6 +31494,15 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sass-formatter": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.6.tgz", + "integrity": "sha512-hXdxU6PCkiV3XAiSnX+XLqz2ohHoEnVUlrd8LEVMAI80uB1+OTScIkH9n6qQwImZpTye1r1WG1rbGUteHNhoHg==", + "dev": true, + "requires": { + "suf-log": "^2.5.3" + } + }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -26156,6 +31513,16 @@ "loose-envify": "^1.1.0" } }, + "section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -26246,6 +31613,12 @@ "send": "0.18.0" } }, + "server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -26275,6 +31648,17 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, + "shiki": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.11.1.tgz", + "integrity": "sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==", + "dev": true, + "requires": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "^6.0.0" + } + }, "should": { "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", @@ -26403,6 +31787,12 @@ "dev": true, "requires": {} }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -26477,6 +31867,12 @@ "source-map": "^0.6.0" } }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true + }, "spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -26583,6 +31979,38 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "requires": { + "bl": "^5.0.0" + }, + "dependencies": { + "bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, "stream-chain": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.4.tgz", @@ -26611,6 +32039,12 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -26629,6 +32063,16 @@ "strip-ansi": "^6.0.1" } }, + "stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dev": true, + "requires": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -26650,6 +32094,18 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -26680,6 +32136,15 @@ "client-only": "0.0.1" } }, + "suf-log": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz", + "integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==", + "dev": true, + "requires": { + "s.color": "0.0.15" + } + }, "superagent": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.3.tgz", @@ -26806,6 +32271,15 @@ "has-flag": "^3.0.0" } }, + "supports-esm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-esm/-/supports-esm-1.0.0.tgz", + "integrity": "sha512-96Am8CDqUaC0I2+C/swJ0yEvM8ZnGn4unoers/LSdE4umhX7mELzqyLzx3HnZAluq5PXIsGMKqa7NkqaeHMPcg==", + "dev": true, + "requires": { + "has-package-exports": "^1.1.0" + } + }, "supports-hyperlinks": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", @@ -26895,6 +32369,24 @@ } } }, + "synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "requires": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + } + } + }, "tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", @@ -27154,6 +32646,12 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" }, + "trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -27165,6 +32663,12 @@ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, + "trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "dev": true + }, "ts-is-present": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ts-is-present/-/ts-is-present-1.1.5.tgz", @@ -27200,6 +32704,28 @@ } } }, + "tsconfig-resolver": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tsconfig-resolver/-/tsconfig-resolver-3.0.1.tgz", + "integrity": "sha512-ZHqlstlQF449v8glscGRXzL6l2dZvASPCdXJRWG4gHEZlUVx2Jtmr+a2zeVG4LCsKhDXKRj5R3h0C/98UcVAQg==", + "dev": true, + "requires": { + "@types/json5": "^0.0.30", + "@types/resolve": "^1.17.0", + "json5": "^2.1.3", + "resolve": "^1.17.0", + "strip-bom": "^4.0.0", + "type-fest": "^0.13.1" + }, + "dependencies": { + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + } + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -27339,6 +32865,44 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" }, + "undici": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.2.tgz", + "integrity": "sha512-f6pTQ9RF4DQtwoWSaC42P/NKlUjvezVvd9r155ohqkwFNRyBKM3f3pcty3ouusefNRyM25XhIQEbeQ46sZDJfQ==", + "dev": true, + "requires": { + "busboy": "^1.6.0" + } + }, + "unherit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-3.0.1.tgz", + "integrity": "sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==", + "dev": true + }, + "unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true + } + } + }, "unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -27357,6 +32921,79 @@ "imurmurhash": "^0.1.4" } }, + "unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "dev": true + }, + "unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-modify-children": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-3.1.1.tgz", + "integrity": "sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "array-iterate": "^2.0.0" + } + }, + "unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + } + }, + "unist-util-visit-children": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-2.0.2.tgz", + "integrity": "sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + }, "universal-analytics": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.5.3.tgz", @@ -27590,6 +33227,18 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, + "uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + } + }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -27626,29 +33275,61 @@ "extsprintf": "^1.2.0" } }, + "vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + } + }, + "vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + } + }, + "vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + } + }, "vite": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.3.tgz", - "integrity": "sha512-/3XWiktaopByM5bd8dqvHxRt5EEgRikevnnrpND0gRfNkrMrPaGGexhtLCzv15RcCMtV2CLw+BPas8YFeSG0KA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==", "dev": true, "requires": { - "esbuild": "^0.15.6", + "esbuild": "^0.17.5", "fsevents": "~2.3.2", - "postcss": "^8.4.16", + "postcss": "^8.4.21", "resolve": "^1.22.1", - "rollup": "~2.78.0" + "rollup": "^3.18.0" }, "dependencies": { "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true }, "postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "dev": true, "requires": { "nanoid": "^3.3.4", @@ -27658,6 +33339,13 @@ } } }, + "vitefu": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", + "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", + "dev": true, + "requires": {} + }, "vm2": { "version": "3.9.11", "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.11.tgz", @@ -27667,6 +33355,85 @@ "acorn-walk": "^8.2.0" } }, + "vscode-css-languageservice": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.2.4.tgz", + "integrity": "sha512-9UG0s3Ss8rbaaPZL1AkGzdjrGY8F+P+Ne9snsrvD9gxltDGhsn8C2dQpqQewHrMW37OvlqJoI8sUU2AWDb+qNw==", + "dev": true, + "requires": { + "@vscode/l10n": "^0.0.11", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3", + "vscode-uri": "^3.0.7" + } + }, + "vscode-html-languageservice": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.0.4.tgz", + "integrity": "sha512-tvrySfpglu4B2rQgWGVO/IL+skvU7kBkQotRlxA7ocSyRXOZUd6GA13XHkxo8LPe07KWjeoBlN1aVGqdfTK4xA==", + "dev": true, + "requires": { + "@vscode/l10n": "^0.0.11", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.2", + "vscode-uri": "^3.0.7" + } + }, + "vscode-jsonrpc": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz", + "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==", + "dev": true + }, + "vscode-languageserver": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz", + "integrity": "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==", + "dev": true, + "requires": { + "vscode-languageserver-protocol": "3.17.3" + } + }, + "vscode-languageserver-protocol": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz", + "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==", + "dev": true, + "requires": { + "vscode-jsonrpc": "8.1.0", + "vscode-languageserver-types": "3.17.3" + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", + "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==", + "dev": true + }, + "vscode-languageserver-types": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==", + "dev": true + }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz", + "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", + "dev": true + }, + "vscode-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==", + "dev": true + }, "wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -27675,6 +33442,12 @@ "defaults": "^1.0.3" } }, + "web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -27720,6 +33493,22 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-pm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", + "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", + "dev": true, + "requires": { + "load-yaml-file": "^0.2.0", + "path-exists": "^4.0.0" + } + }, + "which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "dev": true + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -27963,6 +33752,18 @@ "compress-commons": "^4.1.0", "readable-stream": "^3.6.0" } + }, + "zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "dev": true + }, + "zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true } } } diff --git a/package.json b/package.json index c1ca887fcec..fa812636bb9 100644 --- a/package.json +++ b/package.json @@ -174,6 +174,7 @@ "@types/express-serve-static-core": "^4.17.8", "@types/fs-extra": "^9.0.13", "@types/glob": "^7.1.1", + "@types/html-escaper": "^3.0.0", "@types/inquirer": "^8.1.3", "@types/js-yaml": "^3.12.2", "@types/jsonwebtoken": "^8.3.8", @@ -210,6 +211,7 @@ "@types/ws": "^7.2.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", + "astro": "^2.2.3", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "eslint": "^8.6.0", @@ -240,6 +242,6 @@ "ts-node": "^10.4.0", "typescript": "^4.5.4", "typescript-json-schema": "^0.50.1", - "vite": "^3.1.0" + "vite": "^4.2.1" } } diff --git a/src/frameworks/astro/index.ts b/src/frameworks/astro/index.ts new file mode 100644 index 00000000000..4f98453d155 --- /dev/null +++ b/src/frameworks/astro/index.ts @@ -0,0 +1,81 @@ +import { sync as spawnSync, spawn } from "cross-spawn"; +import { copy, existsSync } from "fs-extra"; +import { join } from "path"; +import { + BuildResult, + Discovery, + FrameworkType, + SupportLevel, + findDependency, + getNodeModuleBin, +} from ".."; +import { FirebaseError } from "../../error"; +import { readJSON, simpleProxy, warnIfCustomBuildScript } from "../utils"; +import { getBootstrapScript, getConfig } from "./utils"; + +export const name = "Astro"; +export const support = SupportLevel.Experimental; +export const type = FrameworkType.MetaFramework; + +function getAstroVersion(cwd: string): string | undefined { + return findDependency("astro", { cwd, depth: 0, omitDev: false })?.version; +} + +export async function discover(dir: string): Promise { + if (!existsSync(join(dir, "package.json"))) return; + if (!getAstroVersion(dir)) return; + const { output, publicDir: publicDirectory } = await getConfig(dir); + return { + mayWantBackend: output === "server", + publicDirectory, + }; +} + +const DEFAULT_BUILD_SCRIPT = ["astro build"]; + +export async function build(cwd: string): Promise { + const cli = getNodeModuleBin("astro", cwd); + await warnIfCustomBuildScript(cwd, name, DEFAULT_BUILD_SCRIPT); + const { output, adapter } = await getConfig(cwd); + if (output === "server" && adapter?.name !== "@astrojs/node") { + throw new FirebaseError( + "Deploying an Astro application with SSR on Firebase Hosting requires the @astrojs/node adapter." + ); + } + const build = spawnSync(cli, ["build"], { cwd, stdio: "inherit" }); + if (build.status) throw new FirebaseError("Unable to build your Astro app"); + return { wantsBackend: output === "server" }; +} + +export async function ɵcodegenPublicDirectory(root: string, dest: string) { + const { outDir, output } = await getConfig(root); + // output: "server" in astro.config builds "client" and "server" folders, otherwise assets are in top-level outDir + const assetPath = join(root, outDir, output === "server" ? "client" : ""); + await copy(assetPath, dest); +} + +export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) { + const { outDir } = await getConfig(sourceDir); + const packageJson = await readJSON(join(sourceDir, "package.json")); + await copy(join(sourceDir, outDir, "server"), join(destDir)); + return { + packageJson, + bootstrapScript: getBootstrapScript(), + }; +} + +export async function getDevModeHandle(cwd: string) { + const host = new Promise((resolve) => { + const cli = getNodeModuleBin("astro", cwd); + const serve = spawn(cli, ["dev"], { cwd }); + serve.stdout.on("data", (data: any) => { + process.stdout.write(data); + const match = data.toString().match(/(http:\/\/.+:\d+)/); + if (match) resolve(match[1]); + }); + serve.stderr.on("data", (data: any) => { + process.stderr.write(data); + }); + }); + return simpleProxy(await host); +} diff --git a/src/frameworks/astro/utils.ts b/src/frameworks/astro/utils.ts new file mode 100644 index 00000000000..f115a1fb9e2 --- /dev/null +++ b/src/frameworks/astro/utils.ts @@ -0,0 +1,24 @@ +import { dirname, join, relative } from "path"; + +const { dynamicImport } = require(true && "../../dynamicImport"); + +export function getBootstrapScript() { + // `astro build` with node adapter in middleware mode will generate a middleware at entry.mjs + // need to convert the export to `handle` to work with express integration + return `const entry = import('./entry.mjs');\nexport const handle = async (req, res) => (await entry).handler(req, res)`; +} + +export async function getConfig(cwd: string) { + const astroDirectory = dirname(require.resolve("astro/package.json", { paths: [cwd] })); + const { openConfig }: typeof import("astro/dist/core/config/config") = await dynamicImport( + join(astroDirectory, "dist", "core", "config", "config.js") + ); + const logging: any = undefined; // TODO figure out the types here + const { astroConfig: config } = await openConfig({ cmd: "build", cwd, logging }); + return { + outDir: relative(cwd, config.outDir.pathname), + publicDir: relative(cwd, config.publicDir.pathname), + output: config.output, + adapter: config.adapter, + }; +} diff --git a/src/test/frameworks/astro/index.spec.ts b/src/test/frameworks/astro/index.spec.ts new file mode 100644 index 00000000000..3a9a80e25a6 --- /dev/null +++ b/src/test/frameworks/astro/index.spec.ts @@ -0,0 +1,351 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import { EventEmitter } from "events"; +import type { ChildProcess } from "child_process"; +import { Readable, Writable } from "stream"; +import * as crossSpawn from "cross-spawn"; +import * as fsExtra from "fs-extra"; + +import * as frameworksFunctions from "../../../frameworks"; +import * as astroUtils from "../../../frameworks/astro/utils"; +import * as frameworkUtils from "../../../frameworks/utils"; +import { + discover, + getDevModeHandle, + build, + ɵcodegenPublicDirectory, + ɵcodegenFunctionsDirectory, +} from "../../../frameworks/astro"; +import { FirebaseError } from "../../../error"; +import { join } from "path"; + +describe("Astro", () => { + describe("discovery", () => { + const cwd = "."; + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should find a static Astro app", async () => { + const publicDir = Math.random().toString(36).split(".")[1]; + sandbox + .stub(astroUtils, "getConfig") + .withArgs(cwd) + .returns( + Promise.resolve({ + outDir: "dist", + publicDir, + output: "static", + adapter: undefined, + }) + ); + sandbox + .stub(frameworksFunctions, "findDependency") + .withArgs("astro", { cwd, depth: 0, omitDev: false }) + .returns({ + version: "2.2.2", + resolved: "https://registry.npmjs.org/astro/-/astro-2.2.2.tgz", + overridden: false, + }); + expect(await discover(cwd)).to.deep.equal({ + mayWantBackend: false, + publicDirectory: publicDir, + }); + }); + + it("should find an Astro SSR app", async () => { + const publicDir = Math.random().toString(36).split(".")[1]; + sandbox + .stub(astroUtils, "getConfig") + .withArgs(cwd) + .returns( + Promise.resolve({ + outDir: "dist", + publicDir, + output: "server", + adapter: { + name: "@astrojs/node", + hooks: {}, + }, + }) + ); + sandbox + .stub(frameworksFunctions, "findDependency") + .withArgs("astro", { cwd, depth: 0, omitDev: false }) + .returns({ + version: "2.2.2", + resolved: "https://registry.npmjs.org/astro/-/astro-2.2.2.tgz", + overridden: false, + }); + expect(await discover(cwd)).to.deep.equal({ + mayWantBackend: true, + publicDirectory: publicDir, + }); + }); + }); + + describe("ɵcodegenPublicDirectory", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should copy over a static Astro app", async () => { + const root = Math.random().toString(36).split(".")[1]; + const dist = Math.random().toString(36).split(".")[1]; + const outDir = Math.random().toString(36).split(".")[1]; + sandbox + .stub(astroUtils, "getConfig") + .withArgs(root) + .returns( + Promise.resolve({ + outDir, + publicDir: "xxx", + output: "static", + adapter: undefined, + }) + ); + + const copy = sandbox.stub(fsExtra, "copy"); + + await ɵcodegenPublicDirectory(root, dist); + expect(copy.getCalls().map((it) => it.args)).to.deep.equal([[join(root, outDir), dist]]); + }); + + it("should copy over an Astro SSR app", async () => { + const root = Math.random().toString(36).split(".")[1]; + const dist = Math.random().toString(36).split(".")[1]; + const outDir = Math.random().toString(36).split(".")[1]; + sandbox + .stub(astroUtils, "getConfig") + .withArgs(root) + .returns( + Promise.resolve({ + outDir, + publicDir: "xxx", + output: "server", + adapter: { + name: "@astrojs/node", + hooks: {}, + }, + }) + ); + + const copy = sandbox.stub(fsExtra, "copy"); + + await ɵcodegenPublicDirectory(root, dist); + expect(copy.getCalls().map((it) => it.args)).to.deep.equal([ + [join(root, outDir, "client"), dist], + ]); + }); + }); + + describe("ɵcodegenFunctionsDirectory", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should copy over the cloud function", async () => { + const root = Math.random().toString(36).split(".")[1]; + const dist = Math.random().toString(36).split(".")[1]; + const outDir = Math.random().toString(36).split(".")[1]; + const packageJson = { a: Math.random().toString(36).split(".")[1] }; + sandbox + .stub(astroUtils, "getConfig") + .withArgs(root) + .returns( + Promise.resolve({ + outDir, + publicDir: "xxx", + output: "server", + adapter: { + name: "@astrojs/node", + hooks: {}, + }, + }) + ); + sandbox + .stub(frameworkUtils, "readJSON") + .withArgs(join(root, "package.json")) + .returns(Promise.resolve(packageJson)); + + const copy = sandbox.stub(fsExtra, "copy"); + const bootstrapScript = astroUtils.getBootstrapScript(); + expect(await ɵcodegenFunctionsDirectory(root, dist)).to.deep.equal({ + packageJson, + bootstrapScript, + }); + expect(copy.getCalls().map((it) => it.args)).to.deep.equal([ + [join(root, outDir, "server"), dist], + ]); + }); + }); + + describe("build", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should build an Astro SSR app", async () => { + const process = new EventEmitter() as ChildProcess; + process.stdin = new Writable(); + process.stdout = new EventEmitter() as Readable; + process.stderr = new EventEmitter() as Readable; + + const cwd = "."; + const publicDir = Math.random().toString(36).split(".")[1]; + sandbox + .stub(astroUtils, "getConfig") + .withArgs(cwd) + .returns( + Promise.resolve({ + outDir: "dist", + publicDir, + output: "server", + adapter: { + name: "@astrojs/node", + hooks: {}, + }, + }) + ); + + const cli = Math.random().toString(36).split(".")[1]; + sandbox.stub(frameworksFunctions, "getNodeModuleBin").withArgs("astro", cwd).returns(cli); + sandbox.stub(crossSpawn, "spawn").withArgs(cli, ["build"], { cwd }).returns(process); + + const result = build(cwd); + + process.emit("close"); + + expect(await result).to.deep.equal({ + wantsBackend: true, + }); + }); + + it("should fail to build an Astro SSR app w/wrong adapter", async () => { + const process = new EventEmitter() as ChildProcess; + process.stdin = new Writable(); + process.stdout = new EventEmitter() as Readable; + process.stderr = new EventEmitter() as Readable; + + const cwd = "."; + const publicDir = Math.random().toString(36).split(".")[1]; + sandbox + .stub(astroUtils, "getConfig") + .withArgs(cwd) + .returns( + Promise.resolve({ + outDir: "dist", + publicDir, + output: "server", + adapter: { + name: "EPIC FAIL", + hooks: {}, + }, + }) + ); + + const cli = Math.random().toString(36).split(".")[1]; + sandbox.stub(frameworksFunctions, "getNodeModuleBin").withArgs("astro", cwd).returns(cli); + sandbox.stub(crossSpawn, "spawn").withArgs(cli, ["build"], { cwd }).returns(process); + + await expect(build(cwd)).to.eventually.rejectedWith( + FirebaseError, + "Deploying an Astro application with SSR on Firebase Hosting requires the @astrojs/node adapter." + ); + }); + + it("should build an Astro static app", async () => { + const process = new EventEmitter() as ChildProcess; + process.stdin = new Writable(); + process.stdout = new EventEmitter() as Readable; + process.stderr = new EventEmitter() as Readable; + + const cwd = "."; + const publicDir = Math.random().toString(36).split(".")[1]; + sandbox + .stub(astroUtils, "getConfig") + .withArgs(cwd) + .returns( + Promise.resolve({ + outDir: "dist", + publicDir, + output: "static", + adapter: undefined, + }) + ); + + const cli = Math.random().toString(36).split(".")[1]; + sandbox.stub(frameworksFunctions, "getNodeModuleBin").withArgs("astro", cwd).returns(cli); + sandbox.stub(crossSpawn, "spawn").withArgs(cli, ["build"], { cwd }).returns(process); + + const result = build(cwd); + + process.emit("close"); + + expect(await result).to.deep.equal({ + wantsBackend: false, + }); + }); + }); + + describe("getDevModeHandle", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should resolve with dev server output", async () => { + const process = new EventEmitter() as ChildProcess; + process.stdin = new Writable(); + process.stdout = new EventEmitter() as Readable; + process.stderr = new EventEmitter() as Readable; + + const cli = Math.random().toString(36).split(".")[1]; + sandbox.stub(frameworksFunctions, "getNodeModuleBin").withArgs("astro", ".").returns(cli); + sandbox.stub(crossSpawn, "spawn").withArgs(cli, ["dev"], { cwd: "." }).returns(process); + + const devModeHandle = getDevModeHandle("."); + + process.stdout.emit( + "data", + ` 🚀 astro v2.2.2 started in 64ms + + ┃ Local http://localhost:3000/ + ┃ Network use --host to expose + +` + ); + + await expect(devModeHandle).eventually.be.fulfilled; + }); + }); +}); From 863eeec1d66ce0f3c173921bd58bf1855a46c078 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 13 Apr 2023 13:49:28 -0700 Subject: [PATCH 0873/1699] Fix broken Functions CLI experience for projects with incomplete GCF 2nd Gen functions. (#5684) The codebase assumes that "serviceConfig" property must be present in all 2nd Gen Functions in GCF. This is an incorrect assumption - 2nd Gen GCF functions may be missing a Cloud Run service if the build never succeeded. Also refactors `backend` module a bit to avoid calling out to Cloud Run to retrieve concurrency & cpu config - it's available on the GCF response now! Fixes https://github.com/firebase/firebase-tools/issues/4800 --- CHANGELOG.md | 1 + src/deploy/functions/backend.ts | 19 +-- src/deploy/functions/release/fabricator.ts | 36 +++-- src/gcp/cloudfunctionsv2.ts | 167 +++++++++++---------- src/metaprogramming.ts | 16 ++ src/test/deploy/functions/backend.spec.ts | 60 ++------ src/test/gcp/cloudfunctionsv2.spec.ts | 65 ++++---- 7 files changed, 181 insertions(+), 183 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2098d17044f..0f03ff58d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Default emulators:start to use fast dev-mode for Nuxt3 applications (#5551) +- Fix broken Functions CLI experience for projects with incomplete GCF 2nd Gen functions (#5684) - Disable GCF breaking change to automatically run npm build scripts as part of function deploy (#5687) - Add experimental support for deploying Astro applications to Hosting (#5527) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 464e06b8571..7932eecd67a 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -1,11 +1,10 @@ import * as gcf from "../../gcp/cloudfunctions"; import * as gcfV2 from "../../gcp/cloudfunctionsv2"; -import * as run from "../../gcp/run"; import * as utils from "../../utils"; import * as runtimes from "./runtimes"; import { FirebaseError } from "../../error"; import { Context } from "./args"; -import { flattenArray, zip } from "../../functional"; +import { flattenArray } from "../../functional"; /** Retry settings for a ScheduleSpec. */ export interface ScheduleRetryConfig { @@ -532,20 +531,8 @@ async function loadExistingBackend(ctx: Context): Promise { let gcfV2Results; try { gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId); - const runResults = await Promise.all( - gcfV2Results.functions.map((fn) => run.getService(fn.serviceConfig.service!)) - ); - for (const [apiFunction, runService] of zip(gcfV2Results.functions, runResults)) { - // I don't know why but code complete knows apiFunction is a gcfv2.CloudFunction - // and the compiler thinks it's type {}. - const endpoint = gcfV2.endpointFromFunction(apiFunction as any); - endpoint.concurrency = runService.spec.template.spec.containerConcurrency || 1; - // N.B. We don't generally do anything with multiple containers, but we - // might have to figure out WTF to do here if we're updating multiple containers - // and our only reference point is the image. Hopefully by then we'll be - // on the next gen infrastructure and have state we can refer back to. - endpoint.cpu = +runService.spec.template.spec.containers[0].resources.limits.cpu; - + for (const apiFunction of gcfV2Results.functions) { + const endpoint = gcfV2.endpointFromFunction(apiFunction); ctx.existingBackend.endpoints[endpoint.region] = ctx.existingBackend.endpoints[endpoint.region] || {}; ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint; diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 409b9cc2e4b..cb7379741b8 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -347,16 +347,24 @@ export class Fabricator { const resultFunction = await this.functionExecutor .run(async () => { const op: { name: string } = await gcfV2.createFunction(apiFunction); - return await poller.pollOperation({ + return await poller.pollOperation({ ...gcfV2PollerOptions, pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, }); }) - .catch(rethrowAs(endpoint, "create")); - - endpoint.uri = resultFunction.serviceConfig.uri; - const serviceName = resultFunction.serviceConfig.service!; + .catch(rethrowAs(endpoint, "create")); + + endpoint.uri = resultFunction.serviceConfig?.uri; + const serviceName = resultFunction.serviceConfig?.service; + if (!serviceName) { + logger.debug("Result function unexpectedly didn't have a service name."); + utils.logLabeledWarning( + "functions", + "Updated function is not associated with a service. This deployment is in an unexpected state - please re-deploy your functions." + ); + return; + } if (backend.isHttpsTriggered(endpoint)) { const invoker = endpoint.httpsTrigger.invoker || ["public"]; if (!invoker.includes("private")) { @@ -455,16 +463,24 @@ export class Fabricator { const resultFunction = await this.functionExecutor .run(async () => { const op: { name: string } = await gcfV2.updateFunction(apiFunction); - return await poller.pollOperation({ + return await poller.pollOperation({ ...gcfV2PollerOptions, pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, }); }) - .catch(rethrowAs(endpoint, "update")); - - endpoint.uri = resultFunction.serviceConfig.uri; - const serviceName = resultFunction.serviceConfig.service!; + .catch(rethrowAs(endpoint, "update")); + + endpoint.uri = resultFunction.serviceConfig?.uri; + const serviceName = resultFunction.serviceConfig?.service; + if (!serviceName) { + logger.debug("Result function unexpectedly didn't have a service name."); + utils.logLabeledWarning( + "functions", + "Updated function is not associated with a service. This deployment is in an unexpected state - please re-deploy your functions." + ); + return; + } let invoker: string[] | undefined; if (backend.isHttpsTriggered(endpoint)) { invoker = endpoint.httpsTrigger.invoker === null ? ["public"] : endpoint.httpsTrigger.invoker; diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index d419b3d0eef..99bb390ba00 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -18,6 +18,7 @@ import { CODEBASE_LABEL, HASH_LABEL, } from "../functions/constants"; +import { RequireKeys } from "../metaprogramming"; export const API_VERSION = "v2"; @@ -31,17 +32,6 @@ export type VpcConnectorEgressSettings = "PRIVATE_RANGES_ONLY" | "ALL_TRAFFIC"; export type IngressSettings = "ALLOW_ALL" | "ALLOW_INTERNAL_ONLY" | "ALLOW_INTERNAL_AND_GCLB"; export type FunctionState = "ACTIVE" | "FAILED" | "DEPLOYING" | "DELETING" | "UNKONWN"; -// The GCFv2 funtion type has many inner types which themselves have output-only fields: -// eventTrigger.trigger -// buildConfig.config -// buildConfig.workerPool -// serviceConfig.service -// serviceConfig.uri -// -// Because Omit<> doesn't work with nested property addresses, we're making those fields optional. -// An alternative would be to name the types OutputCloudFunction/CloudFunction or CloudFunction/InputCloudFunction. -export type OutputOnlyFields = "state" | "updateTime"; - // Values allowed for the operator field in EventFilter export type EventFilterOperator = "match-path-pattern"; @@ -153,17 +143,26 @@ export interface EventTrigger { channel?: string; } -export interface CloudFunction { +interface CloudFunctionBase { name: string; description?: string; buildConfig: BuildConfig; - serviceConfig: ServiceConfig; + serviceConfig?: ServiceConfig; eventTrigger?: EventTrigger; - state: FunctionState; - updateTime: Date; labels?: Record | null; } +export type OutputCloudFunction = CloudFunctionBase & { + state: FunctionState; + updateTime: Date; + serviceConfig?: RequireKeys; +}; + +export type InputCloudFunction = CloudFunctionBase & { + // serviceConfig is required. + serviceConfig: ServiceConfig; +}; + export interface OperationMetadata { createTime: string; endTime: string; @@ -181,13 +180,13 @@ export interface Operation { metadata?: OperationMetadata; done: boolean; error?: { code: number; message: string; details: unknown }; - response?: CloudFunction; + response?: OutputCloudFunction; } // Private API interface for ListFunctionsResponse. listFunctions returns // a CloudFunction[] interface ListFunctionsResponse { - functions: CloudFunction[]; + functions: OutputCloudFunction[]; unreachable: string[]; } @@ -292,9 +291,7 @@ export async function generateUploadUrl( /** * Creates a new Cloud Function. */ -export async function createFunction( - cloudFunction: Omit -): Promise { +export async function createFunction(cloudFunction: InputCloudFunction): Promise { // the API is a POST to the collection that owns the function name. const components = cloudFunction.name.split("/"); const functionId = components.splice(-1, 1)[0]; @@ -325,9 +322,9 @@ export async function getFunction( projectId: string, location: string, functionId: string -): Promise { +): Promise { const name = `projects/${projectId}/locations/${location}/functions/${functionId}`; - const res = await client.get(name); + const res = await client.get(name); return res.body; } @@ -335,7 +332,10 @@ export async function getFunction( * List all functions in a region. * Customers should generally use backend.existingBackend. */ -export async function listFunctions(projectId: string, region: string): Promise { +export async function listFunctions( + projectId: string, + region: string +): Promise { const res = await listFunctionsInternal(projectId, region); if (res.unreachable.includes(region)) { throw new FirebaseError(`Cloud Functions region ${region} is unavailable`); @@ -356,7 +356,7 @@ async function listFunctionsInternal( region: string ): Promise { type Response = ListFunctionsResponse & { nextPageToken?: string }; - const functions: CloudFunction[] = []; + const functions: OutputCloudFunction[] = []; const unreacahble = new Set(); let pageToken = ""; while (true) { @@ -386,9 +386,7 @@ async function listFunctionsInternal( * Updates a Cloud Function. * Customers can force a field to be deleted by setting that field to `undefined` */ -export async function updateFunction( - cloudFunction: Omit -): Promise { +export async function updateFunction(cloudFunction: InputCloudFunction): Promise { // Keys in labels and environmentVariables and secretEnvironmentVariables are user defined, so we don't recurse // for field masks. const fieldMasks = proto.fieldMasks( @@ -439,7 +437,7 @@ export async function deleteFunction(cloudFunction: string): Promise export function functionFromEndpoint( endpoint: backend.Endpoint, source: StorageSource -): Omit { +): InputCloudFunction { if (endpoint.platform !== "gcfv2") { throw new FirebaseError( "Trying to create a v2 CloudFunction with v1 API. This should never happen" @@ -453,7 +451,7 @@ export function functionFromEndpoint( ); } - const gcfFunction: Omit = { + const gcfFunction: InputCloudFunction = { name: backend.functionName(endpoint), buildConfig: { runtime: endpoint.runtime, @@ -601,7 +599,7 @@ export function functionFromEndpoint( /** * Generate a versionless Endpoint object from a v2 Cloud Function API object. */ -export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoint { +export function endpointFromFunction(gcfFunction: OutputCloudFunction): backend.Endpoint { const [, project, , region, , id] = gcfFunction.name.split("/"); let trigger: backend.Triggered; if (gcfFunction.labels?.["deployment-scheduled"] === "true") { @@ -671,63 +669,78 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi ...trigger, entryPoint: gcfFunction.buildConfig.entryPoint, runtime: gcfFunction.buildConfig.runtime, - uri: gcfFunction.serviceConfig.uri, }; - proto.copyIfPresent( - endpoint, - gcfFunction.serviceConfig, - "ingressSettings", - "environmentVariables", - "secretEnvironmentVariables", - "timeoutSeconds" - ); - proto.renameIfPresent( - endpoint, - gcfFunction.serviceConfig, - "serviceAccount", - "serviceAccountEmail" - ); - proto.convertIfPresent( - endpoint, - gcfFunction.serviceConfig, - "availableMemoryMb", - "availableMemory", - (prod) => { - if (prod === null) { - logger.debug("Prod should always return a valid memory amount"); - return prod as never; + if (gcfFunction.serviceConfig) { + proto.copyIfPresent( + endpoint, + gcfFunction.serviceConfig, + "ingressSettings", + "environmentVariables", + "secretEnvironmentVariables", + "timeoutSeconds", + "uri" + ); + proto.renameIfPresent( + endpoint, + gcfFunction.serviceConfig, + "serviceAccount", + "serviceAccountEmail" + ); + proto.convertIfPresent( + endpoint, + gcfFunction.serviceConfig, + "availableMemoryMb", + "availableMemory", + (prod) => { + if (prod === null) { + logger.debug("Prod should always return a valid memory amount"); + return prod as never; + } + const mem = mebibytes(prod); + if (!backend.isValidMemoryOption(mem)) { + logger.warn("Converting a function to an endpoint with an invalid memory option", mem); + } + return mem as backend.MemoryOptions; } - const mem = mebibytes(prod); - if (!backend.isValidMemoryOption(mem)) { - logger.warn("Converting a function to an endpoint with an invalid memory option", mem); + ); + proto.convertIfPresent(endpoint, gcfFunction.serviceConfig, "cpu", "availableCpu", (cpu) => { + let cpuVal: number | null = Number(cpu); + if (Number.isNaN(cpuVal)) { + cpuVal = null; } - return mem as backend.MemoryOptions; - } - ); - proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount"); - proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount"); - proto.copyIfPresent(endpoint, gcfFunction, "labels"); - if (gcfFunction.serviceConfig.vpcConnector) { - endpoint.vpc = { connector: gcfFunction.serviceConfig.vpcConnector }; + return cpuVal; + }); + proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount"); + proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount"); proto.renameIfPresent( - endpoint.vpc, + endpoint, gcfFunction.serviceConfig, - "egressSettings", - "vpcConnectorEgressSettings" + "concurrency", + "maxInstanceRequestConcurrency" ); + proto.copyIfPresent(endpoint, gcfFunction, "labels"); + if (gcfFunction.serviceConfig.vpcConnector) { + endpoint.vpc = { connector: gcfFunction.serviceConfig.vpcConnector }; + proto.renameIfPresent( + endpoint.vpc, + gcfFunction.serviceConfig, + "egressSettings", + "vpcConnectorEgressSettings" + ); + } + const serviceName = gcfFunction.serviceConfig.service; + if (!serviceName) { + logger.debug( + "Got a v2 function without a service name." + + "Maybe we've migrated to using the v2 API everywhere and missed this code" + ); + } else { + endpoint.runServiceId = utils.last(serviceName.split("/")); + } } endpoint.codebase = gcfFunction.labels?.[CODEBASE_LABEL] || projectConfig.DEFAULT_CODEBASE; if (gcfFunction.labels?.[HASH_LABEL]) { endpoint.hash = gcfFunction.labels[HASH_LABEL]; } - const serviceName = gcfFunction.serviceConfig.service; - if (!serviceName) { - logger.debug( - "Got a v2 function without a service name." + - "Maybe we've migrated to using the v2 API everywhere and missed this code" - ); - } else { - endpoint.runServiceId = utils.last(serviceName.split("/")); - } return endpoint; } diff --git a/src/metaprogramming.ts b/src/metaprogramming.ts index fb86756c804..4894253a74b 100644 --- a/src/metaprogramming.ts +++ b/src/metaprogramming.ts @@ -88,6 +88,22 @@ type DeepPickUnsafe = { : DeepPickUnsafe>; }; +/** + * Make properties of an object required. + * + * type Foo = { + * a?: string + * b?: number + * c?: object + * } + * + * type Bar = RequireKeys + * // Property "a" and "b" are now required. + */ +export type RequireKeys = T & { + [Key in Keys]: T[Key]; +}; + /** In the array LeafElems<[[["a"], "b"], ["c"]]> is "a" | "b" | "c" */ export type LeafElems = T extends Array ? Elem extends unknown[] diff --git a/src/test/deploy/functions/backend.spec.ts b/src/test/deploy/functions/backend.spec.ts index c881eaaf441..0dcdc7fba7d 100644 --- a/src/test/deploy/functions/backend.spec.ts +++ b/src/test/deploy/functions/backend.spec.ts @@ -6,7 +6,6 @@ import * as args from "../../../deploy/functions/args"; import * as backend from "../../../deploy/functions/backend"; import * as gcf from "../../../gcp/cloudfunctions"; import * as gcfV2 from "../../../gcp/cloudfunctionsv2"; -import * as run from "../../../gcp/run"; import * as utils from "../../../utils"; import * as projectConfig from "../../../functions/projectConfig"; @@ -37,7 +36,7 @@ describe("Backend", () => { generation: 42, }; - const CLOUD_FUNCTION_V2: Omit = { + const CLOUD_FUNCTION_V2: gcfV2.InputCloudFunction = { name: "projects/project/locations/region/functions/id", buildConfig: { entryPoint: "function", @@ -49,53 +48,18 @@ describe("Backend", () => { }, serviceConfig: { service: "projects/project/locations/region/services/service", + availableCpu: "1", + maxInstanceRequestConcurrency: 80, }, }; - - const CLOUD_RUN_SERVICE: run.Service = { - apiVersion: "serving.knative.dev/v1", - kind: "Service", - metadata: { - name: "service", - namespace: "projectnumber", - }, - spec: { - template: { - spec: { - containerConcurrency: 80, - containers: [ - { - image: "image", - ports: [ - { - name: "main", - containerPort: 8080, - }, - ], - env: {}, - resources: { - limits: { - memory: "256MiB", - cpu: "1", - }, - }, - }, - ], - }, - metadata: { - name: "service", - namespace: "project", - }, - }, - traffic: [], - }, - }; - const RUN_URI = "https://id-nonce-region-project.run.app"; - const HAVE_CLOUD_FUNCTION_V2: gcfV2.CloudFunction = { + const HAVE_CLOUD_FUNCTION_V2: gcfV2.OutputCloudFunction = { ...CLOUD_FUNCTION_V2, serviceConfig: { + service: "service", uri: RUN_URI, + availableCpu: "1", + maxInstanceRequestConcurrency: 80, }, state: "ACTIVE", updateTime: new Date(), @@ -161,20 +125,17 @@ describe("Backend", () => { let listAllFunctions: sinon.SinonStub; let listAllFunctionsV2: sinon.SinonStub; let logLabeledWarning: sinon.SinonSpy; - let getService: sinon.SinonStub; beforeEach(() => { listAllFunctions = sinon.stub(gcf, "listAllFunctions").rejects("Unexpected call"); listAllFunctionsV2 = sinon.stub(gcfV2, "listAllFunctions").rejects("Unexpected v2 call"); logLabeledWarning = sinon.spy(utils, "logLabeledWarning"); - getService = sinon.stub(run, "getService").rejects("Unexpected call to getService"); }); afterEach(() => { listAllFunctions.restore(); listAllFunctionsV2.restore(); logLabeledWarning.restore(); - getService.restore(); }); function newContext(): args.Context { @@ -300,9 +261,6 @@ describe("Backend", () => { }); it("should read v2 functions when enabled", async () => { - getService - .withArgs(HAVE_CLOUD_FUNCTION_V2.serviceConfig.service!) - .resolves(CLOUD_RUN_SERVICE); listAllFunctions.onFirstCall().resolves({ functions: [], unreachable: [], @@ -320,10 +278,10 @@ describe("Backend", () => { concurrency: 80, cpu: 1, httpsTrigger: {}, - uri: HAVE_CLOUD_FUNCTION_V2.serviceConfig.uri, + runServiceId: HAVE_CLOUD_FUNCTION_V2.serviceConfig?.service, + uri: HAVE_CLOUD_FUNCTION_V2.serviceConfig?.uri, }) ); - expect(getService).to.have.been.called; }); it("should deduce features of scheduled functions", async () => { diff --git a/src/test/gcp/cloudfunctionsv2.spec.ts b/src/test/gcp/cloudfunctionsv2.spec.ts index cc03fa73788..0431aeca64a 100644 --- a/src/test/gcp/cloudfunctionsv2.spec.ts +++ b/src/test/gcp/cloudfunctionsv2.spec.ts @@ -23,6 +23,7 @@ describe("cloudfunctionsv2", () => { entryPoint: "function", runtime: "nodejs16", codebase: projectConfig.DEFAULT_CODEBASE, + runServiceId: "service", }; const CLOUD_FUNCTION_V2_SOURCE: cloudfunctionsv2.StorageSource = { @@ -31,26 +32,26 @@ describe("cloudfunctionsv2", () => { generation: 42, }; - const CLOUD_FUNCTION_V2: Omit = - { - name: "projects/project/locations/region/functions/id", - buildConfig: { - entryPoint: "function", - runtime: "nodejs16", - source: { - storageSource: CLOUD_FUNCTION_V2_SOURCE, - }, - environmentVariables: {}, - }, - serviceConfig: { - availableMemory: `${backend.DEFAULT_MEMORY}Mi`, + const CLOUD_FUNCTION_V2: cloudfunctionsv2.InputCloudFunction = { + name: "projects/project/locations/region/functions/id", + buildConfig: { + entryPoint: "function", + runtime: "nodejs16", + source: { + storageSource: CLOUD_FUNCTION_V2_SOURCE, }, - }; + environmentVariables: {}, + }, + serviceConfig: { + availableMemory: `${backend.DEFAULT_MEMORY}Mi`, + }, + }; const RUN_URI = "https://id-nonce-region-project.run.app"; - const HAVE_CLOUD_FUNCTION_V2: cloudfunctionsv2.CloudFunction = { + const HAVE_CLOUD_FUNCTION_V2: cloudfunctionsv2.OutputCloudFunction = { ...CLOUD_FUNCTION_V2, serviceConfig: { + service: "service", uri: RUN_URI, }, state: "ACTIVE", @@ -116,10 +117,7 @@ describe("cloudfunctionsv2", () => { channel: "projects/myproject/locations/us-wildwest11/channels/mychannel", }, }; - const eventGcfFunction: Omit< - cloudfunctionsv2.CloudFunction, - cloudfunctionsv2.OutputOnlyFields - > = { + const eventGcfFunction: cloudfunctionsv2.InputCloudFunction = { ...CLOUD_FUNCTION_V2, eventTrigger: { eventType: "google.cloud.audit.log.v1.written", @@ -266,10 +264,7 @@ describe("cloudfunctionsv2", () => { ], }; - const fullGcfFunction: Omit< - cloudfunctionsv2.CloudFunction, - cloudfunctionsv2.OutputOnlyFields - > = { + const fullGcfFunction: cloudfunctionsv2.InputCloudFunction = { ...CLOUD_FUNCTION_V2, labels: { ...CLOUD_FUNCTION_V2.labels, @@ -317,10 +312,7 @@ describe("cloudfunctionsv2", () => { availableMemoryMb: 128, }; - const complexGcfFunction: Omit< - cloudfunctionsv2.CloudFunction, - cloudfunctionsv2.OutputOnlyFields - > = { + const complexGcfFunction: cloudfunctionsv2.InputCloudFunction = { ...CLOUD_FUNCTION_V2, eventTrigger: { eventType: events.v2.PUBSUB_PUBLISH_EVENT, @@ -355,7 +347,7 @@ describe("cloudfunctionsv2", () => { concurrency: 40, cpu: 2, }; - const gcfFunction: Omit = { + const gcfFunction: cloudfunctionsv2.InputCloudFunction = { ...CLOUD_FUNCTION_V2, serviceConfig: { ...CLOUD_FUNCTION_V2.serviceConfig, @@ -404,7 +396,7 @@ describe("cloudfunctionsv2", () => { }); it("should copy run service IDs", () => { - const fn: cloudfunctionsv2.CloudFunction = { + const fn: cloudfunctionsv2.OutputCloudFunction = { ...HAVE_CLOUD_FUNCTION_V2, serviceConfig: { ...HAVE_CLOUD_FUNCTION_V2.serviceConfig, @@ -701,6 +693,21 @@ describe("cloudfunctionsv2", () => { hash: "my-hash", }); }); + + it("should convert function without serviceConfig", () => { + const expectedEndpoint = { + ...ENDPOINT, + platform: "gcfv2", + httpsTrigger: {}, + }; + delete expectedEndpoint.runServiceId; + expect( + cloudfunctionsv2.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION_V2, + serviceConfig: undefined, + }) + ).to.deep.equal(expectedEndpoint); + }); }); describe("listFunctions", () => { From 98aff09e8bd935874f43a49fd8b9ac773ead6535 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 13 Apr 2023 22:14:40 +0000 Subject: [PATCH 0874/1699] 11.27.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5d0420a0236..99ff5892da7 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.26.0", + "version": "11.27.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.26.0", + "version": "11.27.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index fa812636bb9..95ac7468201 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.26.0", + "version": "11.27.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From d7c993eca03427f97d434e1d05824ec6ea75f857 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 13 Apr 2023 22:14:51 +0000 Subject: [PATCH 0875/1699] [firebase-release] Removed change log and reset repo after 11.27.0 release --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f03ff58d42..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +0,0 @@ -- Default emulators:start to use fast dev-mode for Nuxt3 applications (#5551) -- Fix broken Functions CLI experience for projects with incomplete GCF 2nd Gen functions (#5684) -- Disable GCF breaking change to automatically run npm build scripts as part of function deploy (#5687) -- Add experimental support for deploying Astro applications to Hosting (#5527) From de50243a57d85b723599c5104901db450180def6 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 13 Apr 2023 16:10:23 -0700 Subject: [PATCH 0876/1699] Correct experiment description. Remove dead experiment (#5676) --- src/deploy/functions/build.ts | 31 ++++++++++++++----------------- src/experiments.ts | 7 +------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index 432a73d3f3d..222b0a75d4e 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -2,7 +2,6 @@ import * as backend from "./backend"; import * as proto from "../../gcp/proto"; import * as api from "../../.../../api"; import * as params from "./params"; -import * as experiments from "../../experiments"; import { FirebaseError } from "../../error"; import { assertExhaustive, mapObject, nullsafeVisitor } from "../../functional"; import { UserEnvsOpts, writeUserEnvs } from "../../functions/env"; @@ -285,24 +284,22 @@ export async function resolveBackend( nonInteractive?: boolean ): Promise<{ backend: backend.Backend; envs: Record }> { let paramValues: Record = {}; - if (experiments.isEnabled("functionsparams")) { - paramValues = await params.resolveParams( - build.params, - firebaseConfig, - envWithTypes(build.params, userEnvs), - nonInteractive - ); - - const toWrite: Record = {}; - for (const paramName of Object.keys(paramValues)) { - const paramValue = paramValues[paramName]; - if (Object.prototype.hasOwnProperty.call(userEnvs, paramName) || paramValue.internal) { - continue; - } - toWrite[paramName] = paramValue.toString(); + paramValues = await params.resolveParams( + build.params, + firebaseConfig, + envWithTypes(build.params, userEnvs), + nonInteractive + ); + + const toWrite: Record = {}; + for (const paramName of Object.keys(paramValues)) { + const paramValue = paramValues[paramName]; + if (Object.prototype.hasOwnProperty.call(userEnvs, paramName) || paramValue.internal) { + continue; } - writeUserEnvs(toWrite, userEnvOpt); + toWrite[paramName] = paramValue.toString(); } + writeUserEnvs(toWrite, userEnvOpt); return { backend: toBackend(build, paramValues), envs: paramValues }; } diff --git a/src/experiments.ts b/src/experiments.ts index e335807e0ee..7d12f50eeff 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -64,10 +64,6 @@ export const ALL_EXPERIMENTS = experiments({ "of how that image was created.", public: true, }, - functionsparams: { - shortDescription: "Adds support for paramaterizing functions deployments", - default: true, - }, // Emulator experiments emulatoruisnapshot: { @@ -79,8 +75,7 @@ export const ALL_EXPERIMENTS = experiments({ shortDescription: "Native support for popular web frameworks", fullDescription: "Adds support for popular web frameworks such as Next.js " + - "Angular, React, Svelte, and Vite-compatible frameworks. Firebase is " + - "committed to support these platforms long-term, but a manual migration " + + "Angular, React, Svelte, and Vite-compatible frameworks. A manual migration " + "may be required when the non-experimental support for these frameworks " + "is released", docsUri: "https://firebase.google.com/docs/hosting/frameworks-overview", From cfad0cc7c7d76243558bdaab38c172606822156b Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 14 Apr 2023 14:24:39 -0700 Subject: [PATCH 0877/1699] Continue publishing even if tweet fails to send (#5693) --- scripts/tweet.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/tweet.js b/scripts/tweet.js index a1016cc30f8..d2a2def4a29 100644 --- a/scripts/tweet.js +++ b/scripts/tweet.js @@ -45,8 +45,7 @@ client.post( { status: `v${version} of @Firebase CLI is available. Release notes: ${getUrl(version)}` }, (err) => { if (err) { - console.error(err); - process.exit(1); + console.error(`Failed to make a tweet for firebase-tools@${version}: ${err}`); } } ); From 4cc49d4fc213788c4e895d94a6cb50778a09d260 Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Mon, 17 Apr 2023 10:13:42 -0700 Subject: [PATCH 0878/1699] Relaxed repo URI validation. (#5698) * Relaxed repo URI validation. * Added changelog entry. --- CHANGELOG.md | 1 + src/extensions/extensionsHelper.ts | 30 ++++-------------------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..2c913d6459e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Relaxed repo URI validation in ext:dev:publish (#5698). diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index ced13ec72b6..93034213044 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -604,33 +604,11 @@ export async function publishExtensionVersionFromRemoteRepo(args: { throw new FirebaseError("Repo URI is required but not currently set."); } } - if (extension?.repoUri && extension.repoUri !== repoUri) { - throw new FirebaseError( - `Repo URI '${clc.bold(args.repoUri)}' does not match repo URI '${clc.bold( - extension.repoUri! - )}' already associated with Extension ${clc.bold(extensionRef)}. Repo URI cannot be changed.` - ); - } - if (!extension?.repoUri) { - logger.info( - `\n${clc.red("Warning:")} You are about to associate repo URI ${clc.bold( - repoUri - )} with Extension ${clc.bold( - extensionRef - )}. This cannot be changed. All future verifiable versions must be published from this repo. ` + - `You can continue publishing unverifiable versions from local source.` - ); - const confirmed = await confirm({ - nonInteractive: args.nonInteractive, - force: args.force, - default: false, - }); - if (!confirmed) { - return; - } - } else { + if (extension?.repoUri) { logger.info( - `Extension ${clc.bold(extensionRef)} is published from ${clc.bold(extension?.repoUri)}.` + `Extension ${clc.bold(extensionRef)} is published from ${clc.bold( + extension?.repoUri + )}. Use --repo to change this repo.` ); } From f5786fd9c4cda2959c855e432cd5e206b39cca15 Mon Sep 17 00:00:00 2001 From: Victor Elmir Verchkovski Date: Mon, 17 Apr 2023 14:57:50 -0400 Subject: [PATCH 0879/1699] Firebase CLI Supports Firestore Provisioning API (#5616) b/267473272 - Add support for Firestore Provisioning API. This feature is experimental and may not yet be enabled on all projects. It introduces 6 new commands to the Firebase CLI --- CHANGELOG.md | 7 + src/commands/firestore-databases-create.ts | 68 +++++ src/commands/firestore-databases-delete.ts | 44 +++ src/commands/firestore-databases-get.ts | 27 ++ src/commands/firestore-databases-list.ts | 26 ++ src/commands/firestore-databases-update.ts | 61 ++++ src/commands/firestore-indexes-list.ts | 4 +- src/commands/firestore-locations.ts | 26 ++ src/commands/index.ts | 7 + src/commands/open.ts | 10 + src/deploy/firestore/deploy.ts | 4 +- .../{indexes-sort.ts => api-sort.ts} | 26 +- .../{indexes-spec.ts => api-spec.ts} | 2 +- .../{indexes-api.ts => api-types.ts} | 43 +++ src/firestore/{indexes.ts => api.ts} | 267 +++++++++++++++--- src/firestore/options.ts | 4 + src/init/features/firestore/indexes.ts | 4 +- src/test/firestore/indexes.spec.ts | 10 +- 18 files changed, 593 insertions(+), 47 deletions(-) create mode 100644 src/commands/firestore-databases-create.ts create mode 100644 src/commands/firestore-databases-delete.ts create mode 100644 src/commands/firestore-databases-get.ts create mode 100644 src/commands/firestore-databases-list.ts create mode 100644 src/commands/firestore-databases-update.ts create mode 100644 src/commands/firestore-locations.ts rename src/firestore/{indexes-sort.ts => api-sort.ts} (89%) rename src/firestore/{indexes-spec.ts => api-spec.ts} (95%) rename src/firestore/{indexes-api.ts => api-types.ts} (64%) rename src/firestore/{indexes.ts => api.ts} (68%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c913d6459e..375be31d767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,8 @@ +- Adds new commands for provisioning and managing Firestore databases: (#5616) +- firestore:databases:list +- firestore:databases:create +- firestore:databases:get +- firestore:databases:update +- firestore:databases:delete +- firestore:locations - Relaxed repo URI validation in ext:dev:publish (#5698). diff --git a/src/commands/firestore-databases-create.ts b/src/commands/firestore-databases-create.ts new file mode 100644 index 00000000000..271fba07dcd --- /dev/null +++ b/src/commands/firestore-databases-create.ts @@ -0,0 +1,68 @@ +import { Command } from "../command"; +import * as clc from "colorette"; +import * as fsi from "../firestore/api"; +import * as types from "../firestore/api-types"; +import { logger } from "../logger"; +import { requirePermissions } from "../requirePermissions"; +import { Emulators } from "../emulator/types"; +import { warnEmulatorNotSupported } from "../emulator/commandUtils"; +import { FirestoreOptions } from "../firestore/options"; + +export const command = new Command("firestore:databases:create ") + .description("Create a database in your Firebase project.") + .option( + "--location ", + "Region to create database, for example 'nam5'. Run 'firebase firestore:locations' to get a list of eligible locations. (required)" + ) + .option( + "--delete-protection ", + "Whether or not to prevent deletion of database, for example 'ENABLED' or 'DISABLED'. Default is 'DISABLED'" + ) + .before(requirePermissions, ["datastore.databases.create"]) + .before(warnEmulatorNotSupported, Emulators.FIRESTORE) + .action(async (database: string, options: FirestoreOptions) => { + const api = new fsi.FirestoreApi(); + if (!options.location) { + logger.error( + "Missing required flag --location. See firebase firestore:databases:create --help for more info." + ); + return; + } + // Type is always Firestore Native since Firebase does not support Datastore Mode + const type: types.DatabaseType = types.DatabaseType.FIRESTORE_NATIVE; + if ( + options.deleteProtection && + options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.ENABLED && + options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.DISABLED + ) { + logger.error( + "Invalid value for flag --delete-protection. See firebase firestore:databases:create --help for more info." + ); + return; + } + const deleteProtectionState: types.DatabaseDeleteProtectionState = + options.deleteProtection === types.DatabaseDeleteProtectionStateOption.ENABLED + ? types.DatabaseDeleteProtectionState.ENABLED + : types.DatabaseDeleteProtectionState.DISABLED; + + const databaseResp: types.DatabaseResp = await api.createDatabase( + options.project, + database, + options.location, + type, + deleteProtectionState + ); + + if (options.json) { + logger.info(JSON.stringify(databaseResp, undefined, 2)); + } else { + logger.info(clc.bold(`Successfully created ${api.prettyDatabaseString(databaseResp)}`)); + logger.info( + "Please be sure to configure Firebase rules in your Firebase config file for\n" + + "the new database. By default, created databases will have closed rules that\n" + + "block any incoming third-party traffic." + ); + } + + return databaseResp; + }); diff --git a/src/commands/firestore-databases-delete.ts b/src/commands/firestore-databases-delete.ts new file mode 100644 index 00000000000..4017b1165eb --- /dev/null +++ b/src/commands/firestore-databases-delete.ts @@ -0,0 +1,44 @@ +import { Command } from "../command"; +import * as clc from "colorette"; +import * as fsi from "../firestore/api"; +import * as types from "../firestore/api-types"; +import { promptOnce } from "../prompt"; +import { logger } from "../logger"; +import { requirePermissions } from "../requirePermissions"; +import { Emulators } from "../emulator/types"; +import { warnEmulatorNotSupported } from "../emulator/commandUtils"; +import { FirestoreOptions } from "../firestore/options"; +import { FirebaseError } from "../error"; + +export const command = new Command("firestore:databases:delete ") + .description( + "Delete a database in your Cloud Firestore project. Database delete protection state must be disabled. To do so, use the update command: firebase firestore:databases:update --delete-protection DISABLED" + ) + .option("--force", "Attempt to delete database without prompting for confirmation.") + .before(requirePermissions, ["datastore.databases.delete"]) + .before(warnEmulatorNotSupported, Emulators.FIRESTORE) + .action(async (database: string, options: FirestoreOptions) => { + const api = new fsi.FirestoreApi(); + + if (!options.force) { + const confirmMessage = `You are about to delete projects/${options.project}/databases/${database}. Do you wish to continue?`; + const consent = await promptOnce({ + type: "confirm", + message: confirmMessage, + default: false, + }); + if (!consent) { + throw new FirebaseError("Delete database canceled."); + } + } + + const databaseResp: types.DatabaseResp = await api.deleteDatabase(options.project, database); + + if (options.json) { + logger.info(JSON.stringify(databaseResp, undefined, 2)); + } else { + logger.info(clc.bold(`Successfully deleted ${api.prettyDatabaseString(databaseResp)}`)); + } + + return databaseResp; + }); diff --git a/src/commands/firestore-databases-get.ts b/src/commands/firestore-databases-get.ts new file mode 100644 index 00000000000..b22081a0f6e --- /dev/null +++ b/src/commands/firestore-databases-get.ts @@ -0,0 +1,27 @@ +import { Command } from "../command"; +import * as fsi from "../firestore/api"; +import * as types from "../firestore/api-types"; +import { logger } from "../logger"; +import { requirePermissions } from "../requirePermissions"; +import { Emulators } from "../emulator/types"; +import { warnEmulatorNotSupported } from "../emulator/commandUtils"; +import { FirestoreOptions } from "../firestore/options"; + +export const command = new Command("firestore:databases:get [database]") + .description("Get database in your Cloud Firestore project.") + .before(requirePermissions, ["datastore.databases.get"]) + .before(warnEmulatorNotSupported, Emulators.FIRESTORE) + .action(async (database: string, options: FirestoreOptions) => { + const api = new fsi.FirestoreApi(); + + const databaseId = database || "(default)"; + const databaseResp: types.DatabaseResp = await api.getDatabase(options.project, databaseId); + + if (options.json) { + logger.info(JSON.stringify(databaseResp, undefined, 2)); + } else { + api.prettyPrintDatabase(databaseResp); + } + + return databaseResp; + }); diff --git a/src/commands/firestore-databases-list.ts b/src/commands/firestore-databases-list.ts new file mode 100644 index 00000000000..b7d6f712c49 --- /dev/null +++ b/src/commands/firestore-databases-list.ts @@ -0,0 +1,26 @@ +import { Command } from "../command"; +import * as fsi from "../firestore/api"; +import * as types from "../firestore/api-types"; +import { logger } from "../logger"; +import { requirePermissions } from "../requirePermissions"; +import { Emulators } from "../emulator/types"; +import { warnEmulatorNotSupported } from "../emulator/commandUtils"; +import { FirestoreOptions } from "../firestore/options"; + +export const command = new Command("firestore:databases:list") + .description("List databases in your Cloud Firestore project.") + .before(requirePermissions, ["datastore.databases.list"]) + .before(warnEmulatorNotSupported, Emulators.FIRESTORE) + .action(async (options: FirestoreOptions) => { + const api = new fsi.FirestoreApi(); + + const databases: types.DatabaseResp[] = await api.listDatabases(options.project); + + if (options.json) { + logger.info(JSON.stringify(databases, undefined, 2)); + } else { + api.prettyPrintDatabases(databases); + } + + return databases; + }); diff --git a/src/commands/firestore-databases-update.ts b/src/commands/firestore-databases-update.ts new file mode 100644 index 00000000000..88eea9b76af --- /dev/null +++ b/src/commands/firestore-databases-update.ts @@ -0,0 +1,61 @@ +import { Command } from "../command"; +import * as clc from "colorette"; +import * as fsi from "../firestore/api"; +import * as types from "../firestore/api-types"; +import { logger } from "../logger"; +import { requirePermissions } from "../requirePermissions"; +import { Emulators } from "../emulator/types"; +import { warnEmulatorNotSupported } from "../emulator/commandUtils"; +import { FirestoreOptions } from "../firestore/options"; + +export const command = new Command("firestore:databases:update ") + .description( + "Update a database in your Firebase project. Must specify at least one property to update." + ) + .option("--json", "Prints raw json response of the create API call if specified") + .option( + "--delete-protection ", + "Whether or not to prevent deletion of database, for example 'ENABLED' or 'DISABLED'. Default is 'DISABLED'" + ) + .before(requirePermissions, ["datastore.databases.update"]) + .before(warnEmulatorNotSupported, Emulators.FIRESTORE) + .action(async (database: string, options: FirestoreOptions) => { + const api = new fsi.FirestoreApi(); + + if (!options.type && !options.deleteProtection) { + logger.error( + "Missing properties to update. See firebase firestore:databases:update --help for more info." + ); + return; + } + const type: types.DatabaseType = types.DatabaseType.FIRESTORE_NATIVE; + if ( + options.deleteProtection && + options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.ENABLED && + options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.DISABLED + ) { + logger.error( + "Invalid value for flag --delete-protection. See firebase firestore:databases:update --help for more info." + ); + return; + } + const deleteProtectionState: types.DatabaseDeleteProtectionState = + options.deleteProtection === types.DatabaseDeleteProtectionStateOption.ENABLED + ? types.DatabaseDeleteProtectionState.ENABLED + : types.DatabaseDeleteProtectionState.DISABLED; + + const databaseResp: types.DatabaseResp = await api.updateDatabase( + options.project, + database, + type, + deleteProtectionState + ); + + if (options.json) { + logger.info(JSON.stringify(databaseResp, undefined, 2)); + } else { + logger.info(clc.bold(`Successfully updated ${api.prettyDatabaseString(databaseResp)}`)); + } + + return databaseResp; + }); diff --git a/src/commands/firestore-indexes-list.ts b/src/commands/firestore-indexes-list.ts index 0cf1c40c78d..83873650fae 100644 --- a/src/commands/firestore-indexes-list.ts +++ b/src/commands/firestore-indexes-list.ts @@ -1,6 +1,6 @@ import { Command } from "../command"; import * as clc from "colorette"; -import * as fsi from "../firestore/indexes"; +import * as fsi from "../firestore/api"; import { logger } from "../logger"; import { requirePermissions } from "../requirePermissions"; import { Emulators } from "../emulator/types"; @@ -21,7 +21,7 @@ export const command = new Command("firestore:indexes") .before(requirePermissions, ["datastore.indexes.list"]) .before(warnEmulatorNotSupported, Emulators.FIRESTORE) .action(async (options: FirestoreOptions) => { - const indexApi = new fsi.FirestoreIndexes(); + const indexApi = new fsi.FirestoreApi(); const databaseId = options.database ?? "(default)"; const indexes = await indexApi.listIndexes(options.project, databaseId); diff --git a/src/commands/firestore-locations.ts b/src/commands/firestore-locations.ts new file mode 100644 index 00000000000..083e4bc4e0f --- /dev/null +++ b/src/commands/firestore-locations.ts @@ -0,0 +1,26 @@ +import { Command } from "../command"; +import * as fsi from "../firestore/api"; +import * as types from "../firestore/api-types"; +import { logger } from "../logger"; +import { requirePermissions } from "../requirePermissions"; +import { Emulators } from "../emulator/types"; +import { warnEmulatorNotSupported } from "../emulator/commandUtils"; +import { FirestoreOptions } from "../firestore/options"; + +export const command = new Command("firestore:locations") + .description("List possible locations for your Cloud Firestore project.") + .before(requirePermissions, ["datastore.locations.list"]) + .before(warnEmulatorNotSupported, Emulators.FIRESTORE) + .action(async (options: FirestoreOptions) => { + const api = new fsi.FirestoreApi(); + + const locations: types.Location[] = await api.locations(options.project); + + if (options.json) { + logger.info(JSON.stringify(locations, undefined, 2)); + } else { + api.prettyPrintLocations(locations); + } + + return locations; + }); diff --git a/src/commands/index.ts b/src/commands/index.ts index a23e82d9bee..4beaaaae8c2 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -108,6 +108,13 @@ export function load(client: any): any { client.firestore = {}; client.firestore.delete = loadCommand("firestore-delete"); client.firestore.indexes = loadCommand("firestore-indexes-list"); + client.firestore.locations = loadCommand("firestore-locations"); + client.firestore.databases = {}; + client.firestore.databases.list = loadCommand("firestore-databases-list"); + client.firestore.databases.get = loadCommand("firestore-databases-get"); + client.firestore.databases.create = loadCommand("firestore-databases-create"); + client.firestore.databases.update = loadCommand("firestore-databases-update"); + client.firestore.databases.delete = loadCommand("firestore-databases-delete"); client.functions = {}; client.functions.config = {}; client.functions.config.clone = loadCommand("functions-config-clone"); diff --git a/src/commands/open.ts b/src/commands/open.ts index edaea79601c..45d5a5138c0 100644 --- a/src/commands/open.ts +++ b/src/commands/open.ts @@ -30,6 +30,16 @@ const LINKS: Link[] = [ { name: "Firestore: Data", arg: "firestore", consolePath: "/firestore/data" }, { name: "Firestore: Rules", arg: "firestore:rules", consolePath: "/firestore/rules" }, { name: "Firestore: Indexes", arg: "firestore:indexes", consolePath: "/firestore/indexes" }, + { + name: "Firestore: Databases List", + arg: "firestore:databases:list", + consolePath: "/firestore/databases/list", + }, + { + name: "Firestore: Locations", + arg: "firestore:locations", + consolePath: "/firestore/locations", + }, { name: "Firestore: Usage", arg: "firestore:usage", consolePath: "/firestore/usage" }, { name: "Functions", arg: "functions", consolePath: "/functions/list" }, { name: "Functions Log", arg: "functions:log" } /* Special Case */, diff --git a/src/deploy/firestore/deploy.ts b/src/deploy/firestore/deploy.ts index e2bef67cab3..313036c02b1 100644 --- a/src/deploy/firestore/deploy.ts +++ b/src/deploy/firestore/deploy.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; -import { FirestoreIndexes } from "../../firestore/indexes"; +import { FirestoreApi } from "../../firestore/api"; import { logger } from "../../logger"; import * as utils from "../../utils"; import { RulesDeploy, RulesetServiceType } from "../../rulesDeploy"; @@ -30,7 +30,7 @@ async function deployIndexes(context: any, options: any): Promise { const indexesContext: IndexContext[] = context?.firestore?.indexes; utils.logBullet(clc.bold(clc.cyan("firestore: ")) + "deploying indexes..."); - const firestoreIndexes = new FirestoreIndexes(); + const firestoreIndexes = new FirestoreApi(); await Promise.all( indexesContext.map(async (indexContext: IndexContext): Promise => { const { databaseId, indexesFileName, indexesRawSpec } = indexContext; diff --git a/src/firestore/indexes-sort.ts b/src/firestore/api-sort.ts similarity index 89% rename from src/firestore/indexes-sort.ts rename to src/firestore/api-sort.ts index 8539eb002cf..8768be0a8a0 100644 --- a/src/firestore/indexes-sort.ts +++ b/src/firestore/api-sort.ts @@ -1,5 +1,5 @@ -import * as API from "./indexes-api"; -import * as Spec from "./indexes-spec"; +import * as API from "./api-types"; +import * as Spec from "./api-spec"; import * as util from "./util"; const QUERY_SCOPE_SEQUENCE = [ @@ -59,6 +59,28 @@ export function compareApiIndex(a: API.Index, b: API.Index): number { return compareArrays(a.fields, b.fields, compareIndexField); } +/** + * Compare two Database api entries for sorting. + * + * Comparisons: + * 1) The databaseId (name) + */ +export function compareApiDatabase(a: API.DatabaseResp, b: API.DatabaseResp): number { + // Name should always be unique and present + return a.name > b.name ? 1 : -1; +} + +/** + * Compare two Location api entries for sorting. + * + * Comparisons: + * 1) The locationId. + */ +export function compareLocation(a: API.Location, b: API.Location): number { + // LocationId should always be unique and present + return a.locationId > b.locationId ? 1 : -1; +} + /** * Compare two Field api entries for sorting. * diff --git a/src/firestore/indexes-spec.ts b/src/firestore/api-spec.ts similarity index 95% rename from src/firestore/indexes-spec.ts rename to src/firestore/api-spec.ts index 6fb10f23f7e..da491b8438a 100644 --- a/src/firestore/indexes-spec.ts +++ b/src/firestore/api-spec.ts @@ -4,7 +4,7 @@ * Please review and update the README as needed and notify firebase-docs@google.com. */ -import * as API from "./indexes-api"; +import * as API from "./api-types"; /** * An entry specifying a compound or other non-default index. diff --git a/src/firestore/indexes-api.ts b/src/firestore/api-types.ts similarity index 64% rename from src/firestore/indexes-api.ts rename to src/firestore/api-types.ts index d8127f7a75d..9ceec083c89 100644 --- a/src/firestore/indexes-api.ts +++ b/src/firestore/api-types.ts @@ -81,3 +81,46 @@ export interface IndexConfig { ancestorField?: string; indexes?: Index[]; } + +export interface Location { + name: string; + labels: any; + metadata: any; + locationId: string; + displayName: string; +} + +export enum DatabaseType { + DATASTORE_MODE = "DATASTORE_MODE", + FIRESTORE_NATIVE = "FIRESTORE_NATIVE", +} + +export enum DatabaseDeleteProtectionStateOption { + ENABLED = "ENABLED", + DISABLED = "DISABLED", +} + +export enum DatabaseDeleteProtectionState { + ENABLED = "DELETE_PROTECTION_ENABLED", + DISABLED = "DELETE_PROTECTION_DISABLED", +} + +export interface DatabaseReq { + locationId?: string; + type?: DatabaseType; + deleteProtectionState?: DatabaseDeleteProtectionState; +} + +export interface DatabaseResp { + name: string; + uid: string; + createTime: string; + updateTime: string; + locationId: string; + type: DatabaseType; + concurrencyMode: string; + appEngineIntegrationMode: string; + keyPrefix: string; + deleteProtectionState: DatabaseDeleteProtectionState; + etag: string; +} diff --git a/src/firestore/indexes.ts b/src/firestore/api.ts similarity index 68% rename from src/firestore/indexes.ts rename to src/firestore/api.ts index bcd5cf09ff5..a00edab8bb6 100644 --- a/src/firestore/indexes.ts +++ b/src/firestore/api.ts @@ -4,15 +4,16 @@ import { logger } from "../logger"; import * as utils from "../utils"; import * as validator from "./validator"; -import * as API from "./indexes-api"; -import * as Spec from "./indexes-spec"; -import * as sort from "./indexes-sort"; +import * as types from "./api-types"; +import * as Spec from "./api-spec"; +import * as sort from "./api-sort"; import * as util from "./util"; import { promptOnce } from "../prompt"; import { firestoreOrigin } from "../api"; +import { FirebaseError } from "../error"; import { Client } from "../apiv2"; -export class FirestoreIndexes { +export class FirestoreApi { apiClient = new Client({ urlPrefix: firestoreOrigin, apiVersion: "v1" }); /** @@ -40,8 +41,8 @@ export class FirestoreIndexes { const indexesToDeploy: Spec.Index[] = spec.indexes; const fieldOverridesToDeploy: Spec.FieldOverride[] = spec.fieldOverrides; - const existingIndexes: API.Index[] = await this.listIndexes(options.project, databaseId); - const existingFieldOverrides: API.Field[] = await this.listFieldOverrides( + const existingIndexes: types.Index[] = await this.listIndexes(options.project, databaseId); + const existingFieldOverrides: types.Field[] = await this.listFieldOverrides( options.project, databaseId ); @@ -172,18 +173,18 @@ export class FirestoreIndexes { * List all indexes that exist on a given project. * @param project the Firebase project id. */ - async listIndexes(project: string, databaseId: string = "(default)"): Promise { + async listIndexes(project: string, databaseId: string = "(default)"): Promise { const url = `/projects/${project}/databases/${databaseId}/collectionGroups/-/indexes`; - const res = await this.apiClient.get<{ indexes?: API.Index[] }>(url); + const res = await this.apiClient.get<{ indexes?: types.Index[] }>(url); const indexes = res.body.indexes; if (!indexes) { return []; } - return indexes.map((index: any): API.Index => { + return indexes.map((index: any): types.Index => { // Ignore any fields that point at the document ID, as those are implied // in all indexes. - const fields = index.fields.filter((field: API.IndexField) => { + const fields = index.fields.filter((field: types.IndexField) => { return field.fieldPath !== "__name__"; }); @@ -203,11 +204,11 @@ export class FirestoreIndexes { async listFieldOverrides( project: string, databaseId: string = "(default)" - ): Promise { + ): Promise { const parent = `projects/${project}/databases/${databaseId}/collectionGroups/-`; const url = `/${parent}/fields?filter=indexConfig.usesAncestorConfig=false OR ttlConfig:*`; - const res = await this.apiClient.get<{ fields?: API.Field[] }>(url); + const res = await this.apiClient.get<{ fields?: types.Field[] }>(url); const fields = res.body.fields; // This should never be the case, since the API always returns the __default__ @@ -226,7 +227,7 @@ export class FirestoreIndexes { * Turn an array of indexes and field overrides into a {@link Spec.IndexFile} suitable for use * in an indexes.json file. */ - makeIndexSpec(indexes: API.Index[], fields?: API.Field[]): Spec.IndexFile { + makeIndexSpec(indexes: types.Index[], fields?: types.Field[]): Spec.IndexFile { const indexesJson = indexes.map((index) => { return { collectionGroup: util.parseIndexName(index.name).collectionGroupId, @@ -271,7 +272,7 @@ export class FirestoreIndexes { * Print an array of indexes to the console. * @param indexes the array of indexes. */ - prettyPrintIndexes(indexes: API.Index[]): void { + prettyPrintIndexes(indexes: types.Index[]): void { if (indexes.length === 0) { logger.info("None"); return; @@ -283,11 +284,77 @@ export class FirestoreIndexes { }); } + /** + * Print an array of databases to the console as an ASCII table. + * @param databases the array of Firestore databases. + */ + prettyPrintDatabases(databases: types.DatabaseResp[]): void { + if (databases.length === 0) { + logger.info("No databases found."); + return; + } + const sortedDatabases: types.DatabaseResp[] = databases.sort(sort.compareApiDatabase); + const Table = require("cli-table"); + const table = new Table({ + head: ["Database Name"], + colWidths: [Math.max(...sortedDatabases.map((database) => database.name.length + 5), 20)], + }); + + table.push(...sortedDatabases.map((database) => [this.prettyDatabaseString(database)])); + logger.info(table.toString()); + } + + /** + * Print important fields of a database to the console as an ASCII table. + * @param database the Firestore database. + */ + prettyPrintDatabase(database: types.DatabaseResp): void { + const Table = require("cli-table"); + const table = new Table({ + head: ["Field", "Value"], + colWidths: [25, Math.max(50, 5 + database.name.length)], + }); + + table.push( + ["Name", clc.yellow(database.name)], + ["Create Time", clc.yellow(database.createTime)], + ["Last Update Time", clc.yellow(database.updateTime)], + ["Type", clc.yellow(database.type)], + ["Location", clc.yellow(database.locationId)], + ["Delete Protection State", clc.yellow(database.deleteProtectionState)] + ); + logger.info(table.toString()); + } + + /** + * Print an array of locations to the console in an ASCII table. Group multi regions together + * Example: United States: nam5 + * @param locations the array of locations. + */ + prettyPrintLocations(locations: types.Location[]): void { + if (locations.length === 0) { + logger.info("No Locations Available"); + return; + } + const Table = require("cli-table"); + const table = new Table({ + head: ["Display Name", "LocationId"], + colWidths: [20, 30], + }); + + table.push( + ...locations + .sort(sort.compareLocation) + .map((location) => [location.displayName, location.locationId]) + ); + logger.info(table.toString()); + } + /** * Print an array of field overrides to the console. * @param fields the array of field overrides. */ - printFieldOverrides(fields: API.Field[]): void { + printFieldOverrides(fields: types.Field[]): void { if (fields.length === 0) { logger.info("None"); return; @@ -318,12 +385,12 @@ export class FirestoreIndexes { } /** - * Validate that an arbitrary object is safe to use as an {@link API.Field}. + * Validate that an arbitrary object is safe to use as an {@link types.Field}. */ validateIndex(index: any): void { validator.assertHas(index, "collectionGroup"); validator.assertHas(index, "queryScope"); - validator.assertEnum(index, "queryScope", Object.keys(API.QueryScope)); + validator.assertEnum(index, "queryScope", Object.keys(types.QueryScope)); validator.assertHas(index, "fields"); @@ -332,11 +399,11 @@ export class FirestoreIndexes { validator.assertHasOneOf(field, ["order", "arrayConfig"]); if (field.order) { - validator.assertEnum(field, "order", Object.keys(API.Order)); + validator.assertEnum(field, "order", Object.keys(types.Order)); } if (field.arrayConfig) { - validator.assertEnum(field, "arrayConfig", Object.keys(API.ArrayConfig)); + validator.assertEnum(field, "arrayConfig", Object.keys(types.ArrayConfig)); } }); } @@ -358,15 +425,15 @@ export class FirestoreIndexes { validator.assertHasOneOf(index, ["arrayConfig", "order"]); if (index.arrayConfig) { - validator.assertEnum(index, "arrayConfig", Object.keys(API.ArrayConfig)); + validator.assertEnum(index, "arrayConfig", Object.keys(types.ArrayConfig)); } if (index.order) { - validator.assertEnum(index, "order", Object.keys(API.Order)); + validator.assertEnum(index, "order", Object.keys(types.Order)); } if (index.queryScope) { - validator.assertEnum(index, "queryScope", Object.keys(API.QueryScope)); + validator.assertEnum(index, "queryScope", Object.keys(types.QueryScope)); } }); } @@ -420,7 +487,7 @@ export class FirestoreIndexes { /** * Delete an existing field overrides on the specified project. */ - deleteField(field: API.Field): Promise { + deleteField(field: types.Field): Promise { const url = field.name; const data = {}; @@ -441,7 +508,7 @@ export class FirestoreIndexes { /** * Delete an existing index on the specified project. */ - deleteIndex(index: API.Index): Promise { + deleteIndex(index: types.Index): Promise { const url = index.name!; return this.apiClient.delete(`/${url}`); } @@ -449,7 +516,7 @@ export class FirestoreIndexes { /** * Determine if an API Index and a Spec Index are functionally equivalent. */ - indexMatchesSpec(index: API.Index, spec: Spec.Index): boolean { + indexMatchesSpec(index: types.Index, spec: Spec.Index): boolean { const collection = util.parseIndexName(index.name).collectionGroupId; if (collection !== spec.collectionGroup) { return false; @@ -489,7 +556,7 @@ export class FirestoreIndexes { /** * Determine if an API Field and a Spec Field are functionally equivalent. */ - fieldMatchesSpec(field: API.Field, spec: Spec.FieldOverride): boolean { + fieldMatchesSpec(field: types.Field, spec: Spec.FieldOverride): boolean { const parsedName = util.parseFieldName(field.name); if (parsedName.collectionGroupId !== spec.collectionGroup) { @@ -566,7 +633,7 @@ export class FirestoreIndexes { result.indexes = spec.indexes.map((index: any) => { const i = { collectionGroup: index.collectionGroup || index.collectionId, - queryScope: index.queryScope || API.QueryScope.COLLECTION, + queryScope: index.queryScope || types.QueryScope.COLLECTION, fields: [], }; @@ -580,8 +647,8 @@ export class FirestoreIndexes { f.order = field.order; } else if (field.arrayConfig) { f.arrayConfig = field.arrayConfig; - } else if (field.mode === API.Mode.ARRAY_CONTAINS) { - f.arrayConfig = API.ArrayConfig.CONTAINS; + } else if (field.mode === types.Mode.ARRAY_CONTAINS) { + f.arrayConfig = types.ArrayConfig.CONTAINS; } else { f.order = field.mode; } @@ -596,18 +663,145 @@ export class FirestoreIndexes { return result; } + /** + * List all databases that exist on a given project. + * @param project the Firebase project id. + */ + async listDatabases(project: string): Promise { + const url = `/projects/${project}/databases`; + const res = await this.apiClient.get<{ databases?: types.DatabaseResp[] }>(url); + const databases = res.body.databases; + if (!databases) { + return []; + } + + return databases; + } + + /** + * List all locations that exist on a given project. + * @param project the Firebase project id. + */ + async locations(project: string): Promise { + const url = `/projects/${project}/locations`; + const res = await this.apiClient.get<{ locations?: types.Location[] }>(url); + const locations = res.body.locations; + if (!locations) { + return []; + } + + return locations; + } + + /** + * Get info on a Firestore database. + * @param project the Firebase project id. + * @param databaseId the id of the Firestore Database + */ + async getDatabase(project: string, databaseId: string): Promise { + const url = `/projects/${project}/databases/${databaseId}`; + const res = await this.apiClient.get(url); + const database = res.body; + if (!database) { + throw new FirebaseError("Not found"); + } + + return database; + } + + /** + * Create a named Firestore Database + * @param project the Firebase project id. + * @param databaseId the name of the Firestore Database + * @param locationId the id of the region the database will be created in + * @param type FIRESTORE_NATIVE or DATASTORE_MODE + * @param deleteProtectionState DELETE_PROTECTION_ENABLED or DELETE_PROTECTION_DISABLED + */ + async createDatabase( + project: string, + databaseId: string, + locationId: string, + type: types.DatabaseType, + deleteProtectionState: types.DatabaseDeleteProtectionState + ): Promise { + const url = `/projects/${project}/databases`; + const payload: types.DatabaseReq = { + type, + locationId, + deleteProtectionState, + }; + const options = { queryParams: { databaseId: databaseId } }; + const res = await this.apiClient.post( + url, + payload, + options + ); + const database = res.body.response; + if (!database) { + throw new FirebaseError("Not found"); + } + + return database; + } + + /** + * Update a named Firestore Database + * @param project the Firebase project id. + * @param databaseId the name of the Firestore Database + * @param type FIRESTORE_NATIVE or DATASTORE_MODE + * @param deleteProtectionState DELETE_PROTECTION_ENABLED or DELETE_PROTECTION_DISABLED + */ + async updateDatabase( + project: string, + databaseId: string, + type?: types.DatabaseType, + deleteProtectionState?: types.DatabaseDeleteProtectionState + ): Promise { + const url = `/projects/${project}/databases/${databaseId}`; + const payload: types.DatabaseReq = { + type, + deleteProtectionState, + }; + const res = await this.apiClient.patch( + url, + payload + ); + const database = res.body.response; + if (!database) { + throw new FirebaseError("Not found"); + } + + return database; + } + + /** + * Delete a Firestore Database + * @param project the Firebase project id. + * @param databaseId the name of the Firestore Database + */ + async deleteDatabase(project: string, databaseId: string): Promise { + const url = `/projects/${project}/databases/${databaseId}`; + const res = await this.apiClient.delete<{ response?: types.DatabaseResp }>(url); + const database = res.body.response; + if (!database) { + throw new FirebaseError("Not found"); + } + + return database as types.DatabaseResp; + } + /** * Get a colored, pretty-printed representation of an index. */ - private prettyIndexString(index: API.Index, includeState: boolean = true): string { + private prettyIndexString(index: types.Index, includeState: boolean = true): string { let result = ""; if (index.state && includeState) { const stateMsg = `[${index.state}] `; - if (index.state === API.State.READY) { + if (index.state === types.State.READY) { result += clc.green(stateMsg); - } else if (index.state === API.State.CREATING) { + } else if (index.state === types.State.CREATING) { result += clc.yellow(stateMsg); } else { result += clc.red(stateMsg); @@ -633,10 +827,17 @@ export class FirestoreIndexes { return result; } + /** + * Get a colored, pretty-printed representation of a database + */ + prettyDatabaseString(database: types.DatabaseResp): string { + return clc.yellow(database.name); + } + /** * Get a colored, pretty-printed representation of a field */ - private prettyFieldString(field: API.Field): string { + private prettyFieldString(field: types.Field): string { let result = ""; const parsedName = util.parseFieldName(field.name); diff --git a/src/firestore/options.ts b/src/firestore/options.ts index 68e7b416691..e27d83896f6 100644 --- a/src/firestore/options.ts +++ b/src/firestore/options.ts @@ -1,4 +1,5 @@ import { Options } from "../options"; +import * as types from "../firestore/api-types"; /** * The set of fields that the Firestore commands need from Options. @@ -12,4 +13,7 @@ export interface FirestoreOptions extends Options { allCollections?: boolean; shallow?: boolean; recursive?: boolean; + location?: string; + type?: types.DatabaseType; + deleteProtection?: types.DatabaseDeleteProtectionStateOption; } diff --git a/src/init/features/firestore/indexes.ts b/src/init/features/firestore/indexes.ts index 268a10ca06b..4d6aa0afd02 100644 --- a/src/init/features/firestore/indexes.ts +++ b/src/init/features/firestore/indexes.ts @@ -2,12 +2,12 @@ import * as clc from "colorette"; import * as fs from "fs"; import { FirebaseError } from "../../../error"; -import * as iv2 from "../../../firestore/indexes"; +import * as api from "../../../firestore/api"; import * as fsutils from "../../../fsutils"; import { prompt, promptOnce } from "../../../prompt"; import { logger } from "../../../logger"; -const indexes = new iv2.FirestoreIndexes(); +const indexes = new api.FirestoreApi(); const INDEXES_TEMPLATE = fs.readFileSync( __dirname + "/../../../../templates/init/firestore/firestore.indexes.json", diff --git a/src/test/firestore/indexes.spec.ts b/src/test/firestore/indexes.spec.ts index 6af149a6a13..388bc01d39d 100644 --- a/src/test/firestore/indexes.spec.ts +++ b/src/test/firestore/indexes.spec.ts @@ -1,11 +1,11 @@ import { expect } from "chai"; -import { FirestoreIndexes } from "../../firestore/indexes"; +import { FirestoreApi } from "../../firestore/api"; import { FirebaseError } from "../../error"; -import * as API from "../../firestore/indexes-api"; -import * as Spec from "../../firestore/indexes-spec"; -import * as sort from "../../firestore/indexes-sort"; +import * as API from "../../firestore/api-types"; +import * as Spec from "../../firestore/api-spec"; +import * as sort from "../../firestore/api-sort"; -const idx = new FirestoreIndexes(); +const idx = new FirestoreApi(); const VALID_SPEC = { indexes: [ From d1244eeaddaac6af17882bf48b44fc375b176ac8 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 17 Apr 2023 12:46:27 -0700 Subject: [PATCH 0880/1699] Adding extensions to firebase init (#5701) * Adding extensions to firebase init * changelog * Formats --- CHANGELOG.md | 13 +++++------ src/commands/ext-install.ts | 2 +- src/commands/ext-update.ts | 2 +- src/commands/init.ts | 5 +++++ src/extensions/extensionsHelper.ts | 24 +-------------------- src/extensions/manifest.ts | 31 +++++++++++++++++++++++++-- src/init/features/extensions/index.ts | 17 +++++++++++++++ src/init/features/index.ts | 1 + src/init/index.ts | 1 + src/prompt.ts | 23 ++++++++++++++++++++ 10 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 src/init/features/extensions/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 375be31d767..e35ddc5d78b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ - Adds new commands for provisioning and managing Firestore databases: (#5616) -- firestore:databases:list -- firestore:databases:create -- firestore:databases:get -- firestore:databases:update -- firestore:databases:delete -- firestore:locations + - firestore:databases:list + - firestore:databases:create + - firestore:databases:get + - firestore:databases:update + - firestore:databases:delete + - firestore:locations +- Adds `extensions` as an option in `firebase init`. - Relaxed repo URI validation in ext:dev:publish (#5698). diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index c03125256ff..321be035219 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -14,7 +14,6 @@ import * as refs from "../extensions/refs"; import { displayWarningPrompts } from "../extensions/warnings"; import * as paramHelper from "../extensions/paramHelper"; import { - confirm, createSourceFromLocation, ensureExtensionsApiEnabled, logPrefix, @@ -25,6 +24,7 @@ import { isLocalPath, canonicalizeRefInput, } from "../extensions/extensionsHelper"; +import { confirm } from "../prompt"; import { getRandomString } from "../extensions/utils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index eba16f4d652..8f84216d22f 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -11,7 +11,6 @@ import { logPrefix, getSourceOrigin, SourceOrigin, - confirm, diagnoseAndFixProject, isLocalPath, } from "../extensions/extensionsHelper"; @@ -19,6 +18,7 @@ import * as paramHelper from "../extensions/paramHelper"; import { inferUpdateSource } from "../extensions/updateHelper"; import * as refs from "../extensions/refs"; import { getProjectId } from "../projectUtils"; +import { confirm } from "../prompt"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; import * as experiments from "../experiments"; diff --git a/src/commands/init.ts b/src/commands/init.ts index 0fd07913955..9a306947dc4 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -65,6 +65,11 @@ const choices = [ name: "Remote Config: Configure a template file for Remote Config", checked: false, }, + { + value: "extensions", + name: "Extensions: Set up an empty Extensions manifest", + checked: false, + }, ]; const featureNames = choices.map((choice) => choice.value); diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 93034213044..4539deb3e2c 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -36,7 +36,7 @@ import { import { Extension, ExtensionSource, ExtensionSpec, ExtensionVersion, Param } from "./types"; import * as refs from "./refs"; import { EXTENSIONS_SPEC_FILE, readFile, getLocalExtensionSpec } from "./localHelper"; -import { promptOnce } from "../prompt"; +import { confirm, promptOnce } from "../prompt"; import { logger } from "../logger"; import { envOverride } from "../utils"; import { getLocalChangelog } from "./change-log"; @@ -1037,28 +1037,6 @@ export function getSourceOrigin(sourceOrVersion: string): SourceOrigin { ); } -/** - * Confirm if the user wants to continue - */ -export async function confirm(args: { - nonInteractive?: boolean; - force?: boolean; - default?: boolean; -}): Promise { - if (!args.nonInteractive && !args.force) { - const message = `Do you wish to continue?`; - return await promptOnce({ - type: "confirm", - message, - default: args.default, - }); - } else if (args.nonInteractive && !args.force) { - throw new FirebaseError("Pass the --force flag to use this command in non-interactive mode"); - } else { - return true; - } -} - export async function diagnoseAndFixProject(options: any): Promise { const projectId = getProjectId(options); if (!projectId) { diff --git a/src/extensions/manifest.ts b/src/extensions/manifest.ts index cb3f3c2222b..ff13e3159f5 100644 --- a/src/extensions/manifest.ts +++ b/src/extensions/manifest.ts @@ -1,10 +1,12 @@ import * as clc from "colorette"; import * as path from "path"; +import * as fs from "fs-extra"; + import * as refs from "./refs"; import { Config } from "../config"; import { getExtensionSpec, ManifestInstanceSpec } from "../deploy/extensions/planner"; import { logger } from "../logger"; -import { promptOnce } from "../prompt"; +import { confirm, promptOnce } from "../prompt"; import { readEnvFile } from "./paramHelper"; import { FirebaseError } from "../error"; import * as utils from "../utils"; @@ -60,6 +62,31 @@ export async function writeToManifest( await writeLocalSecrets(specs, config, options.force); } +export async function writeEmptyManifest( + config: Config, + options: { nonInteractive: boolean; force: boolean } +): Promise { + if (!fs.existsSync(config.path("extensions"))) { + fs.mkdirSync(config.path("extensions")); + } + if (config.has("extensions") && Object.keys(config.get("extensions")).length) { + const currentExtensions = Object.entries(config.get("extensions")) + .map((i) => `${i[0]}: ${i[1]}`) + .join("\n\t"); + if ( + !(await confirm({ + message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite them?`, + nonInteractive: options.nonInteractive, + force: options.force, + default: false, + })) + ) { + return; + } + } + config.set("extensions", {}); +} + /** * Write the secrets in a list of ManifestInstanceSpec into extensions/{instance-id}.secret.local. * @@ -173,7 +200,7 @@ export function getInstanceRef(instanceId: string, config: Config): refs.Ref { return refs.parse(source); } -function writeExtensionsToFirebaseJson(specs: ManifestInstanceSpec[], config: Config): void { +export function writeExtensionsToFirebaseJson(specs: ManifestInstanceSpec[], config: Config): void { const extensions = config.get("extensions", {}); for (const s of specs) { let target; diff --git a/src/init/features/extensions/index.ts b/src/init/features/extensions/index.ts new file mode 100644 index 00000000000..569d18ecf1b --- /dev/null +++ b/src/init/features/extensions/index.ts @@ -0,0 +1,17 @@ +import { requirePermissions } from "../../../requirePermissions"; +import { Options } from "../../../options"; +import { ensure } from "../../../ensureApiEnabled"; +import { Config } from "../../../config"; +import * as manifest from "../../../extensions/manifest"; + +/** + * Set up a new firebase project for extensions. + */ +export async function doSetup(setup: any, config: Config, options: Options): Promise { + const projectId = setup?.rcfile?.projects?.default; + if (projectId) { + await requirePermissions({ ...options, project: projectId }); + await Promise.all([ensure(projectId, "firebaseextensions.googleapis.com", "unused", true)]); + } + return manifest.writeEmptyManifest(config, options); +} diff --git a/src/init/features/index.ts b/src/init/features/index.ts index ef4f70fb4be..a52bfd6f400 100644 --- a/src/init/features/index.ts +++ b/src/init/features/index.ts @@ -5,6 +5,7 @@ export { doSetup as functions } from "./functions"; export { doSetup as hosting } from "./hosting"; export { doSetup as storage } from "./storage"; export { doSetup as emulators } from "./emulators"; +export { doSetup as extensions } from "./extensions"; // always runs, sets up .firebaserc export { doSetup as project } from "./project"; export { doSetup as remoteconfig } from "./remoteconfig"; diff --git a/src/init/index.ts b/src/init/index.ts index 230682a6712..19565c19769 100644 --- a/src/init/index.ts +++ b/src/init/index.ts @@ -25,6 +25,7 @@ const featureFns = new Map P ["hosting", features.hosting], ["storage", features.storage], ["emulators", features.emulators], + ["extensions", features.extensions], ["project", features.project], // always runs, sets up .firebaserc ["remoteconfig", features.remoteconfig], ["hosting:github", features.hostingGithub], diff --git a/src/prompt.ts b/src/prompt.ts index 174688d4b1e..e636ea923df 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -97,3 +97,26 @@ export async function promptOnce
(question: Question, options: Options = {}): await prompt(options, [question]); return options[question.name]; } + +/** + * Confirm if the user wants to continue + */ +export async function confirm(args: { + nonInteractive?: boolean; + force?: boolean; + default?: boolean; + message?: string; +}): Promise { + if (!args.nonInteractive && !args.force) { + const message = args.message ?? `Do you wish to continue?`; + return await promptOnce({ + type: "confirm", + message, + default: args.default, + }); + } else if (args.nonInteractive && !args.force) { + throw new FirebaseError("Pass the --force flag to use this command in non-interactive mode"); + } else { + return true; + } +} From fd879c2991acc52b89115c1abc347b8160b8a747 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Mon, 17 Apr 2023 17:57:50 -0400 Subject: [PATCH 0881/1699] Added secret manager API enablement on ext:install (#5702) --- CHANGELOG.md | 1 + src/commands/ext-install.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e35ddc5d78b..e1609f633a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,4 @@ - firestore:locations - Adds `extensions` as an option in `firebase init`. - Relaxed repo URI validation in ext:dev:publish (#5698). +- Added Secret Manager API enablement during ext:install for extensions that use secrets (#5702). diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 321be035219..c8e9c901282 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -11,6 +11,7 @@ import { getProjectId, needProjectId } from "../projectUtils"; import * as extensionsApi from "../extensions/extensionsApi"; import { ExtensionVersion, ExtensionSource } from "../extensions/types"; import * as refs from "../extensions/refs"; +import * as secretsUtils from "../extensions/secretsUtils"; import { displayWarningPrompts } from "../extensions/warnings"; import * as paramHelper from "../extensions/paramHelper"; import { @@ -197,6 +198,10 @@ async function installToManifest(options: InstallExtensionOptions): Promise Date: Mon, 17 Apr 2023 19:42:51 -0400 Subject: [PATCH 0882/1699] Enable secret manager API during ext:update (#5703) --- CHANGELOG.md | 2 +- src/commands/ext-update.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1609f633a9..ca94c29d69c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,4 +7,4 @@ - firestore:locations - Adds `extensions` as an option in `firebase init`. - Relaxed repo URI validation in ext:dev:publish (#5698). -- Added Secret Manager API enablement during ext:install for extensions that use secrets (#5702). +- Enable Secret Manager API during ext:install/update for extensions that use secrets (#5702). diff --git a/src/commands/ext-update.ts b/src/commands/ext-update.ts index 8f84216d22f..0fe6b24204a 100644 --- a/src/commands/ext-update.ts +++ b/src/commands/ext-update.ts @@ -16,6 +16,7 @@ import { } from "../extensions/extensionsHelper"; import * as paramHelper from "../extensions/paramHelper"; import { inferUpdateSource } from "../extensions/updateHelper"; +import * as secretsUtils from "../extensions/secretsUtils"; import * as refs from "../extensions/refs"; import { getProjectId } from "../projectUtils"; import { confirm } from "../prompt"; @@ -114,6 +115,10 @@ export const command = new Command("ext:update [updateSour return; } + if (secretsUtils.usesSecrets(newExtensionVersion.spec)) { + await secretsUtils.ensureSecretManagerApiEnabled(options); + } + const oldParamValues = manifest.readInstanceParam({ instanceId, projectDir: config.projectDir, From 763fcd64fc90fda527f88936bdeead5e2f0889dc Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 18 Apr 2023 18:44:40 +0000 Subject: [PATCH 0883/1699] 11.28.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 99ff5892da7..84510929860 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "11.27.0", + "version": "11.28.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "11.27.0", + "version": "11.28.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 95ac7468201..a3d5b4ac856 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "11.27.0", + "version": "11.28.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From a4903ed02656eef1135ca600ebb58e5d97bd92c6 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 18 Apr 2023 18:44:53 +0000 Subject: [PATCH 0884/1699] [firebase-release] Removed change log and reset repo after 11.28.0 release --- CHANGELOG.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca94c29d69c..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +0,0 @@ -- Adds new commands for provisioning and managing Firestore databases: (#5616) - - firestore:databases:list - - firestore:databases:create - - firestore:databases:get - - firestore:databases:update - - firestore:databases:delete - - firestore:locations -- Adds `extensions` as an option in `firebase init`. -- Relaxed repo URI validation in ext:dev:publish (#5698). -- Enable Secret Manager API during ext:install/update for extensions that use secrets (#5702). From b80de641dae17d4866b6fa3dead8a18396cde1d5 Mon Sep 17 00:00:00 2001 From: Jeff <3759507+jhuleatt@users.noreply.github.com> Date: Tue, 18 Apr 2023 15:25:36 -0400 Subject: [PATCH 0885/1699] Use the same secret label on create and update (#5704) * always use the `firebase-managed` label * add a test --- CHANGELOG.md | 1 + src/deploy/functions/params.ts | 4 ++-- src/functions/secrets.ts | 8 ++++---- src/test/functions/secrets.spec.ts | 11 +++++++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..5f2ef7cf783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fix an issue where Secret Manager secrets were tagged incorrectly (#5704). diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 18c000e5683..b8981a4a4f0 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -7,6 +7,7 @@ import * as secretManager from "../../gcp/secretManager"; import { listBuckets } from "../../gcp/storage"; import { isCelExpression, resolveExpression } from "./cel"; import { FirebaseConfig } from "./args"; +import { labels as secretLabels } from "../../functions/secrets"; // A convinience type containing options for Prompt's select interface ListItem { @@ -457,8 +458,7 @@ async function handleSecret(secretParam: SecretParam, projectId: string) { secretParam.name }. Enter a value for ${secretParam.label || secretParam.name}:`, }); - const secretLabel: Record = { "firebase-hosting-managed": "yes" }; - await secretManager.createSecret(projectId, secretParam.name, secretLabel); + await secretManager.createSecret(projectId, secretParam.name, secretLabels()); await secretManager.addVersion(projectId, secretParam.name, secretValue); return secretValue; } else if (!metadata.secretVersion) { diff --git a/src/functions/secrets.ts b/src/functions/secrets.ts index ed7abcb2db1..f2cf45e5e4d 100644 --- a/src/functions/secrets.ts +++ b/src/functions/secrets.ts @@ -23,7 +23,7 @@ import { logger } from "../logger"; import { functionsOrigin } from "../api"; import { assertExhaustive } from "../functional"; -const FIREBASE_MANGED = "firebase-managed"; +const FIREBASE_MANAGED = "firebase-managed"; type ProjectInfo = { projectId: string; @@ -34,7 +34,7 @@ type ProjectInfo = { * Returns true if secret is managed by Firebase. */ export function isFirebaseManaged(secret: Secret): boolean { - return Object.keys(secret.labels || []).includes(FIREBASE_MANGED); + return Object.keys(secret.labels || []).includes(FIREBASE_MANAGED); } /** @@ -42,7 +42,7 @@ export function isFirebaseManaged(secret: Secret): boolean { * @internal */ export function labels(): Record { - return { [FIREBASE_MANGED]: "true" }; + return { [FIREBASE_MANAGED]: "true" }; } function toUpperSnakeCase(key: string): string { @@ -173,7 +173,7 @@ export async function pruneSecrets( const prunedSecrets: Set = new Set(); // Collect all Firebase managed secret versions - const haveSecrets = await listSecrets(projectId, `labels.${FIREBASE_MANGED}=true`); + const haveSecrets = await listSecrets(projectId, `labels.${FIREBASE_MANAGED}=true`); for (const secret of haveSecrets) { const versions = await listSecretVersions(projectId, secret.name, `NOT state: DESTROYED`); for (const version of versions) { diff --git a/src/test/functions/secrets.spec.ts b/src/test/functions/secrets.spec.ts index 5407de8111d..4873be2a0c2 100644 --- a/src/test/functions/secrets.spec.ts +++ b/src/test/functions/secrets.spec.ts @@ -124,6 +124,17 @@ describe("functions/secret", () => { expect(promptStub).to.have.been.calledOnce; }); + it("does not prompt user to have Firebase manage the secret if already managed by Firebase", async () => { + getStub.resolves({ ...secret, labels: secrets.labels() }); + patchStub.resolves(secret); + + await expect( + secrets.ensureSecret("project-id", "MY_SECRET", options) + ).to.eventually.deep.equal(secret); + expect(warnStub).not.to.have.been.calledOnce; + expect(promptStub).not.to.have.been.calledOnce; + }); + it("creates a new secret if it doesn't exists", async () => { getStub.rejects({ status: 404 }); createStub.resolves(secret); From 82f829653b997ba3bf4e29565a70a4c3bb034747 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 18 Apr 2023 17:16:53 -0700 Subject: [PATCH 0886/1699] Breaking changes to ext:dev:* commands (#5668) * Breaking changes to ext:dev:* commands * fix build issues * formats * Fixing repeat import * Add back in ext-dev-publish, finish implementing delete * formats * pr fixes --- src/commands/ext-dev-deprecate.ts | 177 ++++++++++++++++------- src/commands/ext-dev-emulators-exec.ts | 29 ---- src/commands/ext-dev-emulators-start.ts | 25 ---- src/commands/ext-dev-extension-delete.ts | 59 -------- src/commands/ext-dev-publish.ts | 67 ++------- src/commands/ext-dev-unpublish.ts | 66 --------- src/commands/ext-dev-upload.ts | 94 ++++++++++++ src/commands/ext-install.ts | 10 +- src/commands/ext-sources-create.ts | 32 ---- src/commands/ext-update.ts | 16 +- src/commands/index.ts | 28 ++-- src/experiments.ts | 10 -- 12 files changed, 241 insertions(+), 372 deletions(-) delete mode 100644 src/commands/ext-dev-emulators-exec.ts delete mode 100644 src/commands/ext-dev-emulators-start.ts delete mode 100644 src/commands/ext-dev-extension-delete.ts delete mode 100644 src/commands/ext-dev-unpublish.ts create mode 100644 src/commands/ext-dev-upload.ts delete mode 100644 src/commands/ext-sources-create.ts diff --git a/src/commands/ext-dev-deprecate.ts b/src/commands/ext-dev-deprecate.ts index c973de18263..cf62cccc106 100644 --- a/src/commands/ext-dev-deprecate.ts +++ b/src/commands/ext-dev-deprecate.ts @@ -4,77 +4,146 @@ import * as semver from "semver"; import * as refs from "../extensions/refs"; import * as utils from "../utils"; import { Command } from "../command"; -import { promptOnce } from "../prompt"; +import { confirm } from "../prompt"; import { ensureExtensionsApiEnabled, logPrefix } from "../extensions/extensionsHelper"; -import { deprecateExtensionVersion, listExtensionVersions } from "../extensions/extensionsApi"; +import { + deleteExtension, + deprecateExtensionVersion, + getExtension, + listExtensionVersions, +} from "../extensions/extensionsApi"; import { parseVersionPredicate } from "../extensions/versionHelper"; import { requireAuth } from "../requireAuth"; import { FirebaseError } from "../error"; +import { Options } from "../options"; + +interface ExtDevDeprecateOptions extends Options { + delete: boolean; + message: string; +} /** * Deprecate all extension versions that match the version predicate. */ -export const command = new Command("ext:dev:deprecate ") +export const command = new Command("ext:dev:deprecate [versionPredicate]") .description("deprecate extension versions that match the version predicate") .option("-m, --message ", "deprecation message") + .option("-d, --delete", "delete the extension instead of deprecating it") .option( "-f, --force", "override deprecation message for existing deprecated extension versions that match" ) .before(requireAuth) .before(ensureExtensionsApiEnabled) - .action(async (extensionRef: string, versionPredicate: string, options: any) => { - const { publisherId, extensionId, version } = refs.parse(extensionRef); - if (version) { - throw new FirebaseError( - `The input extension reference must be of the format ${clc.bold( - "/" - )}. Version should be supplied in the version predicate argument.` - ); - } - if (!publisherId || !extensionId) { - throw new FirebaseError( - `Error parsing publisher ID and extension ID from extension reference '${clc.bold( - extensionRef - )}'. Please use the format '${clc.bold("/")}'.` - ); + .action( + async (extensionRef: string, versionPredicate: string, options: ExtDevDeprecateOptions) => { + const ref = refs.parse(extensionRef); + if (options.delete) { + return deleteExt(ref, versionPredicate, options); + } else { + return deprecate(ref, versionPredicate, options); + } } + ); + +async function deprecate( + extensionRef: refs.Ref, + versionPredicate: string, + options: ExtDevDeprecateOptions +) { + const { publisherId, extensionId, version } = extensionRef; + if (version) { + throw new FirebaseError( + `The input extension reference must be of the format ${clc.bold( + "/" + )}. Version should be supplied in the version predicate argument.` + ); + } + if (!publisherId || !extensionId) { + throw new FirebaseError( + `Error parsing publisher ID and extension ID from extension reference '${clc.bold( + refs.toExtensionRef(extensionRef) + )}'. Please use the format '${clc.bold("/")}'.` + ); + } + + let filter = ""; + if (versionPredicate) { const { comparator, targetSemVer } = parseVersionPredicate(versionPredicate); - const filter = `id${comparator}"${targetSemVer}"`; - const extensionVersions = await listExtensionVersions(extensionRef, filter); - const filteredExtensionVersions = extensionVersions - .sort((ev1, ev2) => { - return -semver.compare(ev1.spec.version, ev2.spec.version); - }) - .filter((extensionVersion) => { - if (extensionVersion.state === "DEPRECATED" && !options.force) { - return false; - } - const message = - extensionVersion.state === "DEPRECATED" ? ", will overwrite deprecation message" : ""; - utils.logLabeledBullet(extensionVersion.ref, extensionVersion.state + message); - return true; - }); - if (filteredExtensionVersions.length > 0) { - if (!options.force) { - const confirmMessage = - "You are about to deprecate these extension version(s). Do you wish to continue?"; - const consent = await promptOnce({ - type: "confirm", - message: confirmMessage, - default: false, - }); - if (!consent) { - throw new FirebaseError("Deprecation canceled."); - } + filter = `id${comparator}"${targetSemVer}"`; + } + const extensionVersions = await listExtensionVersions(refs.toExtensionRef(extensionRef), filter); + const filteredExtensionVersions = extensionVersions + .sort((ev1, ev2) => { + return -semver.compare(ev1.spec.version, ev2.spec.version); + }) + .filter((extensionVersion) => { + if (extensionVersion.state === "DEPRECATED" && !options.force) { + return false; } - } else { - throw new FirebaseError("No extension versions matched the version predicate."); + const message = + extensionVersion.state === "DEPRECATED" ? ", will overwrite deprecation message" : ""; + // TODO: This should not print out PUBLISHED since that means something else now. + utils.logLabeledBullet(extensionVersion.ref, extensionVersion.state + message); + return true; + }); + if (filteredExtensionVersions.length > 0) { + const consent = await confirm({ + default: false, + force: options.force, + nonInteractive: options.nonInteractive, + }); + if (!consent) { + throw new FirebaseError("Deprecation canceled."); } - await utils.allSettled( - filteredExtensionVersions.map(async (extensionVersion) => { - await deprecateExtensionVersion(extensionVersion.ref, options.deprecationMessage); - }) - ); - utils.logLabeledSuccess(logPrefix, "successfully deprecated extension version(s)."); - }); + } else { + throw new FirebaseError("No extension versions matched the version predicate."); + } + await utils.allSettled( + filteredExtensionVersions.map(async (extensionVersion) => { + await deprecateExtensionVersion(extensionVersion.ref, options.message); + }) + ); + utils.logLabeledSuccess(logPrefix, "successfully deprecated extension version(s)."); +} + +async function deleteExt( + extensionRef: refs.Ref, + versionPredicate: string, + options: ExtDevDeprecateOptions +) { + if (versionPredicate) { + throw new FirebaseError("Deleting specific extension versions is not supported."); + } + const extRef = refs.toExtensionRef(extensionRef); + utils.logLabeledWarning( + logPrefix, + "If you delete this extension, developers won't be able to install it. " + + "For developers who currently have this extension installed, " + + "it will continue to run and will appear as unpublished when " + + "listed in the Firebase console or Firebase CLI. " + + "In most cases, you should deprecate your extension instead of deleting it." + ); + utils.logLabeledWarning( + logPrefix, + `This is a permanent action. Once deleted, you may never use the extension name '${clc.bold( + extRef + )}' again.` + ); + await getExtension(refs.toExtensionRef(extensionRef)); + utils.logLabeledWarning( + logPrefix, + `You are about to delete ALL versions of ${clc.green(extRef)}` + ); + if ( + !(await confirm({ + default: false, + force: options.force, + nonInteractive: options.nonInteractive, + })) + ) { + throw new FirebaseError("deletion cancelled."); + } + await deleteExtension(extRef); + utils.logLabeledSuccess(logPrefix, `successfully deleted ${extRef}}`); +} diff --git a/src/commands/ext-dev-emulators-exec.ts b/src/commands/ext-dev-emulators-exec.ts deleted file mode 100644 index 497d3aabd51..00000000000 --- a/src/commands/ext-dev-emulators-exec.ts +++ /dev/null @@ -1,29 +0,0 @@ -// TODO(joehanley): Remove this entire command in v12. -import * as clc from "colorette"; - -import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; -import { Command } from "../command"; -import { FirebaseError } from "../error"; -import * as commandUtils from "../emulator/commandUtils"; - -export const command = new Command("ext:dev:emulators:exec - - diff --git a/scripts/frameworks-tests/vite-project/javascript.svg b/scripts/frameworks-tests/vite-project/javascript.svg deleted file mode 100644 index f9abb2b728d..00000000000 --- a/scripts/frameworks-tests/vite-project/javascript.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/scripts/frameworks-tests/vite-project/main.js b/scripts/frameworks-tests/vite-project/main.js deleted file mode 100644 index 727b4ea209e..00000000000 --- a/scripts/frameworks-tests/vite-project/main.js +++ /dev/null @@ -1,23 +0,0 @@ -import './style.css' -import javascriptLogo from './javascript.svg' -import { setupCounter } from './counter.js' - -document.querySelector('#app').innerHTML = ` -
- - - - - - -

Hello Vite!

-
- -
-

- Click on the Vite logo to learn more -

-
-` - -setupCounter(document.querySelector('#counter')) diff --git a/scripts/frameworks-tests/vite-project/package-lock.json b/scripts/frameworks-tests/vite-project/package-lock.json deleted file mode 100644 index a33faf3c34c..00000000000 --- a/scripts/frameworks-tests/vite-project/package-lock.json +++ /dev/null @@ -1,881 +0,0 @@ -{ - "name": "vite-project", - "version": "0.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "vite-project", - "version": "0.0.0", - "devDependencies": { - "vite": "^3.1.0" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.10.tgz", - "integrity": "sha512-FNONeQPy/ox+5NBkcSbYJxoXj9GWu8gVGJTVmUyoOCKQFDTrHVKgNSzChdNt0I8Aj/iKcsDf2r9BFwv+FSNUXg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.10.tgz", - "integrity": "sha512-w0Ou3Z83LOYEkwaui2M8VwIp+nLi/NA60lBLMvaJ+vXVMcsARYdEzLNE7RSm4+lSg4zq4d7fAVuzk7PNQ5JFgg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.10.tgz", - "integrity": "sha512-N7wBhfJ/E5fzn/SpNgX+oW2RLRjwaL8Y0ezqNqhjD6w0H2p0rDuEz2FKZqpqLnO8DCaWumKe8dsC/ljvVSSxng==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.15.10", - "@esbuild/linux-loong64": "0.15.10", - "esbuild-android-64": "0.15.10", - "esbuild-android-arm64": "0.15.10", - "esbuild-darwin-64": "0.15.10", - "esbuild-darwin-arm64": "0.15.10", - "esbuild-freebsd-64": "0.15.10", - "esbuild-freebsd-arm64": "0.15.10", - "esbuild-linux-32": "0.15.10", - "esbuild-linux-64": "0.15.10", - "esbuild-linux-arm": "0.15.10", - "esbuild-linux-arm64": "0.15.10", - "esbuild-linux-mips64le": "0.15.10", - "esbuild-linux-ppc64le": "0.15.10", - "esbuild-linux-riscv64": "0.15.10", - "esbuild-linux-s390x": "0.15.10", - "esbuild-netbsd-64": "0.15.10", - "esbuild-openbsd-64": "0.15.10", - "esbuild-sunos-64": "0.15.10", - "esbuild-windows-32": "0.15.10", - "esbuild-windows-64": "0.15.10", - "esbuild-windows-arm64": "0.15.10" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.10.tgz", - "integrity": "sha512-UI7krF8OYO1N7JYTgLT9ML5j4+45ra3amLZKx7LO3lmLt1Ibn8t3aZbX5Pu4BjWiqDuJ3m/hsvhPhK/5Y/YpnA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.10.tgz", - "integrity": "sha512-EOt55D6xBk5O05AK8brXUbZmoFj4chM8u3riGflLa6ziEoVvNjRdD7Cnp82NHQGfSHgYR06XsPI8/sMuA/cUwg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.10.tgz", - "integrity": "sha512-hbDJugTicqIm+WKZgp208d7FcXcaK8j2c0l+fqSJ3d2AzQAfjEYDRM3Z2oMeqSJ9uFxyj/muSACLdix7oTstRA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.10.tgz", - "integrity": "sha512-M1t5+Kj4IgSbYmunf2BB6EKLkWUq+XlqaFRiGOk8bmBapu9bCDrxjf4kUnWn59Dka3I27EiuHBKd1rSO4osLFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.10.tgz", - "integrity": "sha512-KMBFMa7C8oc97nqDdoZwtDBX7gfpolkk6Bcmj6YFMrtCMVgoU/x2DI1p74DmYl7CSS6Ppa3xgemrLrr5IjIn0w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.10.tgz", - "integrity": "sha512-m2KNbuCX13yQqLlbSojFMHpewbn8wW5uDS6DxRpmaZKzyq8Dbsku6hHvh2U+BcLwWY4mpgXzFUoENEf7IcioGg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.10.tgz", - "integrity": "sha512-guXrwSYFAvNkuQ39FNeV4sNkNms1bLlA5vF1H0cazZBOLdLFIny6BhT+TUbK/hdByMQhtWQ5jI9VAmPKbVPu1w==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.10.tgz", - "integrity": "sha512-jd8XfaSJeucMpD63YNMO1JCrdJhckHWcMv6O233bL4l6ogQKQOxBYSRP/XLWP+6kVTu0obXovuckJDcA0DKtQA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.10.tgz", - "integrity": "sha512-6N8vThLL/Lysy9y4Ex8XoLQAlbZKUyExCWyayGi2KgTBelKpPgj6RZnUaKri0dHNPGgReJriKVU6+KDGQwn10A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.10.tgz", - "integrity": "sha512-GByBi4fgkvZFTHFDYNftu1DQ1GzR23jws0oWyCfhnI7eMOe+wgwWrc78dbNk709Ivdr/evefm2PJiUBMiusS1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.10.tgz", - "integrity": "sha512-BxP+LbaGVGIdQNJUNF7qpYjEGWb0YyHVSKqYKrn+pTwH/SiHUxFyJYSP3pqkku61olQiSBnSmWZ+YUpj78Tw7Q==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.10.tgz", - "integrity": "sha512-LoSQCd6498PmninNgqd/BR7z3Bsk/mabImBWuQ4wQgmQEeanzWd5BQU2aNi9mBURCLgyheuZS6Xhrw5luw3OkQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.10.tgz", - "integrity": "sha512-Lrl9Cr2YROvPV4wmZ1/g48httE8z/5SCiXIyebiB5N8VT7pX3t6meI7TQVHw/wQpqP/AF4SksDuFImPTM7Z32Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.10.tgz", - "integrity": "sha512-ReP+6q3eLVVP2lpRrvl5EodKX7EZ1bS1/z5j6hsluAlZP5aHhk6ghT6Cq3IANvvDdscMMCB4QEbI+AjtvoOFpA==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.10.tgz", - "integrity": "sha512-iGDYtJCMCqldMskQ4eIV+QSS/CuT7xyy9i2/FjpKvxAuCzrESZXiA1L64YNj6/afuzfBe9i8m/uDkFHy257hTw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.10.tgz", - "integrity": "sha512-ftMMIwHWrnrYnvuJQRJs/Smlcb28F9ICGde/P3FUTCgDDM0N7WA0o9uOR38f5Xe2/OhNCgkjNeb7QeaE3cyWkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.10.tgz", - "integrity": "sha512-mf7hBL9Uo2gcy2r3rUFMjVpTaGpFJJE5QTDDqUFf1632FxteYANffDZmKbqX0PfeQ2XjUDE604IcE7OJeoHiyg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.10.tgz", - "integrity": "sha512-ttFVo+Cg8b5+qHmZHbEc8Vl17kCleHhLzgT8X04y8zudEApo0PxPg9Mz8Z2cKH1bCYlve1XL8LkyXGFjtUYeGg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.10.tgz", - "integrity": "sha512-2H0gdsyHi5x+8lbng3hLbxDWR7mKHWh5BXZGKVG830KUmXOOWFE2YKJ4tHRkejRduOGDrBvHBriYsGtmTv3ntA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.10.tgz", - "integrity": "sha512-S+th4F+F8VLsHLR0zrUcG+Et4hx0RKgK1eyHc08kztmLOES8BWwMiaGdoW9hiXuzznXQ0I/Fg904MNbr11Nktw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "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 - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - } - ], - "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.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/rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/vite": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", - "integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==", - "dev": true, - "dependencies": { - "esbuild": "^0.15.9", - "postcss": "^8.4.16", - "resolve": "^1.22.1", - "rollup": "~2.78.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "less": "*", - "sass": "*", - "stylus": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "terser": { - "optional": true - } - } - } - }, - "dependencies": { - "@esbuild/android-arm": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.10.tgz", - "integrity": "sha512-FNONeQPy/ox+5NBkcSbYJxoXj9GWu8gVGJTVmUyoOCKQFDTrHVKgNSzChdNt0I8Aj/iKcsDf2r9BFwv+FSNUXg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.10.tgz", - "integrity": "sha512-w0Ou3Z83LOYEkwaui2M8VwIp+nLi/NA60lBLMvaJ+vXVMcsARYdEzLNE7RSm4+lSg4zq4d7fAVuzk7PNQ5JFgg==", - "dev": true, - "optional": true - }, - "esbuild": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.10.tgz", - "integrity": "sha512-N7wBhfJ/E5fzn/SpNgX+oW2RLRjwaL8Y0ezqNqhjD6w0H2p0rDuEz2FKZqpqLnO8DCaWumKe8dsC/ljvVSSxng==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.15.10", - "@esbuild/linux-loong64": "0.15.10", - "esbuild-android-64": "0.15.10", - "esbuild-android-arm64": "0.15.10", - "esbuild-darwin-64": "0.15.10", - "esbuild-darwin-arm64": "0.15.10", - "esbuild-freebsd-64": "0.15.10", - "esbuild-freebsd-arm64": "0.15.10", - "esbuild-linux-32": "0.15.10", - "esbuild-linux-64": "0.15.10", - "esbuild-linux-arm": "0.15.10", - "esbuild-linux-arm64": "0.15.10", - "esbuild-linux-mips64le": "0.15.10", - "esbuild-linux-ppc64le": "0.15.10", - "esbuild-linux-riscv64": "0.15.10", - "esbuild-linux-s390x": "0.15.10", - "esbuild-netbsd-64": "0.15.10", - "esbuild-openbsd-64": "0.15.10", - "esbuild-sunos-64": "0.15.10", - "esbuild-windows-32": "0.15.10", - "esbuild-windows-64": "0.15.10", - "esbuild-windows-arm64": "0.15.10" - } - }, - "esbuild-android-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.10.tgz", - "integrity": "sha512-UI7krF8OYO1N7JYTgLT9ML5j4+45ra3amLZKx7LO3lmLt1Ibn8t3aZbX5Pu4BjWiqDuJ3m/hsvhPhK/5Y/YpnA==", - "dev": true, - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.10.tgz", - "integrity": "sha512-EOt55D6xBk5O05AK8brXUbZmoFj4chM8u3riGflLa6ziEoVvNjRdD7Cnp82NHQGfSHgYR06XsPI8/sMuA/cUwg==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.10.tgz", - "integrity": "sha512-hbDJugTicqIm+WKZgp208d7FcXcaK8j2c0l+fqSJ3d2AzQAfjEYDRM3Z2oMeqSJ9uFxyj/muSACLdix7oTstRA==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.10.tgz", - "integrity": "sha512-M1t5+Kj4IgSbYmunf2BB6EKLkWUq+XlqaFRiGOk8bmBapu9bCDrxjf4kUnWn59Dka3I27EiuHBKd1rSO4osLFQ==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.10.tgz", - "integrity": "sha512-KMBFMa7C8oc97nqDdoZwtDBX7gfpolkk6Bcmj6YFMrtCMVgoU/x2DI1p74DmYl7CSS6Ppa3xgemrLrr5IjIn0w==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.10.tgz", - "integrity": "sha512-m2KNbuCX13yQqLlbSojFMHpewbn8wW5uDS6DxRpmaZKzyq8Dbsku6hHvh2U+BcLwWY4mpgXzFUoENEf7IcioGg==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.10.tgz", - "integrity": "sha512-guXrwSYFAvNkuQ39FNeV4sNkNms1bLlA5vF1H0cazZBOLdLFIny6BhT+TUbK/hdByMQhtWQ5jI9VAmPKbVPu1w==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.10.tgz", - "integrity": "sha512-jd8XfaSJeucMpD63YNMO1JCrdJhckHWcMv6O233bL4l6ogQKQOxBYSRP/XLWP+6kVTu0obXovuckJDcA0DKtQA==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.10.tgz", - "integrity": "sha512-6N8vThLL/Lysy9y4Ex8XoLQAlbZKUyExCWyayGi2KgTBelKpPgj6RZnUaKri0dHNPGgReJriKVU6+KDGQwn10A==", - "dev": true, - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.10.tgz", - "integrity": "sha512-GByBi4fgkvZFTHFDYNftu1DQ1GzR23jws0oWyCfhnI7eMOe+wgwWrc78dbNk709Ivdr/evefm2PJiUBMiusS1A==", - "dev": true, - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.10.tgz", - "integrity": "sha512-BxP+LbaGVGIdQNJUNF7qpYjEGWb0YyHVSKqYKrn+pTwH/SiHUxFyJYSP3pqkku61olQiSBnSmWZ+YUpj78Tw7Q==", - "dev": true, - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.10.tgz", - "integrity": "sha512-LoSQCd6498PmninNgqd/BR7z3Bsk/mabImBWuQ4wQgmQEeanzWd5BQU2aNi9mBURCLgyheuZS6Xhrw5luw3OkQ==", - "dev": true, - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.10.tgz", - "integrity": "sha512-Lrl9Cr2YROvPV4wmZ1/g48httE8z/5SCiXIyebiB5N8VT7pX3t6meI7TQVHw/wQpqP/AF4SksDuFImPTM7Z32Q==", - "dev": true, - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.10.tgz", - "integrity": "sha512-ReP+6q3eLVVP2lpRrvl5EodKX7EZ1bS1/z5j6hsluAlZP5aHhk6ghT6Cq3IANvvDdscMMCB4QEbI+AjtvoOFpA==", - "dev": true, - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.10.tgz", - "integrity": "sha512-iGDYtJCMCqldMskQ4eIV+QSS/CuT7xyy9i2/FjpKvxAuCzrESZXiA1L64YNj6/afuzfBe9i8m/uDkFHy257hTw==", - "dev": true, - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.10.tgz", - "integrity": "sha512-ftMMIwHWrnrYnvuJQRJs/Smlcb28F9ICGde/P3FUTCgDDM0N7WA0o9uOR38f5Xe2/OhNCgkjNeb7QeaE3cyWkQ==", - "dev": true, - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.10.tgz", - "integrity": "sha512-mf7hBL9Uo2gcy2r3rUFMjVpTaGpFJJE5QTDDqUFf1632FxteYANffDZmKbqX0PfeQ2XjUDE604IcE7OJeoHiyg==", - "dev": true, - "optional": true - }, - "esbuild-windows-32": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.10.tgz", - "integrity": "sha512-ttFVo+Cg8b5+qHmZHbEc8Vl17kCleHhLzgT8X04y8zudEApo0PxPg9Mz8Z2cKH1bCYlve1XL8LkyXGFjtUYeGg==", - "dev": true, - "optional": true - }, - "esbuild-windows-64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.10.tgz", - "integrity": "sha512-2H0gdsyHi5x+8lbng3hLbxDWR7mKHWh5BXZGKVG830KUmXOOWFE2YKJ4tHRkejRduOGDrBvHBriYsGtmTv3ntA==", - "dev": true, - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.10.tgz", - "integrity": "sha512-S+th4F+F8VLsHLR0zrUcG+Et4hx0RKgK1eyHc08kztmLOES8BWwMiaGdoW9hiXuzznXQ0I/Fg904MNbr11Nktw==", - "dev": true, - "optional": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true - }, - "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 - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", - "dev": true, - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "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 - }, - "vite": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", - "integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==", - "dev": true, - "requires": { - "esbuild": "^0.15.9", - "fsevents": "~2.3.2", - "postcss": "^8.4.16", - "resolve": "^1.22.1", - "rollup": "~2.78.0" - } - } - } -} diff --git a/scripts/frameworks-tests/vite-project/package.json b/scripts/frameworks-tests/vite-project/package.json deleted file mode 100644 index 4e55fa3ed80..00000000000 --- a/scripts/frameworks-tests/vite-project/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "vite-project", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "devDependencies": { - "vite": "^3.1.0" - } -} \ No newline at end of file diff --git a/scripts/frameworks-tests/vite-project/public/vite.svg b/scripts/frameworks-tests/vite-project/public/vite.svg deleted file mode 100644 index e7b8dfb1b2a..00000000000 --- a/scripts/frameworks-tests/vite-project/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/scripts/frameworks-tests/vite-project/style.css b/scripts/frameworks-tests/vite-project/style.css deleted file mode 100644 index 12320801d36..00000000000 --- a/scripts/frameworks-tests/vite-project/style.css +++ /dev/null @@ -1,97 +0,0 @@ -:root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.vanilla:hover { - filter: drop-shadow(0 0 2em #f7df1eaa); -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 64013e4603f..54a64b571cb 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -66,7 +66,7 @@ export const deploy = async function ( const config = options.config.get("hosting"); if (Array.isArray(config) ? config.some((it) => it.source) : config.source) { experiments.assertEnabled("webframeworks", "deploy a web framework from source"); - await prepareFrameworks(targetNames, context, options); + await prepareFrameworks("deploy", targetNames, context, options); } } diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index b1c8ea4a4a7..712288d7e2a 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -434,7 +434,7 @@ export async function emulatorExec(script: string, options: any): Promise let deprecationNotices; try { const showUI = !!options.ui; - ({ deprecationNotices } = await controller.startAll(options, showUI)); + ({ deprecationNotices } = await controller.startAll(options, showUI, true)); exitCode = await runScript(script, extraEnv); await controller.onExit(options); } finally { diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index a245b29eab9..089250f00c7 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -250,7 +250,8 @@ interface EmulatorOptions extends Options { */ export async function startAll( options: EmulatorOptions, - showUI = true + showUI = true, + runningTestScript = false ): Promise<{ deprecationNotices: string[] }> { // Emulators config is specified in firebase.json as: // "emulators": { @@ -463,7 +464,13 @@ export async function startAll( } } // This may add additional sources for Functions emulator and must be done before it. - await prepareFrameworks(targets, options, options, emulators); + await prepareFrameworks( + runningTestScript ? "test" : "emulate", + targets, + undefined, + options, + emulators + ); } const projectDir = (options.extDevDir || options.config.projectDir) as string; diff --git a/src/frameworks/angular/index.ts b/src/frameworks/angular/index.ts index 26faa0afd72..5c4dd4bdeec 100644 --- a/src/frameworks/angular/index.ts +++ b/src/frameworks/angular/index.ts @@ -98,7 +98,7 @@ export async function getDevModeHandle(dir: string, configuration: string) { const { targetStringFromTarget } = relativeRequire(dir, "@angular-devkit/architect"); const { serveTarget } = await getContext(dir, configuration); if (!serveTarget) throw new Error("Could not find the serveTarget"); - const host = new Promise((resolve) => { + const host = new Promise((resolve, reject) => { // Can't use scheduleTarget since that—like prerender—is failing on an ESM bug // will just grep for the hostname const cli = getNodeModuleBin("ng", dir); @@ -113,6 +113,7 @@ export async function getDevModeHandle(dir: string, configuration: string) { serve.stderr.on("data", (data: any) => { process.stderr.write(data); }); + serve.on("exit", reject); }); return simpleProxy(await host); } @@ -146,7 +147,7 @@ export async function getValidBuildTargets(purpose: BUILD_TARGET_PURPOSE, dir: s const validTargetNames = new Set(["development", "production"]); try { const { workspaceProject, browserTarget, serverTarget, serveTarget } = await getContext(dir); - const { target } = ((purpose === "serve" && serveTarget) || serverTarget || browserTarget)!; + const { target } = ((purpose === "emulate" && serveTarget) || serverTarget || browserTarget)!; const workspaceTarget = workspaceProject.targets.get(target)!; Object.keys(workspaceTarget.configurations || {}).forEach((it) => validTargetNames.add(it)); } catch (e) { diff --git a/src/frameworks/astro/index.ts b/src/frameworks/astro/index.ts index 76481ca61d0..b7d7d804d39 100644 --- a/src/frameworks/astro/index.ts +++ b/src/frameworks/astro/index.ts @@ -64,7 +64,7 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: st } export async function getDevModeHandle(cwd: string) { - const host = new Promise((resolve) => { + const host = new Promise((resolve, reject) => { const cli = getNodeModuleBin("astro", cwd); const serve = spawn(cli, ["dev"], { cwd }); serve.stdout.on("data", (data: any) => { @@ -75,6 +75,7 @@ export async function getDevModeHandle(cwd: string) { serve.stderr.on("data", (data: any) => { process.stderr.write(data); }); + serve.on("exit", reject); }); return simpleProxy(await host); } diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 713bd48b5fe..21be5fa8c73 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -5,7 +5,6 @@ import { sync as spawnSync } from "cross-spawn"; import { copyFile, readdir, readFile, rm, writeFile } from "fs/promises"; import { mkdirp, pathExists, stat } from "fs-extra"; import * as process from "node:process"; -import * as semver from "semver"; import * as glob from "glob"; import { needProjectId } from "../projectUtils"; @@ -42,10 +41,18 @@ import { VALID_ENGINES, WebFrameworks, } from "./constants"; -import { BUILD_TARGET_PURPOSE, BuildResult, FirebaseDefaults, Framework } from "./interfaces"; +import { + BUILD_TARGET_PURPOSE, + BuildResult, + FirebaseDefaults, + Framework, + FrameworkContext, + FrameworksOptions, +} from "./interfaces"; import { logWarning } from "../utils"; import { ensureTargeted } from "../functions/ensureTargeted"; import { isDeepStrictEqual } from "util"; +import { resolveProjectPath } from "../projectPath"; export { WebFrameworks }; @@ -100,21 +107,14 @@ function memoizeBuild( * */ export async function prepareFrameworks( + purpose: BUILD_TARGET_PURPOSE, targetNames: string[], - context: any, - options: any, + context: FrameworkContext | undefined, + options: FrameworksOptions, emulators: EmulatorInfo[] = [] ): Promise { - // `firebase-frameworks` requires Node >= 16. We must check for this to avoid horrible errors. - const nodeVersion = process.version; - if (!semver.satisfies(nodeVersion, ">=16.0.0")) { - throw new FirebaseError( - `The frameworks awareness feature requires Node.JS >= 16 and npm >= 8 in order to work correctly, due to some of the downstream dependencies. Please upgrade your version of Node.JS, reinstall firebase-tools, and give it another go.` - ); - } - - const project = needProjectId(context); - const { projectRoot } = options; + const project = needProjectId(context || options); + const projectRoot = resolveProjectPath(options, "."); const account = getProjectDefaultAccount(projectRoot); // options.site is not present when emulated. We could call requireHostingSite but IAM permissions haven't // been booted up (at this point) and we may be offline, so just use projectId. Most of the time @@ -260,11 +260,11 @@ export async function prepareFrameworks( ); const hostingEmulatorInfo = emulators.find((e) => e.name === Emulators.HOSTING); - const buildTargetPurpose: BUILD_TARGET_PURPOSE = - context._name === "deploy" ? "deploy" : context._name === "emulators:exec" ? "test" : "serve"; - const validBuildTargets = await getValidBuildTargets(buildTargetPurpose, getProjectPath()); - const frameworksBuildTarget = getFrameworksBuildTarget(buildTargetPurpose, validBuildTargets); - const useDevModeHandle = await shouldUseDevModeHandle(frameworksBuildTarget, getProjectPath()); + const validBuildTargets = await getValidBuildTargets(purpose, getProjectPath()); + const frameworksBuildTarget = getFrameworksBuildTarget(purpose, validBuildTargets); + const useDevModeHandle = + purpose !== "deploy" && + (await shouldUseDevModeHandle(frameworksBuildTarget, getProjectPath())); let codegenFunctionsDirectory: Framework["ɵcodegenFunctionsDirectory"]; @@ -321,7 +321,7 @@ export async function prepareFrameworks( process.env.__FIREBASE_DEFAULTS__ = JSON.stringify(firebaseDefaults); } - if (context.hostingChannel) { + if (context?.hostingChannel) { experiments.assertEnabled( "pintags", "deploy an app that requires a backend to a preview channel" @@ -344,12 +344,7 @@ export async function prepareFrameworks( // This is just a fallback for previous behavior if the user manually // disables the pintags experiment (e.g. there is a break and they would // rather disable the experiment than roll back). - if ( - !experiments.isEnabled("pintags") || - context._name === "serve" || - context._name === "emulators:start" || - context._name === "emulators:exec" - ) { + if (!experiments.isEnabled("pintags") || purpose !== "deploy") { if (!targetNames.includes("functions")) { targetNames.unshift("functions"); } diff --git a/src/frameworks/interfaces.ts b/src/frameworks/interfaces.ts index c851726b1cd..d8f79a23d7c 100644 --- a/src/frameworks/interfaces.ts +++ b/src/frameworks/interfaces.ts @@ -1,6 +1,8 @@ import { IncomingMessage, ServerResponse } from "http"; import { EmulatorInfo } from "../emulator/types"; import { HostingHeaders, HostingRedirects, HostingRewrites } from "../firebaseConfig"; +import { HostingOptions } from "../hosting/options"; +import { Options } from "../options"; // These serve as the order of operations for discovery // E.g, a framework utilizing Vite should be given priority @@ -32,6 +34,19 @@ export interface BuildResult { i18n?: boolean; } +export type RequestHandler = (req: IncomingMessage, res: ServerResponse, next: () => void) => void; + +export type FrameworksOptions = HostingOptions & + Options & { + frameworksDevModeHandle?: RequestHandler; + nonInteractive?: boolean; + }; + +export type FrameworkContext = { + projectId?: string; + hostingChannel?: string; +}; + export interface Framework { discover: (dir: string) => Promise; type: FrameworkType; @@ -44,7 +59,7 @@ export interface Framework { dir: string, target: string, hostingEmulatorInfo?: EmulatorInfo - ) => Promise<(req: IncomingMessage, res: ServerResponse, next: () => void) => void>; + ) => Promise; ɵcodegenPublicDirectory: ( dir: string, dest: string, @@ -70,7 +85,7 @@ export interface Framework { shouldUseDevModeHandle?: (target: string, dir: string) => Promise; } -export type BUILD_TARGET_PURPOSE = "deploy" | "test" | "serve"; +export type BUILD_TARGET_PURPOSE = "deploy" | "test" | "emulate"; // TODO pull from @firebase/util when published export interface FirebaseDefaults { diff --git a/src/frameworks/nuxt/index.ts b/src/frameworks/nuxt/index.ts index fffd1164167..a5871f1fb52 100644 --- a/src/frameworks/nuxt/index.ts +++ b/src/frameworks/nuxt/index.ts @@ -90,7 +90,7 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string) { } export async function getDevModeHandle(cwd: string) { - const host = new Promise((resolve) => { + const host = new Promise((resolve, reject) => { const cli = getNodeModuleBin("nuxt", cwd); const serve = spawn(cli, ["dev"], { cwd: cwd }); @@ -104,6 +104,8 @@ export async function getDevModeHandle(cwd: string) { serve.stderr.on("data", (data: any) => { process.stderr.write(data); }); + + serve.on("exit", reject); }); return simpleProxy(await host); diff --git a/src/frameworks/nuxt2/index.ts b/src/frameworks/nuxt2/index.ts index 5609efc56d1..f31a99ba6d7 100644 --- a/src/frameworks/nuxt2/index.ts +++ b/src/frameworks/nuxt2/index.ts @@ -97,7 +97,7 @@ export async function ɵcodegenFunctionsDirectory(rootDir: string, destDir: stri } export async function getDevModeHandle(cwd: string) { - const host = new Promise((resolve) => { + const host = new Promise((resolve, reject) => { const cli = getNodeModuleBin("nuxt", cwd); const serve = spawn(cli, ["dev"], { cwd }); @@ -111,6 +111,8 @@ export async function getDevModeHandle(cwd: string) { serve.stderr.on("data", (data: any) => { process.stderr.write(data); }); + + serve.on("exit", reject); }); return simpleProxy(await host); diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index 5ac49c2027c..265c742adc5 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -290,26 +290,22 @@ export function getFrameworksBuildTarget(purpose: BUILD_TARGET_PURPOSE, validOpt ); } return frameworksBuild; - } else if (purpose === "deploy") { + } else if (["test", "deploy"].includes(purpose)) { return "production"; - // TODO handle other language / frameworks environment variables - } else if (process.env.NODE_ENV) { - switch (process.env.NODE_ENV) { - case "development": - return "development"; - case "production": - case "test": - return "production"; - default: - throw new FirebaseError( - `We cannot infer your build target from a non-standard NODE_ENV. Please set the FIREBASE_FRAMEWORKS_BUILD_TARGET environment variable. Valid values are: ${validOptions.join( - ", " - )}` - ); - } - } else if (purpose === "test") { - return "production"; - } else { - return "development"; + } + // TODO handle other language / frameworks environment variables + switch (process.env.NODE_ENV) { + case undefined: + case "development": + return "development"; + case "production": + case "test": + return "production"; + default: + throw new FirebaseError( + `We cannot infer your build target from a non-standard NODE_ENV. Please set the FIREBASE_FRAMEWORKS_BUILD_TARGET environment variable. Valid values are: ${validOptions.join( + ", " + )}` + ); } } diff --git a/src/frameworks/vite/index.ts b/src/frameworks/vite/index.ts index 959145a867e..3a16558c069 100644 --- a/src/frameworks/vite/index.ts +++ b/src/frameworks/vite/index.ts @@ -84,7 +84,7 @@ export async function ɵcodegenPublicDirectory(root: string, dest: string) { } export async function getDevModeHandle(dir: string) { - const host = new Promise((resolve) => { + const host = new Promise((resolve, reject) => { // Can't use scheduleTarget since that—like prerender—is failing on an ESM bug // will just grep for the hostname const cli = getNodeModuleBin("vite", dir); @@ -97,6 +97,8 @@ export async function getDevModeHandle(dir: string) { serve.stderr.on("data", (data: any) => { process.stderr.write(data); }); + + serve.on("exit", reject); }); return simpleProxy(await host); } diff --git a/src/serve/index.ts b/src/serve/index.ts index 925447f10d3..59c34902b15 100644 --- a/src/serve/index.ts +++ b/src/serve/index.ts @@ -25,7 +25,7 @@ export async function serve(options: any): Promise { options.port = parseInt(options.port, 10); if (targetNames.includes("hosting") && config.extract(options).some((it: any) => it.source)) { experiments.assertEnabled("webframeworks", "emulate a web framework"); - await prepareFrameworks(targetNames, options, options); + await prepareFrameworks("emulate", targetNames, undefined, options); } const isDemoProject = Constants.isDemoProject(getProjectId(options) || ""); targetNames.forEach((targetName) => { From 4bc4304460a8430cfd5704c1a4f04266d191edd5 Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Tue, 30 May 2023 14:14:18 -0400 Subject: [PATCH 0997/1699] change v2 scheduled fns to be handled as http by emulator (#5891) --- src/emulator/functionsEmulatorShared.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 9e474cb1489..87f6cbc1664 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -454,6 +454,9 @@ export function getSignatureType(def: EmulatedTriggerDefinition): SignatureType if (def.httpsTrigger || def.blockingTrigger) { return "http"; } + if (def.platform === "gcfv2" && def.schedule) { + return "http"; + } // TODO: As implemented, emulated CF3v1 functions cannot receive events in CloudEvent format, and emulated CF3v2 // functions cannot receive events in legacy format. This conflicts with our goal of introducing a 'compat' layer // that allows CF3v1 functions to target GCFv2 and vice versa. From ce5d7823b6cbac6d0fbe7e7f6fcaf421700c075a Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 30 May 2023 16:36:14 -0700 Subject: [PATCH 0998/1699] Update docs link in JS template (#5914) --- templates/extensions/javascript/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/extensions/javascript/index.js b/templates/extensions/javascript/index.js index 33837f73eae..3bd6dc6d4fb 100644 --- a/templates/extensions/javascript/index.js +++ b/templates/extensions/javascript/index.js @@ -5,7 +5,7 @@ * Reference PARAMETERS in your functions code with: * `process.env.` * Learn more about building extensions in the docs: - * https://firebase.google.com/docs/extensions/alpha/overview + * https://firebase.google.com/docs/extensions/publishers */ const functions = require("firebase-functions"); From 3b80ee25f225436f03352b1bd13dca330685fd9b Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 1 Jun 2023 11:57:42 -0400 Subject: [PATCH 0999/1699] Support Astro hybrid rendering (#5898) * Astro hybrid support * Middleware mode callout --- CHANGELOG.md | 1 + src/frameworks/astro/index.ts | 11 ++++++----- src/test/frameworks/astro/index.spec.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f658a1f206..b42f3e7631d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Address additional cases where we were attempting to deploy a framework's development bundle (#5895) +- Support Astro hybrid rendering (#5898) diff --git a/src/frameworks/astro/index.ts b/src/frameworks/astro/index.ts index b7d7d804d39..02e5ad3daaa 100644 --- a/src/frameworks/astro/index.ts +++ b/src/frameworks/astro/index.ts @@ -25,7 +25,7 @@ export async function discover(dir: string): Promise { if (!getAstroVersion(dir)) return; const { output, publicDir: publicDirectory } = await getConfig(dir); return { - mayWantBackend: output === "server", + mayWantBackend: output !== "static", publicDirectory, }; } @@ -36,20 +36,21 @@ export async function build(cwd: string): Promise { const cli = getNodeModuleBin("astro", cwd); await warnIfCustomBuildScript(cwd, name, DEFAULT_BUILD_SCRIPT); const { output, adapter } = await getConfig(cwd); - if (output === "server" && adapter?.name !== "@astrojs/node") { + const wantsBackend = output !== "static"; + if (wantsBackend && adapter?.name !== "@astrojs/node") { throw new FirebaseError( - "Deploying an Astro application with SSR on Firebase Hosting requires the @astrojs/node adapter." + "Deploying an Astro application with SSR on Firebase Hosting requires the @astrojs/node adapter in middleware mode. https://docs.astro.build/en/guides/integrations-guide/node/" ); } const build = spawnSync(cli, ["build"], { cwd, stdio: "inherit" }); if (build.status !== 0) throw new FirebaseError("Unable to build your Astro app"); - return { wantsBackend: output === "server" }; + return { wantsBackend }; } export async function ɵcodegenPublicDirectory(root: string, dest: string) { const { outDir, output } = await getConfig(root); // output: "server" in astro.config builds "client" and "server" folders, otherwise assets are in top-level outDir - const assetPath = join(root, outDir, output === "server" ? "client" : ""); + const assetPath = join(root, outDir, output !== "static" ? "client" : ""); await copy(assetPath, dest); } diff --git a/src/test/frameworks/astro/index.spec.ts b/src/test/frameworks/astro/index.spec.ts index ef2289de609..7682b4b9404 100644 --- a/src/test/frameworks/astro/index.spec.ts +++ b/src/test/frameworks/astro/index.spec.ts @@ -268,7 +268,7 @@ describe("Astro", () => { await expect(build(cwd)).to.eventually.rejectedWith( FirebaseError, - "Deploying an Astro application with SSR on Firebase Hosting requires the @astrojs/node adapter." + "Deploying an Astro application with SSR on Firebase Hosting requires the @astrojs/node adapter in middleware mode. https://docs.astro.build/en/guides/integrations-guide/node/" ); }); From 48bdcb055170beef7ea3ccc8ff2ba2cf20b03b2b Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 1 Jun 2023 09:43:02 -0700 Subject: [PATCH 1000/1699] Switch ext:dev:init to default billingRequire to true (#5917) --- CHANGELOG.md | 1 + templates/extensions/extension.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b42f3e7631d..b7f96127751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Address additional cases where we were attempting to deploy a framework's development bundle (#5895) +- Switch `ext:dev:init` to default 'billingRequired' to true in `extension.yaml` - Support Astro hybrid rendering (#5898) diff --git a/templates/extensions/extension.yaml b/templates/extensions/extension.yaml index e9792cb8a70..c1445250c44 100644 --- a/templates/extensions/extension.yaml +++ b/templates/extensions/extension.yaml @@ -19,7 +19,7 @@ sourceUrl: https://github.com/firebase/firebase-tools/tree/master/templates/exte # Specify whether a paid-tier billing plan is required to use your extension. # Learn more in the docs: https://firebase.google.com/docs/extensions/reference/extension-yaml#billing-required-field -billingRequired: false +billingRequired: true # In an `apis` field, list any Google APIs (like Cloud Translation, BigQuery, etc.) # required for your extension to operate. From a3a1da93b46bef00f06f39472dd5417b96908b90 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 1 Jun 2023 13:08:21 -0400 Subject: [PATCH 1001/1699] Get autoTokenSyncURL in simpleProxy sooner (#5894) Fixes #5852 --- CHANGELOG.md | 1 + src/frameworks/utils.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f96127751..5ee9b7ed124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fix a bug preventing web framework's dev-mode from working out-of-box with Firebase Authentication. (#5894) - Address additional cases where we were attempting to deploy a framework's development bundle (#5895) - Switch `ext:dev:init` to default 'billingRequired' to true in `extension.yaml` - Support Astro hybrid rendering (#5898) diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index 265c742adc5..acf58c336cf 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -67,16 +67,16 @@ type RequestHandler = (req: IncomingMessage, res: ServerResponse) => Promise void) => { const { method, headers, url: path } = originalReq; if (!method || !path) { originalRes.end(); return; } - // If the path is a the auth token sync URL pass through to Cloud Functions - const firebaseDefaultsJSON = process.env.__FIREBASE_DEFAULTS__; - const authTokenSyncURL: string | undefined = - firebaseDefaultsJSON && JSON.parse(firebaseDefaultsJSON)._authTokenSyncURL; if (path === authTokenSyncURL) { return next(); } From a1a6cc98459e57c6ae46d1e352b3231910f4cd2b Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 1 Jun 2023 12:53:32 -0700 Subject: [PATCH 1002/1699] Remove LOCATION param from extension.yaml template. (#5925) * Switch ext:dev:init to default billingRequire to true * whitespace --- CHANGELOG.md | 1 + templates/extensions/extension.yaml | 53 ----------------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ee9b7ed124..8647affa394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ - Fix a bug preventing web framework's dev-mode from working out-of-box with Firebase Authentication. (#5894) - Address additional cases where we were attempting to deploy a framework's development bundle (#5895) - Switch `ext:dev:init` to default 'billingRequired' to true in `extension.yaml` +- Remove `LOCATION` param from the `extensions.yaml` template for `ext:dev:init` - Support Astro hybrid rendering (#5898) diff --git a/templates/extensions/extension.yaml b/templates/extensions/extension.yaml index c1445250c44..83c44297726 100644 --- a/templates/extensions/extension.yaml +++ b/templates/extensions/extension.yaml @@ -39,8 +39,6 @@ resources: description: >- HTTP request-triggered function that responds with a specified greeting message properties: - # LOCATION is a user-configured parameter value specified by the user during installation. - location: ${LOCATION} # httpsTrigger is used for an HTTP triggered function. httpsTrigger: {} runtime: "nodejs16" @@ -58,54 +56,3 @@ params: default: Hello required: true immutable: false - - - param: LOCATION - label: Cloud Functions location - description: >- - Where do you want to deploy the functions created for this extension? - For help selecting a location, refer to the [location selection - guide](https://firebase.google.com/docs/functions/locations). - type: select - options: - - label: Iowa (us-central1) - value: us-central1 - - label: South Carolina (us-east1) - value: us-east1 - - label: Northern Virginia (us-east4) - value: us-east4 - - label: Los Angeles (us-west2) - value: us-west2 - - label: Salt Lake City (us-west3) - value: us-west3 - - label: Las Vegas (us-west4) - value: us-west4 - - label: Warsaw (europe-central2) - value: europe-central2 - - label: Belgium (europe-west1) - value: europe-west1 - - label: London (europe-west2) - value: europe-west2 - - label: Frankfurt (europe-west3) - value: europe-west3 - - label: Zurich (europe-west6) - value: europe-west6 - - label: Hong Kong (asia-east2) - value: asia-east2 - - label: Tokyo (asia-northeast1) - value: asia-northeast1 - - label: Osaka (asia-northeast2) - value: asia-northeast2 - - label: Seoul (asia-northeast3) - value: asia-northeast3 - - label: Mumbai (asia-south1) - value: asia-south1 - - label: Jakarta (asia-southeast2) - value: asia-southeast2 - - label: Montreal (northamerica-northeast1) - value: northamerica-northeast1 - - label: Sao Paulo (southamerica-east1) - value: southamerica-east1 - - label: Sydney (australia-southeast1) - value: australia-southeast1 - required: true - immutable: true From da5960aeafcf2141d3c96dafceddf5134c17d0c9 Mon Sep 17 00:00:00 2001 From: aalej Date: Fri, 2 Jun 2023 04:17:36 +0800 Subject: [PATCH 1003/1699] Improve error message raised when `--import` flag directory does not exist (#5905) * Improve error message raised when flag directory does not exist * Update CHANGELOG.md --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + src/emulator/controller.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8647affa394..616b99ba2d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ - Fix a bug preventing web framework's dev-mode from working out-of-box with Firebase Authentication. (#5894) - Address additional cases where we were attempting to deploy a framework's development bundle (#5895) +- Improve error message raised when `--import` flag directory does not exist. (#5851) - Switch `ext:dev:init` to default 'billingRequired' to true in `extension.yaml` - Remove `LOCATION` param from the `extensions.yaml` template for `ext:dev:init` - Support Astro hybrid rendering (#5898) diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 089250f00c7..2a0d2da2eab 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -186,6 +186,11 @@ export function shouldStart(options: Options, name: Emulators): boolean { } function findExportMetadata(importPath: string): ExportMetadata | undefined { + const pathExists = fs.existsSync(importPath); + if (!pathExists) { + throw new FirebaseError(`Directory "${importPath}" does not exist.`); + } + const pathIsDirectory = fs.lstatSync(importPath).isDirectory(); if (!pathIsDirectory) { return; From 2b6b19d319689404487cdff7d9ea446127dbba8c Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 1 Jun 2023 13:45:51 -0700 Subject: [PATCH 1004/1699] Clarify that sourceUri needs to be replaced (#5927) * Clarify that sourceUri needs to be replaced * TODO --- templates/extensions/extension.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/templates/extensions/extension.yaml b/templates/extensions/extension.yaml index 83c44297726..b34258ef9d8 100644 --- a/templates/extensions/extension.yaml +++ b/templates/extensions/extension.yaml @@ -1,7 +1,9 @@ # Learn detailed information about the fields of an extension.yaml file in the docs: # https://firebase.google.com/docs/extensions/reference/extension-yaml -name: greet-the-world # Identifier for your extension +# Identifier for your extension +# TODO: Replace this with an descriptive name for your extension. +name: greet-the-world version: 0.0.1 # Follow semver versioning specVersion: v1beta # Version of the Firebase Extensions specification @@ -14,8 +16,9 @@ description: >- license: Apache-2.0 # https://spdx.org/licenses/ -# Public URL for the source code of your extension -sourceUrl: https://github.com/firebase/firebase-tools/tree/master/templates/extensions +# Public URL for the source code of your extension. +# TODO: Replace this with your GitHub repo. +sourceUrl: https://github.com/ORG_OR_USER/REPO_NAME # Specify whether a paid-tier billing plan is required to use your extension. # Learn more in the docs: https://firebase.google.com/docs/extensions/reference/extension-yaml#billing-required-field From 708efef1685afb4ddc73d0597eb1caadf6417da7 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 1 Jun 2023 13:58:31 -0700 Subject: [PATCH 1005/1699] Export hosting-channel-deploy action (#5820) Co-authored-by: joehan --- src/commands/hosting-channel-deploy.ts | 263 +++++++++++++------------ 1 file changed, 133 insertions(+), 130 deletions(-) diff --git a/src/commands/hosting-channel-deploy.ts b/src/commands/hosting-channel-deploy.ts index 49e533830a5..343290236c9 100644 --- a/src/commands/hosting-channel-deploy.ts +++ b/src/commands/hosting-channel-deploy.ts @@ -46,149 +46,152 @@ export const command = new Command("hosting:channel:deploy [channelId]") .before(requireConfig) .before(requirePermissions, ["firebasehosting.sites.update"]) .before(requireHostingSite) - .action( - async ( - channelId: string, - options: Options & HostingOptions - ): Promise<{ [targetOrSite: string]: ChannelInfo }> => { - const projectId = needProjectId(options); - - // TODO: implement --open. - if (options.open) { - throw new FirebaseError("open is not yet implemented"); - } - - let expireTTL = DEFAULT_DURATION; - if (options.expires) { - expireTTL = calculateChannelExpireTTL(options.expires); - logger.debug(`Expires TTL: ${expireTTL}`); - } + .action(hostingChannelDeployAction); - // TODO: interactive prompt if channel doesn't exist - if (!channelId) { - throw new FirebaseError("channelID is currently required"); - } +/** + * Deploys to specified hosting channel. + * + * @param channelId ID of hosting channel to deploy to. + * @param options Deployment options + */ +export async function hostingChannelDeployAction( + channelId: string, + options: Options & HostingOptions +): Promise<{ [targetOrSite: string]: ChannelInfo }> { + const projectId = needProjectId(options); + + // TODO: implement --open. + if (options.open) { + throw new FirebaseError("open is not yet implemented"); + } - channelId = normalizeName(channelId); + let expireTTL = DEFAULT_DURATION; + if (options.expires) { + expireTTL = calculateChannelExpireTTL(options.expires); + logger.debug(`Expires TTL: ${expireTTL}`); + } - // Some normalizing to be very sure of this check. - if (channelId.toLowerCase().trim() === "live") { - throw new FirebaseError( - `Cannot deploy to the ${bold("live")} channel using this command. Please use ${bold( - yellow("firebase deploy") - )} instead.` - ); - } + // TODO: interactive prompt if channel doesn't exist + if (!channelId) { + throw new FirebaseError("channelID is currently required"); + } - if (options.only) { - // HACK: Re-use deploy in a rather ham-fisted way. - options.only = options.only - .split(",") - .map((o: string) => `hosting:${o}`) - .join(","); - } else { - // N.B. The hosting deploy code uses the only string to add all (and only) - // functions that are pinned to the only string. If we didn't set the - // only string here and only used the hosting deploy targets, we'd only - // be able to deploy *all* functions. - options.only = "hosting"; - } + channelId = normalizeName(channelId); - const sites: ChannelInfo[] = hostingConfig(options).map((config) => { - return { - target: config.target, - site: config.site, - url: "", - version: "", - expireTime: "", - }; - }); - - await Promise.all( - sites.map(async (siteInfo) => { - const site = siteInfo.site; - let chan = await getChannel(projectId, site, channelId); - if (chan) { - logger.debug("[hosting] found existing channel for site", site, chan); - const channelExpires = Boolean(chan.expireTime); - if (!channelExpires && options.expires) { - // If the channel doesn't expire, but the user provided a TTL, update the channel. - chan = await updateChannelTtl(projectId, site, channelId, expireTTL); - } else if (channelExpires) { - // If the channel expires, calculate the time remaining to maybe update the channel. - const channelTimeRemaining = new Date(chan.expireTime).getTime() - Date.now(); - // If the user explicitly gave us a time OR the time remaining is less than the new TTL: - if (options.expires || channelTimeRemaining < expireTTL) { - chan = await updateChannelTtl(projectId, site, channelId, expireTTL); - logger.debug("[hosting] updated TTL for existing channel for site", site, chan); - } - } - } else { - chan = await createChannel(projectId, site, channelId, expireTTL); - logger.debug("[hosting] created new channnel for site", site, chan); - logLabeledSuccess( - LOG_TAG, - `Channel ${bold(channelId)} has been created on site ${bold(site)}.` - ); - } - siteInfo.url = chan.url; - siteInfo.expireTime = chan.expireTime; - return; - }) - ); - - const { hosting } = await deploy(["hosting"], options, { hostingChannel: channelId }); - - // The version names are returned in the hosting key of the deploy result. - // - // If there is only one element it is returned as a string, otherwise it - // is an array of strings. Not sure why it's done that way, but that's - // something we can't change because it is in the deploy output in json. - // - // The code below turns it back to an array of version names. - const versionNames: Array = []; - if (typeof hosting === "string") { - versionNames.push(hosting); - } else if (Array.isArray(hosting)) { - hosting.forEach((version) => { - versionNames.push(version); - }); - } + // Some normalizing to be very sure of this check. + if (channelId.toLowerCase().trim() === "live") { + throw new FirebaseError( + `Cannot deploy to the ${bold("live")} channel using this command. Please use ${bold( + yellow("firebase deploy") + )} instead.` + ); + } - if (options.authorizedDomains) { - await syncAuthState(projectId, sites); - } else { - logger.debug( - `skipping syncAuthState since authorizedDomains is ${options.authorizedDomains}` - ); - } + if (options.only) { + // HACK: Re-use deploy in a rather ham-fisted way. + options.only = options.only + .split(",") + .map((o: string) => `hosting:${o}`) + .join(","); + } else { + // N.B. The hosting deploy code uses the only string to add all (and only) + // functions that are pinned to the only string. If we didn't set the + // only string here and only used the hosting deploy targets, we'd only + // be able to deploy *all* functions. + options.only = "hosting"; + } - logger.info(); - const deploys: { [key: string]: ChannelInfo } = {}; - sites.forEach((d) => { - deploys[d.target || d.site] = d; - let expires = ""; - if (d.expireTime) { - expires = `[expires ${bold(datetimeString(new Date(d.expireTime)))}]`; - } - const versionPrefix = `sites/${d.site}/versions/`; - const versionName = versionNames.find((v) => { - return v.startsWith(versionPrefix); - }); - let version = ""; - if (versionName) { - d.version = versionName.replace(versionPrefix, ""); - version = ` [version ${bold(d.version)}]`; + const sites: ChannelInfo[] = hostingConfig(options).map((config) => { + return { + target: config.target, + site: config.site, + url: "", + version: "", + expireTime: "", + }; + }); + + await Promise.all( + sites.map(async (siteInfo) => { + const site = siteInfo.site; + let chan = await getChannel(projectId, site, channelId); + if (chan) { + logger.debug("[hosting] found existing channel for site", site, chan); + const channelExpires = Boolean(chan.expireTime); + if (!channelExpires && options.expires) { + // If the channel doesn't expire, but the user provided a TTL, update the channel. + chan = await updateChannelTtl(projectId, site, channelId, expireTTL); + } else if (channelExpires) { + // If the channel expires, calculate the time remaining to maybe update the channel. + const channelTimeRemaining = new Date(chan.expireTime).getTime() - Date.now(); + // If the user explicitly gave us a time OR the time remaining is less than the new TTL: + if (options.expires || channelTimeRemaining < expireTTL) { + chan = await updateChannelTtl(projectId, site, channelId, expireTTL); + logger.debug("[hosting] updated TTL for existing channel for site", site, chan); + } } + } else { + chan = await createChannel(projectId, site, channelId, expireTTL); + logger.debug("[hosting] created new channnel for site", site, chan); logLabeledSuccess( LOG_TAG, - `Channel URL (${bold(d.site || d.target || "")}): ${d.url} ${expires}${version}` + `Channel ${bold(channelId)} has been created on site ${bold(site)}.` ); - }); - return deploys; - } + } + siteInfo.url = chan.url; + siteInfo.expireTime = chan.expireTime; + return; + }) ); + const { hosting } = await deploy(["hosting"], options, { hostingChannel: channelId }); + + // The version names are returned in the hosting key of the deploy result. + // + // If there is only one element it is returned as a string, otherwise it + // is an array of strings. Not sure why it's done that way, but that's + // something we can't change because it is in the deploy output in json. + // + // The code below turns it back to an array of version names. + const versionNames: Array = []; + if (typeof hosting === "string") { + versionNames.push(hosting); + } else if (Array.isArray(hosting)) { + hosting.forEach((version) => { + versionNames.push(version); + }); + } + + if (options.authorizedDomains) { + await syncAuthState(projectId, sites); + } else { + logger.debug(`skipping syncAuthState since authorizedDomains is ${options.authorizedDomains}`); + } + + logger.info(); + const deploys: { [key: string]: ChannelInfo } = {}; + sites.forEach((d) => { + deploys[d.target || d.site] = d; + let expires = ""; + if (d.expireTime) { + expires = `[expires ${bold(datetimeString(new Date(d.expireTime)))}]`; + } + const versionPrefix = `sites/${d.site}/versions/`; + const versionName = versionNames.find((v) => { + return v.startsWith(versionPrefix); + }); + let version = ""; + if (versionName) { + d.version = versionName.replace(versionPrefix, ""); + version = ` [version ${bold(d.version)}]`; + } + logLabeledSuccess( + LOG_TAG, + `Channel URL (${bold(d.site || d.target || "")}): ${d.url} ${expires}${version}` + ); + }); + return deploys; +} /** * Helper function to sync authorized domains for deployed sites. * @param projectId the project id. From ac21ab3cf18eba6f6f06b932ee7b04d84d02d054 Mon Sep 17 00:00:00 2001 From: aalej Date: Fri, 2 Jun 2023 05:33:30 +0800 Subject: [PATCH 1006/1699] Fix issue where a user is created when empty email and password is passed (#5906) * Fix issue where a user is created when passing an empty email and password * Fix issue where a user is created when passing an empty email and password --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + src/emulator/auth/operations.ts | 4 ++-- src/test/emulators/auth/signUp.spec.ts | 11 +++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 616b99ba2d6..ed5acc5674e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ - Fix a bug preventing web framework's dev-mode from working out-of-box with Firebase Authentication. (#5894) - Address additional cases where we were attempting to deploy a framework's development bundle (#5895) +- Fixes issue where Authentication emulator creates a user if empty email and empty password is provided. (#5639) - Improve error message raised when `--import` flag directory does not exist. (#5851) - Switch `ext:dev:init` to default 'billingRequired' to true in `extension.yaml` - Remove `LOCATION` param from the `extensions.yaml` template for `ext:dev:init` diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index cde34a11853..9cc1d4619c3 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -198,13 +198,13 @@ async function signUp( } } - if (reqBody.email) { + if (typeof reqBody.email === "string") { assert(isValidEmailAddress(reqBody.email), "INVALID_EMAIL"); const email = canonicalizeEmailAddress(reqBody.email); assert(!state.getUserByEmail(email), "EMAIL_EXISTS"); updates.email = email; } - if (reqBody.password) { + if (typeof reqBody.password === "string") { assert( reqBody.password.length >= PASSWORD_MIN_LENGTH, `WEAK_PASSWORD : Password should be at least ${PASSWORD_MIN_LENGTH} characters` diff --git a/src/test/emulators/auth/signUp.spec.ts b/src/test/emulators/auth/signUp.spec.ts index bb7d6787fd8..a69b12d908e 100644 --- a/src/test/emulators/auth/signUp.spec.ts +++ b/src/test/emulators/auth/signUp.spec.ts @@ -40,6 +40,17 @@ describeAuthEmulator("accounts:signUp", ({ authApi }) => { }); }); + it("should throw error if empty email and password is provided", async () => { + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:signUp") + .send({ email: "", password: "" }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error).to.have.property("message").equals("INVALID_EMAIL"); + }); + }); + it("should issue idToken and refreshToken on anon signUp", async () => { await authApi() .post("/identitytoolkit.googleapis.com/v1/accounts:signUp") From 0261dc813c6f72229bc2ff2e1fa767c17fe1ca3b Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 1 Jun 2023 18:23:54 -0400 Subject: [PATCH 1007/1699] Continue on 404, baseUrl for Next rewrites (#5923) * Test the effective firebase.json * Using aliases for the emulator test * Caught that baseUrl wasn't being returned correctly by NextJS * Adding back in continue on 404 for the emulators, fixes #5840 * Switch to using logger rather than console.log * Cleanup the test script * Test combination of Cloud Function rewrites and web frameworks * Web Frameworks rewrites/redirects/headers should only come before existing if there's a baseUrl * Move basePath to build so we can use it sooner --- CHANGELOG.md | 3 + .../webframeworks-deploy-tests/.firebaserc | 19 +- .../webframeworks-deploy-tests/firebase.json | 45 +- .../functions/.gitignore | 1 + .../functions/index.js | 5 + .../functions/package-lock.json | 10865 ++++++++++++++++ .../functions/package.json | 23 + .../nextjs/next.config.js | 22 + scripts/webframeworks-deploy-tests/run.sh | 13 +- scripts/webframeworks-deploy-tests/tests.ts | 145 +- src/frameworks/angular/index.ts | 23 +- src/frameworks/index.ts | 75 +- src/frameworks/interfaces.ts | 8 +- src/frameworks/next/index.ts | 43 +- src/frameworks/nuxt/index.ts | 14 +- src/frameworks/utils.ts | 30 +- 16 files changed, 11242 insertions(+), 92 deletions(-) create mode 100644 scripts/webframeworks-deploy-tests/functions/.gitignore create mode 100644 scripts/webframeworks-deploy-tests/functions/index.js create mode 100644 scripts/webframeworks-deploy-tests/functions/package-lock.json create mode 100644 scripts/webframeworks-deploy-tests/functions/package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5acc5674e..6822691b7c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ - Fix a bug preventing web framework's dev-mode from working out-of-box with Firebase Authentication. (#5894) - Address additional cases where we were attempting to deploy a framework's development bundle (#5895) +- NextJS rewrites should be prefixed with the basePath defined in next.config.js (#5923) +- Web Frameworks emulators will again respect existing Cloud Functions rewrites (#5923) +- Web Frameworks rewrites/redirects/headers will only prepend those in firebase.json if there's a baseUrl (#5923) - Fixes issue where Authentication emulator creates a user if empty email and empty password is provided. (#5639) - Improve error message raised when `--import` flag directory does not exist. (#5851) - Switch `ext:dev:init` to default 'billingRequired' to true in `extension.yaml` diff --git a/scripts/webframeworks-deploy-tests/.firebaserc b/scripts/webframeworks-deploy-tests/.firebaserc index 0967ef424bc..17a020da167 100644 --- a/scripts/webframeworks-deploy-tests/.firebaserc +++ b/scripts/webframeworks-deploy-tests/.firebaserc @@ -1 +1,18 @@ -{} +{ + "projects": { + "default": "nextjs-demo-73e34" + }, + "targets": { + "demo-123": { + "hosting": { + "angular": [ + "demo-angular" + ], + "nextjs": [ + "demo-nextjs" + ] + } + } + }, + "etags": {} +} diff --git a/scripts/webframeworks-deploy-tests/firebase.json b/scripts/webframeworks-deploy-tests/firebase.json index c37e2d01e99..a59a94f84a8 100644 --- a/scripts/webframeworks-deploy-tests/firebase.json +++ b/scripts/webframeworks-deploy-tests/firebase.json @@ -1,9 +1,40 @@ { - "hosting": [{ - "site": "demo-nextjs", - "source": "nextjs" - }, { - "site": "demo-angular", - "source": "angular" - }] + "hosting": [ + { + "target": "nextjs", + "source": "nextjs", + "frameworksBackend": { + "maxInstances": 1, + "region": "asia-east1" + }, + "rewrites": [{ + "source": "helloWorld", + "function": "helloWorld" + }] + }, + { + "target": "angular", + "source": "angular", + "frameworksBackend": { + "maxInstances": 1, + "region": "europe-west1" + }, + "rewrites": [{ + "source": "helloWorld", + "function": "helloWorld" + }] + } + ], + "functions": [ + { + "source": "functions", + "codebase": "default", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ] + } + ] } diff --git a/scripts/webframeworks-deploy-tests/functions/.gitignore b/scripts/webframeworks-deploy-tests/functions/.gitignore new file mode 100644 index 00000000000..40b878db5b1 --- /dev/null +++ b/scripts/webframeworks-deploy-tests/functions/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/scripts/webframeworks-deploy-tests/functions/index.js b/scripts/webframeworks-deploy-tests/functions/index.js new file mode 100644 index 00000000000..ebf396726e7 --- /dev/null +++ b/scripts/webframeworks-deploy-tests/functions/index.js @@ -0,0 +1,5 @@ +import { onRequest } from "firebase-functions/v2/https"; + +export const helloWorld = onRequest((request, response) => { + response.send("Hello from Firebase!"); +}); diff --git a/scripts/webframeworks-deploy-tests/functions/package-lock.json b/scripts/webframeworks-deploy-tests/functions/package-lock.json new file mode 100644 index 00000000000..9ebba9bb98e --- /dev/null +++ b/scripts/webframeworks-deploy-tests/functions/package-lock.json @@ -0,0 +1,10865 @@ +{ + "name": "functions", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "functions", + "dependencies": { + "firebase-admin": "^11.8.0", + "firebase-functions": "^4.3.1" + }, + "devDependencies": { + "firebase-functions-test": "^3.1.0" + }, + "engines": { + "node": "18" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.3.tgz", + "integrity": "sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.1.tgz", + "integrity": "sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==", + "dev": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.22.0", + "@babel/helper-compilation-targets": "^7.22.1", + "@babel/helper-module-transforms": "^7.22.1", + "@babel/helpers": "^7.22.0", + "@babel/parser": "^7.22.0", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "peer": true + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true + }, + "node_modules/@babel/generator": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.3.tgz", + "integrity": "sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz", + "integrity": "sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.22.0", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/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, + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "peer": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz", + "integrity": "sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.21.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz", + "integrity": "sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.3.tgz", + "integrity": "sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "peer": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", + "integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", + "devOptional": true, + "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, + "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, + "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, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "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, + "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, + "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.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "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, + "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, + "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, + "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, + "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, + "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, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.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, + "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.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", + "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz", + "integrity": "sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/parser": "^7.21.9", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.4.tgz", + "integrity": "sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.22.3", + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.22.4", + "@babel/types": "^7.22.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true + }, + "node_modules/@babel/types": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz", + "integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "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, + "peer": true + }, + "node_modules/@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", + "dependencies": { + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + }, + "node_modules/@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "dependencies": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "dependencies": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.6.1.tgz", + "integrity": "sha512-Z41j2h0mrgBH9qNIVmbRLqGKc6XmdJtWipeKwdnGa/bPTP1gn2SGTrYyWnpfsLMEtzKSYieHPSkAFp5kduF2RA==", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^3.5.7", + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.10.1.tgz", + "integrity": "sha512-EtLlT0YbXtrbUxaNbEfTyTytrjELtl4i42flf8COg+Hu5+apdNjsFO9XEY39wshxAuVjLf4fCSm7GTSW+BD3gQ==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz", + "integrity": "sha512-w84maJ6CKl5aApCMzFll0hxtFNT6or9WwMslobKaqWUEf1K+zhlL43bSQhFreyYWIWR+Z0xnVFC1KtLm4ZpM/A==", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", + "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", + "optional": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "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, + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@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.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "peer": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@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": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.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.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "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.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@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.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "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.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.4.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.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", + "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.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==" + }, + "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==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "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==", + "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==" + }, + "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==" + }, + "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==" + }, + "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==" + }, + "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==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true, + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.2.0.tgz", + "integrity": "sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg==", + "dev": true, + "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==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "dev": true, + "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.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.0.tgz", + "integrity": "sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "optional": true, + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true, + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, + "node_modules/@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "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==", + "optional": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "optional": true + }, + "node_modules/@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true, + "peer": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", + "optional": true, + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true, + "peer": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true, + "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==", + "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==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "optional": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "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, + "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, + "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, + "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, + "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, + "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==" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "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==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "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, + "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-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "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.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@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-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "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==", + "devOptional": 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" + } + ], + "optional": true + }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "optional": true + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.11.0", + "raw-body": "2.5.1", + "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==", + "devOptional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "peer": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", + "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "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" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001489", + "electron-to-chromium": "^1.4.411", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "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, + "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==" + }, + "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, + "peer": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001492", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz", + "integrity": "sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==", + "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" + } + ], + "peer": true + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": 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, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true, + "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, + "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, + "peer": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true, + "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, + "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 + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": 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==", + "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==", + "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, + "peer": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "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==" + }, + "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==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "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==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true, + "peer": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "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, + "peer": true, + "engines": { + "node": ">=0.10.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==", + "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==", + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "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==", + "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==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.417", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.417.tgz", + "integrity": "sha512-8rY8HdCxuSVY8wku3i/eDac4g1b4cSbruzocenrqBlzqruAZYHjQCHIjC66dLR9DXhEHTojsC4EjhZ8KmzwXqA==", + "dev": true, + "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, + "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 + }, + "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==", + "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==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "optional": true + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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, + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "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==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "optional": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "optional": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "devOptional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "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==", + "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, + "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.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "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" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "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==", + "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, + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "optional": true + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "optional": true + }, + "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==", + "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, + "peer": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "peer": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "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, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/firebase-admin": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.9.0.tgz", + "integrity": "sha512-My7qrInVZFmImX8aTulrp9kgY6d88Wn+ie8UIXKzZ3SJqQQhDwFT7Q3pgQXK9RfdsUtcxJJ3rCK7MWBm4GGtuw==", + "dependencies": { + "@fastify/busboy": "^1.2.1", + "@firebase/database-compat": "^0.3.4", + "@firebase/database-types": "^0.10.4", + "@types/node": ">=12.12.47", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", + "node-forge": "^1.3.1", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^6.6.0", + "@google-cloud/storage": "^6.9.5" + } + }, + "node_modules/firebase-functions": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.4.0.tgz", + "integrity": "sha512-Vdkr9/y/UKQez//cPm2Iu/9CeayqQ2tQF6o3KXozDDBokK9AOlAalVHImCpKo6nWptT/ncZ8djJFk5cR8l+E+A==", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "node-fetch": "^2.6.7", + "protobufjs": "^7.2.2" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": ">=14.10.0" + }, + "peerDependencies": { + "firebase-admin": "^10.0.0 || ^11.0.0" + } + }, + "node_modules/firebase-functions-test": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.1.0.tgz", + "integrity": "sha512-yfm9ToguShxmRXb7TINN88zE2bM9gsBbs7vMWVKJAxGcl/n1f/U0sT5k2yho676QIcSqXVSjCONU8W4cUEL+Sw==", + "dev": true, + "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", + "firebase-functions": ">=4.3.0", + "jest": ">=28.0.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "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==", + "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==", + "devOptional": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "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==", + "optional": true + }, + "node_modules/gaxios": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.0.tgz", + "integrity": "sha512-aezGIjb+/VfsJtIcHGcBSerNEDdfdHeMros+RbYbGpmonKWQCOVOes0LVZhn1lDtIgq55qq0HaxymIoae3Fl/A==", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "optional": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "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, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "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, + "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, + "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==", + "devOptional": 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, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/google-auth-library": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.8.0.tgz", + "integrity": "sha512-0iJn7IDqObDG5Tu9Tn2WemmJ31ksEa96IyK0J0OZCpTh6CrC6FrattwKX87h3qKVuprCJpdOGKc1Xi8V0kMh8Q==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.2.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.0.tgz", + "integrity": "sha512-2fyb61vWxUonHiArRNJQmE4tx5oY1ni8VPo08fzII409vDSCWG7apDX4qNOQ2GXXT82gLBn3d3P1Dydh7pWjyw==", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "~1.8.0", + "@grpc/proto-loader": "^0.7.0", + "@types/long": "^4.0.0", + "@types/rimraf": "^3.0.2", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^8.0.2", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.2.3", + "protobufjs-cli": "1.1.1", + "retry-request": "^5.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "optional": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "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==", + "devOptional": true + }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "optional": true, + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.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==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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==", + "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==" + }, + "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==", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "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==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "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, + "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==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "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, + "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==", + "devOptional": 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==" + }, + "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==", + "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, + "peer": true + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "peer": true, + "dependencies": { + "has": "^1.0.3" + }, + "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, + "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, + "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, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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, + "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/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "peer": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "peer": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "peer": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "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.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.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.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "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.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@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.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.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, + "peer": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "peer": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@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.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.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.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "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/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@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.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.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, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.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, + "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.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "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, + "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, + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "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, + "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, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "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==", + "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==", + "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==" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "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==", + "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.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", + "dependencies": { + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", + "debug": "^4.3.4", + "jose": "^4.10.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "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==", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "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, + "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, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "peer": true + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "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, + "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==" + }, + "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==", + "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==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "peer": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "optional": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "optional": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "optional": true + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "optional": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "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, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "peer": true, + "dependencies": { + "braces": "^3.0.2", + "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==", + "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==", + "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==", + "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, + "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==", + "devOptional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "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==", + "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, + "peer": true + }, + "node_modules/node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true, + "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, + "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, + "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==", + "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==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "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==", + "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, + "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, + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "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, + "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, + "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, + "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, + "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==", + "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, + "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==", + "devOptional": 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, + "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, + "peer": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "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, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "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, + "peer": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.4.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, + "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, + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto3-json-serializer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", + "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", + "optional": true, + "dependencies": { + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "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/protobufjs-cli": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", + "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", + "optional": true, + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/protobufjs-cli/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs-cli/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "peer": true + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "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.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true, + "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==", + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "peer": true, + "dependencies": { + "is-core-module": "^2.11.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, + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "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==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/retry-request/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/retry-request/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "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/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "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==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "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, + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "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, + "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, + "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, + "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==", + "devOptional": 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, + "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, + "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, + "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==", + "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==", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "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==", + "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, + "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, + "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, + "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, + "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, + "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==", + "devOptional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "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==", + "devOptional": 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, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/teeny-request": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", + "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "peer": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "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, + "peer": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "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, + "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==", + "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==" + }, + "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 + }, + "node_modules/tslib": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "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, + "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==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "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" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.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==", + "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==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "peer": true + }, + "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==", + "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, + "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==" + }, + "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==", + "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==", + "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==", + "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, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "devOptional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true + }, + "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, + "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/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "optional": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "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, + "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, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "peer": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.3.tgz", + "integrity": "sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==", + "dev": true, + "peer": true + }, + "@babel/core": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.1.tgz", + "integrity": "sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==", + "dev": true, + "peer": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.22.0", + "@babel/helper-compilation-targets": "^7.22.1", + "@babel/helper-module-transforms": "^7.22.1", + "@babel/helpers": "^7.22.0", + "@babel/parser": "^7.22.0", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "peer": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true + } + } + }, + "@babel/generator": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.3.tgz", + "integrity": "sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz", + "integrity": "sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/compat-data": "^7.22.0", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "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, + "peer": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "peer": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz", + "integrity": "sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==", + "dev": true, + "peer": true + }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "peer": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.21.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz", + "integrity": "sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true, + "peer": true + }, + "@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "dev": true, + "peer": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "peer": true + }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "peer": true + }, + "@babel/helpers": { + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.3.tgz", + "integrity": "sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==", + "dev": true, + "peer": true, + "requires": { + "@babel/template": "^7.21.9", + "@babel/traverse": "^7.22.1", + "@babel/types": "^7.22.3" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "peer": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "peer": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "peer": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "peer": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", + "integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", + "devOptional": true + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", + "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/template": { + "version": "7.21.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.21.9.tgz", + "integrity": "sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.21.4", + "@babel/parser": "^7.21.9", + "@babel/types": "^7.21.5" + } + }, + "@babel/traverse": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.4.tgz", + "integrity": "sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.22.3", + "@babel/helper-environment-visitor": "^7.22.1", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.22.4", + "@babel/types": "^7.22.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true + } + } + }, + "@babel/types": { + "version": "7.22.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz", + "integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@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, + "peer": true + }, + "@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", + "requires": { + "text-decoding": "^1.0.0" + } + }, + "@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + }, + "@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "requires": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "requires": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "requires": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@google-cloud/firestore": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.6.1.tgz", + "integrity": "sha512-Z41j2h0mrgBH9qNIVmbRLqGKc6XmdJtWipeKwdnGa/bPTP1gn2SGTrYyWnpfsLMEtzKSYieHPSkAFp5kduF2RA==", + "optional": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^3.5.7", + "protobufjs": "^7.0.0" + } + }, + "@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "optional": true + }, + "@google-cloud/promisify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "optional": true + }, + "@google-cloud/storage": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.10.1.tgz", + "integrity": "sha512-EtLlT0YbXtrbUxaNbEfTyTytrjELtl4i42flf8COg+Hu5+apdNjsFO9XEY39wshxAuVjLf4fCSm7GTSW+BD3gQ==", + "optional": true, + "requires": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true + } + } + }, + "@grpc/grpc-js": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz", + "integrity": "sha512-w84maJ6CKl5aApCMzFll0hxtFNT6or9WwMslobKaqWUEf1K+zhlL43bSQhFreyYWIWR+Z0xnVFC1KtLm4ZpM/A==", + "optional": true, + "requires": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + } + }, + "@grpc/proto-loader": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", + "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", + "optional": true, + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^17.7.2" + } + }, + "@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, + "peer": true, + "requires": { + "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" + } + }, + "@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, + "peer": true + }, + "@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@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.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + } + }, + "@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "peer": true, + "requires": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + } + }, + "@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "peer": true, + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + } + }, + "@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "peer": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@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": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "peer": true, + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "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.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "peer": true, + "requires": { + "@jest/schemas": "^29.4.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" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "peer": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "peer": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + } + } + }, + "@jsdoc/salty": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", + "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "optional": true, + "requires": { + "lodash": "^4.17.21" + } + }, + "@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==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true, + "peer": true + }, + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "peer": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.2.0.tgz", + "integrity": "sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg==", + "dev": true, + "peer": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@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==", + "optional": true + }, + "@types/babel__core": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "dev": true, + "peer": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "peer": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.0.tgz", + "integrity": "sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "optional": true, + "requires": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true, + "peer": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "peer": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "peer": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "requires": { + "@types/node": "*" + } + }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, + "@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "optional": true + }, + "@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" + }, + "@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true, + "peer": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "@types/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", + "optional": true, + "requires": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true, + "peer": true + }, + "@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "peer": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true, + "peer": true + }, + "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==", + "optional": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "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==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "optional": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "optional": true, + "requires": {} + }, + "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==", + "optional": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "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, + "peer": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "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 + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "peer": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true + }, + "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==", + "optional": true, + "requires": { + "retry": "0.13.1" + } + }, + "babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "peer": true, + "requires": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "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, + "peer": true, + "requires": { + "@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" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "peer": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@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-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "peer": true, + "requires": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "optional": true + }, + "bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "optional": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "optional": true + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "peer": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", + "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "dev": true, + "peer": true, + "requires": { + "caniuse-lite": "^1.0.30001489", + "electron-to-chromium": "^1.4.411", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + } + }, + "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, + "peer": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "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==" + }, + "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, + "peer": true + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "peer": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "peer": true + }, + "caniuse-lite": { + "version": "1.0.30001492", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz", + "integrity": "sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==", + "dev": true, + "peer": true + }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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, + "peer": true + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "peer": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true, + "peer": true + }, + "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, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "peer": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true, + "peer": true + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true + }, + "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==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "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, + "peer": true + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true, + "peer": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "optional": true + }, + "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, + "peer": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "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, + "peer": true + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "peer": true + }, + "duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "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==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "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==" + }, + "electron-to-chromium": { + "version": "1.4.417", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.417.tgz", + "integrity": "sha512-8rY8HdCxuSVY8wku3i/eDac4g1b4cSbruzocenrqBlzqruAZYHjQCHIjC66dLR9DXhEHTojsC4EjhZ8KmzwXqA==", + "dev": true, + "peer": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "peer": true + }, + "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 + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "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==", + "optional": true, + "requires": { + "once": "^1.4.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "optional": true + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true + }, + "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, + "peer": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "devOptional": true + }, + "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==" + }, + "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==", + "devOptional": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "optional": true + }, + "espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "optional": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "devOptional": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "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==", + "optional": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "peer": true, + "requires": { + "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" + } + }, + "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 + }, + "expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "peer": true, + "requires": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "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==", + "optional": true + }, + "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, + "peer": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "optional": true + }, + "fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "optional": true + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "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, + "peer": true, + "requires": { + "bser": "2.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "peer": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "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, + "peer": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "firebase-admin": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.9.0.tgz", + "integrity": "sha512-My7qrInVZFmImX8aTulrp9kgY6d88Wn+ie8UIXKzZ3SJqQQhDwFT7Q3pgQXK9RfdsUtcxJJ3rCK7MWBm4GGtuw==", + "requires": { + "@fastify/busboy": "^1.2.1", + "@firebase/database-compat": "^0.3.4", + "@firebase/database-types": "^0.10.4", + "@google-cloud/firestore": "^6.6.0", + "@google-cloud/storage": "^6.9.5", + "@types/node": ">=12.12.47", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", + "node-forge": "^1.3.1", + "uuid": "^9.0.0" + } + }, + "firebase-functions": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.4.0.tgz", + "integrity": "sha512-Vdkr9/y/UKQez//cPm2Iu/9CeayqQ2tQF6o3KXozDDBokK9AOlAalVHImCpKo6nWptT/ncZ8djJFk5cR8l+E+A==", + "requires": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "node-fetch": "^2.6.7", + "protobufjs": "^7.2.2" + } + }, + "firebase-functions-test": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.1.0.tgz", + "integrity": "sha512-yfm9ToguShxmRXb7TINN88zE2bM9gsBbs7vMWVKJAxGcl/n1f/U0sT5k2yho676QIcSqXVSjCONU8W4cUEL+Sw==", + "dev": true, + "requires": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5", + "ts-deepmerge": "^2.0.1" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true, + "peer": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "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==", + "optional": true + }, + "gaxios": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.0.tgz", + "integrity": "sha512-aezGIjb+/VfsJtIcHGcBSerNEDdfdHeMros+RbYbGpmonKWQCOVOes0LVZhn1lDtIgq55qq0HaxymIoae3Fl/A==", + "optional": true, + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + } + }, + "gcp-metadata": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz", + "integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==", + "optional": true, + "requires": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + } + }, + "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, + "peer": true + }, + "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 + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "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, + "peer": true + }, + "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, + "peer": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "devOptional": true, + "requires": { + "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" + } + }, + "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, + "peer": true + }, + "google-auth-library": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.8.0.tgz", + "integrity": "sha512-0iJn7IDqObDG5Tu9Tn2WemmJ31ksEa96IyK0J0OZCpTh6CrC6FrattwKX87h3qKVuprCJpdOGKc1Xi8V0kMh8Q==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.2.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-gax": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.0.tgz", + "integrity": "sha512-2fyb61vWxUonHiArRNJQmE4tx5oY1ni8VPo08fzII409vDSCWG7apDX4qNOQ2GXXT82gLBn3d3P1Dydh7pWjyw==", + "optional": true, + "requires": { + "@grpc/grpc-js": "~1.8.0", + "@grpc/proto-loader": "^0.7.0", + "@types/long": "^4.0.0", + "@types/rimraf": "^3.0.2", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^8.0.2", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.2.3", + "protobufjs-cli": "1.1.1", + "retry-request": "^5.0.0" + } + }, + "google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "optional": true, + "requires": { + "node-forge": "^1.3.1" + } + }, + "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==", + "devOptional": true + }, + "gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "optional": true, + "requires": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "devOptional": true + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "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, + "peer": true + }, + "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==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "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==" + }, + "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==", + "optional": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "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==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "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, + "peer": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "peer": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "peer": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "devOptional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "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==" + }, + "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, + "peer": true + }, + "is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "peer": true, + "requires": { + "has": "^1.0.3" + } + }, + "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 + }, + "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, + "peer": true + }, + "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, + "peer": true + }, + "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 + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "peer": true + }, + "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, + "peer": true, + "requires": { + "@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" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "peer": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "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, + "peer": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "peer": true + } + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "peer": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + } + }, + "jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "peer": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "peer": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "peer": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "peer": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.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.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "peer": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + } + }, + "jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "peer": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "peer": true + }, + "jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "peer": true, + "requires": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + } + }, + "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, + "peer": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "peer": true + }, + "jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "peer": true, + "requires": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + } + }, + "jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + } + }, + "jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "peer": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@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.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "peer": true, + "requires": { + "@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/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "dependencies": { + "semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "peer": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "peer": true, + "requires": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "peer": true + } + } + }, + "jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "peer": true, + "requires": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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, + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" + }, + "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, + "peer": true + }, + "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, + "peer": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, + "requires": { + "xmlcreate": "^2.0.4" + } + }, + "jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "optional": true, + "requires": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "peer": true + }, + "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==", + "optional": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "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, + "peer": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "peer": true + }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "dependencies": { + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jwks-rsa": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", + "requires": { + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", + "debug": "^4.3.4", + "jose": "^4.10.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "dependencies": { + "@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "peer": true + }, + "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, + "peer": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "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, + "peer": true + }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "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, + "peer": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "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==", + "optional": true + }, + "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==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "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==", + "requires": { + "yallist": "^4.0.0" + } + }, + "lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "requires": { + "semver": "^6.0.0" + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "peer": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "optional": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "optional": true + } + } + }, + "markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "optional": true, + "requires": {} + }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "optional": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, + "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==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "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, + "peer": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "peer": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "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==", + "requires": { + "mime-db": "1.52.0" + } + }, + "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, + "peer": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "optional": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "peer": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, + "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, + "peer": true + }, + "node-releases": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "dev": true, + "peer": true + }, + "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, + "peer": true + }, + "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, + "peer": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "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==" + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + }, + "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, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "peer": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "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, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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, + "peer": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "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, + "peer": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "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, + "peer": true + }, + "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, + "peer": true, + "requires": { + "@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" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "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, + "peer": true + }, + "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==", + "devOptional": true + }, + "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, + "peer": true + }, + "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, + "peer": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "peer": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "peer": true + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "peer": true + }, + "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, + "peer": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true + }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "peer": true, + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "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, + "peer": true + } + } + }, + "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, + "peer": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "proto3-json-serializer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", + "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", + "optional": true, + "requires": { + "protobufjs": "^7.0.0" + } + }, + "protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "requires": { + "@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" + }, + "dependencies": { + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + } + } + }, + "protobufjs-cli": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", + "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", + "optional": true, + "requires": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "optional": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "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==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "peer": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "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==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true, + "peer": true + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "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 + }, + "requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "requires": { + "lodash": "^4.17.21" + } + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "peer": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "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, + "peer": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "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, + "peer": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "peer": true + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true + }, + "retry-request": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", + "optional": true, + "requires": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "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==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "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" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "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, + "peer": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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, + "peer": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "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, + "peer": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "peer": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "peer": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true + }, + "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, + "peer": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "peer": true + }, + "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, + "peer": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "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==", + "optional": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "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, + "peer": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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, + "peer": true + }, + "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, + "peer": true + }, + "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==", + "devOptional": true + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "optional": true + }, + "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==", + "devOptional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "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, + "peer": true + }, + "teeny-request": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", + "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", + "optional": true, + "requires": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + } + }, + "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, + "peer": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "peer": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "peer": true + }, + "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, + "peer": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "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 + }, + "tslib": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "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, + "peer": true + }, + "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, + "peer": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true + }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "peer": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "optional": true + }, + "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==" + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + }, + "v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "peer": true + } + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "peer": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "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==" + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "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==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true + }, + "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, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "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 + }, + "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, + "peer": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "optional": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "devOptional": true, + "requires": { + "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" + } + }, + "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 + }, + "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 + } + } +} diff --git a/scripts/webframeworks-deploy-tests/functions/package.json b/scripts/webframeworks-deploy-tests/functions/package.json new file mode 100644 index 00000000000..392196b9844 --- /dev/null +++ b/scripts/webframeworks-deploy-tests/functions/package.json @@ -0,0 +1,23 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": { + "serve": "firebase emulators:start --only functions", + "shell": "firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "18" + }, + "main": "index.js", + "dependencies": { + "firebase-admin": "^11.8.0", + "firebase-functions": "^4.3.1" + }, + "devDependencies": { + "firebase-functions-test": "^3.1.0" + }, + "private": true +} diff --git a/scripts/webframeworks-deploy-tests/nextjs/next.config.js b/scripts/webframeworks-deploy-tests/nextjs/next.config.js index f4da21ee399..021cc97aa01 100644 --- a/scripts/webframeworks-deploy-tests/nextjs/next.config.js +++ b/scripts/webframeworks-deploy-tests/nextjs/next.config.js @@ -10,6 +10,28 @@ const nextConfig = { locales: ['en', 'fr'], defaultLocale: 'en', }, + rewrites: () => [{ + source: '/about', + destination: '/', + },], + redirects: () => [{ + source: '/about', + destination: '/', + permanent: true, + },], + headers: () => [{ + source: '/about', + headers: [ + { + key: 'x-custom-header', + value: 'my custom header value', + }, + { + key: 'x-another-custom-header', + value: 'my other custom header value', + }, + ], + },], } module.exports = nextConfig diff --git a/scripts/webframeworks-deploy-tests/run.sh b/scripts/webframeworks-deploy-tests/run.sh index f678a53053e..da7d13db0d2 100755 --- a/scripts/webframeworks-deploy-tests/run.sh +++ b/scripts/webframeworks-deploy-tests/run.sh @@ -6,11 +6,8 @@ set -e # Immediately exit on failure source scripts/set-default-credentials.sh -(cd scripts/webframeworks-deploy-tests/nextjs; -npm ci; -cd ../angular; -npm ci; -cd ..; -FIREBASE_CLI_EXPERIMENTS=webframeworks,pintags firebase emulators:exec "cd ../..; mocha scripts/webframeworks-deploy-tests/tests.ts" --project $FBTOOLS_TARGET_PROJECT --debug > firebase-emulators.log || \ -(cat firebase-emulators.log && exit 1); -cat firebase-emulators.log) +npm ci --prefix scripts/webframeworks-deploy-tests/nextjs +npm ci --prefix scripts/webframeworks-deploy-tests/angular +npm ci --prefix scripts/webframeworks-deploy-tests/functions + +FIREBASE_CLI_EXPERIMENTS=webframeworks,pintags firebase emulators:exec "mocha scripts/webframeworks-deploy-tests/tests.ts" --config scripts/webframeworks-deploy-tests/firebase.json --project demo-123 --debug diff --git a/scripts/webframeworks-deploy-tests/tests.ts b/scripts/webframeworks-deploy-tests/tests.ts index d223bb36ad6..58cc1bf3fc5 100644 --- a/scripts/webframeworks-deploy-tests/tests.ts +++ b/scripts/webframeworks-deploy-tests/tests.ts @@ -16,7 +16,7 @@ const NEXT_BASE_PATH: NextConfig["basePath"] = "base"; const ANGULAR_BASE_PATH = ""; const I18N_BASE = ""; const DEFAULT_LANG = "en"; -const LOG_FILE = "scripts/webframeworks-deploy-tests/firebase-emulators.log"; +const LOG_FILE = "firebase-debug.log"; const NEXT_SOURCE = `${__dirname}/nextjs`; async function getFilesListFromDir(dir: string): Promise { @@ -48,6 +48,149 @@ describe("webframeworks", function (this) { // This is not an empty block. }); + describe("build", () => { + it("should have the correct effective firebase.json", () => { + const result = readFileSync(LOG_FILE).toString(); + const effectiveFirebaseJSON = result + .split("[web frameworks] effective firebase.json: ") + .at(-1) + ?.split(new RegExp(`(\\[\\S+\\] )?\\[${new Date().getFullYear()}`))[0] + ?.trim(); + expect(effectiveFirebaseJSON && JSON.parse(effectiveFirebaseJSON), "firebase.json").to.eql({ + hosting: [ + { + target: "nextjs", + source: "nextjs", + frameworksBackend: { + maxInstances: 1, + region: "asia-east1", + }, + rewrites: [ + { + destination: "/base", + source: "/base/about", + }, + { + source: "/base/**", + function: { + functionId: "ssrdemonextjs", + region: "asia-east1", + pinTag: true, + }, + }, + { + function: { + functionId: "helloWorld", + }, + source: "helloWorld", + }, + ], + site: "demo-nextjs", + redirects: [ + { + destination: "/base/", + source: "/base/en/about", + type: 308, + }, + { + destination: "/base", + source: "/base/about", + type: 308, + }, + ], + headers: [ + { + headers: [ + { + key: "x-custom-header", + value: "my custom header value", + }, + { + key: "x-another-custom-header", + value: "my other custom header value", + }, + ], + source: "/base/about", + }, + { + source: "/base/app/api/static", + headers: [ + { + key: "content-type", + value: "application/json", + }, + { + key: "custom-header", + value: "custom-value", + }, + { + key: "x-next-cache-tags", + value: "/app/api/static/route", + }, + ], + }, + ], + cleanUrls: true, + trailingSlash: false, + i18n: { + root: "/", + }, + public: ".firebase/demo-nextjs/hosting", + webFramework: "next_ssr", + }, + { + target: "angular", + source: "angular", + frameworksBackend: { + maxInstances: 1, + region: "europe-west1", + }, + rewrites: [ + { + function: { + functionId: "helloWorld", + }, + source: "helloWorld", + }, + { + source: "/**", + function: { + functionId: "ssrdemoangular", + region: "europe-west1", + pinTag: true, + }, + }, + ], + site: "demo-angular", + redirects: [], + headers: [], + cleanUrls: true, + i18n: { + root: "/", + }, + public: ".firebase/demo-angular/hosting", + webFramework: "angular_ssr", + }, + ], + functions: [ + { + codebase: "default", + ignore: ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"], + source: "functions", + }, + { + codebase: "firebase-frameworks-demo-nextjs", + source: ".firebase/demo-nextjs/functions", + }, + { + codebase: "firebase-frameworks-demo-angular", + source: ".firebase/demo-angular/functions", + }, + ], + }); + }); + }); + describe("next.js", () => { describe("app directory", () => { it("should have working SSG", async () => { diff --git a/src/frameworks/angular/index.ts b/src/frameworks/angular/index.ts index 5c4dd4bdeec..c0c0e4d2831 100644 --- a/src/frameworks/angular/index.ts +++ b/src/frameworks/angular/index.ts @@ -65,10 +65,13 @@ export async function init(setup: any, config: any) { } export async function build(dir: string, configuration: string): Promise { - const { targets, serverTarget, serveOptimizedImages, locales, baseHref } = await getBuildConfig( - dir, - configuration - ); + const { + targets, + serverTarget, + serveOptimizedImages, + locales, + baseHref: baseUrl, + } = await getBuildConfig(dir, configuration); await warnIfCustomBuildScript(dir, name, DEFAULT_BUILD_SCRIPT); for (const target of targets) { // TODO there is a bug here. Spawn for now. @@ -86,12 +89,12 @@ export async function build(dir: string, configuration: string): Promise req.sendStatus(404);\n`; - rewriteSource = posix.join(baseUrl, "__image__"); + rewriteSource = posix.join(baseHref, "__image__"); } - return { bootstrapScript, packageJson, baseUrl, dotEnv, rewriteSource }; + return { bootstrapScript, packageJson, dotEnv, rewriteSource }; } diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 21be5fa8c73..42509442101 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -53,6 +53,7 @@ import { logWarning } from "../utils"; import { ensureTargeted } from "../functions/ensureTargeted"; import { isDeepStrictEqual } from "util"; import { resolveProjectPath } from "../projectPath"; +import { logger } from "../logger"; export { WebFrameworks }; @@ -255,7 +256,7 @@ export async function prepareFrameworks( getValidBuildTargets = GET_DEFAULT_BUILD_TARGETS, shouldUseDevModeHandle = DEFAULT_SHOULD_USE_DEV_MODE_HANDLE, } = WebFrameworks[framework]; - console.log( + logger.info( `\n${frameworksCallToAction(SupportLevelWarnings[support](name), docsUrl, " ")}\n` ); @@ -267,6 +268,10 @@ export async function prepareFrameworks( (await shouldUseDevModeHandle(frameworksBuildTarget, getProjectPath())); let codegenFunctionsDirectory: Framework["ɵcodegenFunctionsDirectory"]; + let baseUrl = ""; + const rewrites = []; + const redirects = []; + const headers = []; const devModeHandle = useDevModeHandle && @@ -287,18 +292,15 @@ export async function prepareFrameworks( [firebaseDefaults, frameworksBuildTarget], frameworksBuildTarget ); - const { - wantsBackend = false, - rewrites = [], - redirects = [], - headers = [], - trailingSlash, - i18n = false, - }: BuildResult = buildResult || {}; - - config.rewrites.push(...rewrites); - config.redirects.push(...redirects); - config.headers.push(...headers); + const { wantsBackend = false, trailingSlash, i18n = false }: BuildResult = buildResult || {}; + + if (buildResult) { + baseUrl = buildResult.baseUrl ?? baseUrl; + if (buildResult.headers) headers.push(...buildResult.headers); + if (buildResult.rewrites) rewrites.push(...buildResult.rewrites); + if (buildResult.redirects) redirects.push(...buildResult.redirects); + } + config.trailingSlash ??= trailingSlash; if (i18n) config.i18n ??= { root: I18N_ROOT }; @@ -374,22 +376,27 @@ export async function prepareFrameworks( packageJson, bootstrapScript, frameworksEntry = framework, - baseUrl = "", dotEnv = {}, - rewriteSource = posix.join(baseUrl, "**"), + rewriteSource, } = await codegenFunctionsDirectory(getProjectPath(), functionsDist, frameworksBuildTarget); - config.rewrites = [ - { - source: rewriteSource, - function: { - functionId, - region: ssrRegion, - pinTag: experiments.isEnabled("pintags"), - }, + const rewrite = { + source: rewriteSource || posix.join(baseUrl, "**"), + function: { + functionId, + region: ssrRegion, + pinTag: experiments.isEnabled("pintags"), }, - ...config.rewrites, - ]; + }; + + // If the rewriteSource is overridden, we're talking a very specific rewrite. E.g, Image Optimization + // in this case, we should ensure that it's the first priority—otherwise defer to the push/unshift + // logic based off the baseUrl + if (rewriteSource) { + config.rewrites.unshift(rewrite); + } else { + rewrites.push(rewrite); + } // Set the framework entry in the env variables to handle generation of the functions.yaml process.env.__FIREBASE_FRAMEWORKS_ENTRY__ = frameworksEntry; @@ -527,13 +534,20 @@ ${ } } + const ourConfigShouldComeFirst = !["", "/"].includes(baseUrl); + const operation = ourConfigShouldComeFirst ? "unshift" : "push"; + + config.rewrites[operation](...rewrites); + config.redirects[operation](...redirects); + config.headers[operation](...headers); + if (firebaseDefaults) { const encodedDefaults = Buffer.from(JSON.stringify(firebaseDefaults)).toString("base64url"); const expires = new Date(new Date().getTime() + 60_000_000_000); const sameSite = "Strict"; const path = `/`; config.headers.push({ - source: "**/*.[jt]s", + source: posix.join(baseUrl, "**", "*.[jt]s"), headers: [ { key: "Set-Cookie", @@ -543,10 +557,11 @@ ${ }); } } - if (process.env.DEBUG) { - console.log("Effective firebase.json:"); - console.log(JSON.stringify(configs, undefined, 2)); - } + + logger.debug( + "[web frameworks] effective firebase.json: ", + JSON.stringify({ hosting: configs, functions: options.config.get("functions") }, undefined, 2) + ); // Clean up memos/caches BUILD_MEMO.clear(); diff --git a/src/frameworks/interfaces.ts b/src/frameworks/interfaces.ts index d8f79a23d7c..06cf7062aaf 100644 --- a/src/frameworks/interfaces.ts +++ b/src/frameworks/interfaces.ts @@ -32,9 +32,14 @@ export interface BuildResult { wantsBackend?: boolean; trailingSlash?: boolean; i18n?: boolean; + baseUrl?: string; } -export type RequestHandler = (req: IncomingMessage, res: ServerResponse, next: () => void) => void; +export type RequestHandler = ( + req: IncomingMessage, + res: ServerResponse, + next: () => void +) => void | Promise; export type FrameworksOptions = HostingOptions & Options & { @@ -77,7 +82,6 @@ export interface Framework { bootstrapScript?: string; packageJson: any; frameworksEntry?: string; - baseUrl?: string; dotEnv?: Record; rewriteSource?: string; }>; diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index 1a2c9360c61..e64ae4865d1 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -67,6 +67,7 @@ import { APP_PATHS_MANIFEST, } from "./constants"; import { getAllSiteDomains } from "../../hosting/api"; +import { logger } from "../../logger"; const DEFAULT_BUILD_SCRIPT = ["next build"]; const PUBLIC_DIR = "public"; @@ -118,7 +119,7 @@ export async function build(dir: string): Promise { }); const reasonsForBackend = new Set(); - const { distDir, trailingSlash, basePath } = await getConfig(dir); + const { distDir, trailingSlash, basePath: baseUrl } = await getConfig(dir); if (await isUsingMiddleware(join(dir, distDir), false)) { reasonsForBackend.add("middleware"); @@ -198,7 +199,7 @@ export async function build(dir: string): Promise { const headersFromMetaFiles = await getHeadersFromMetaFiles( dir, distDir, - basePath, + baseUrl, appPathRoutesManifest ); headers.push(...headersFromMetaFiles); @@ -261,19 +262,24 @@ export async function build(dir: string): Promise { const wantsBackend = reasonsForBackend.size > 0; if (wantsBackend) { - const numberOfReasonsToList = process.env.DEBUG ? Infinity : DEFAULT_NUMBER_OF_REASONS_TO_LIST; - console.log("Building a Cloud Function to run this application. This is needed due to:"); - for (const reason of Array.from(reasonsForBackend).slice(0, numberOfReasonsToList)) { - console.log(` • ${reason}`); + logger.info("Building a Cloud Function to run this application. This is needed due to:"); + for (const reason of Array.from(reasonsForBackend).slice( + 0, + DEFAULT_NUMBER_OF_REASONS_TO_LIST + )) { + logger.info(` • ${reason}`); } - if (reasonsForBackend.size > numberOfReasonsToList) { - console.log( + for (const reason of Array.from(reasonsForBackend).slice(DEFAULT_NUMBER_OF_REASONS_TO_LIST)) { + logger.debug(` • ${reason}`); + } + if (reasonsForBackend.size > DEFAULT_NUMBER_OF_REASONS_TO_LIST && !process.env.DEBUG) { + logger.info( ` • and ${ - reasonsForBackend.size - numberOfReasonsToList + reasonsForBackend.size - DEFAULT_NUMBER_OF_REASONS_TO_LIST } other reasons, use --debug to see more` ); } - console.log(""); + logger.info(""); } const i18n = !!nextjsI18n; @@ -285,6 +291,7 @@ export async function build(dir: string): Promise { rewrites, trailingSlash, i18n, + baseUrl, }; } @@ -391,14 +398,13 @@ export async function ɵcodegenPublicDirectory( await Promise.all( Object.entries(routesToCopy).map(async ([path, route]) => { if (route.initialRevalidateSeconds) { - if (process.env.DEBUG) console.log(`skipping ${path} due to revalidate`); + logger.debug(`skipping ${path} due to revalidate`); return; } if (pathsUsingsFeaturesNotSupportedByHosting.some((it) => path.match(it))) { - if (process.env.DEBUG) - console.log( - `skipping ${path} due to it matching an unsupported rewrite/redirect/header or middlware` - ); + logger.debug( + `skipping ${path} due to it matching an unsupported rewrite/redirect/header or middlware` + ); return; } const appPathRoute = @@ -415,8 +421,7 @@ export async function ɵcodegenPublicDirectory( matchingI18nDomain.locales.includes(locale); if (!includeOnThisDomain) { - if (process.env.DEBUG) - console.log(`skipping ${path} since it is for a locale not deployed on this domain`); + logger.debug(`skipping ${path} since it is for a locale not deployed on this domain`); return; } @@ -472,7 +477,7 @@ const BUNDLE_NEXT_CONFIG_TIMEOUT = 10_000; * Create a directory for SSR content. */ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: string) { - const { distDir, basePath } = await getConfig(sourceDir); + const { distDir } = await getConfig(sourceDir); const packageJson = await readJSON(join(sourceDir, "package.json")); // Bundle their next.config.js with esbuild via NPX, pinned version was having troubles on m1 // macs and older Node versions; either way, we should avoid taking on any deps in firebase-tools @@ -545,7 +550,7 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string, destDir: st await mkdirp(join(destDir, distDir)); await copy(join(sourceDir, distDir), join(destDir, distDir)); - return { packageJson, frameworksEntry: "next.js", basePath }; + return { packageJson, frameworksEntry: "next.js" }; } /** diff --git a/src/frameworks/nuxt/index.ts b/src/frameworks/nuxt/index.ts index a5871f1fb52..8b79667f9c1 100644 --- a/src/frameworks/nuxt/index.ts +++ b/src/frameworks/nuxt/index.ts @@ -44,7 +44,7 @@ export async function build(cwd: string) { const cli = getNodeModuleBin("nuxt", cwd); const { ssr: wantsBackend, - app: { baseURL }, + app: { baseURL: baseUrl }, } = await getConfig(cwd); const command = wantsBackend ? ["build"] : ["generate"]; const build = spawnSync(cli, command, { @@ -57,11 +57,11 @@ export async function build(cwd: string) { ? [] : [ { - source: posix.join(baseURL, "**"), - destination: posix.join(baseURL, "200.html"), + source: posix.join(baseUrl, "**"), + destination: posix.join(baseUrl, "200.html"), }, ]; - return { wantsBackend, rewrites }; + return { wantsBackend, rewrites, baseUrl }; } export async function ɵcodegenPublicDirectory(root: string, dest: string) { @@ -82,11 +82,7 @@ export async function ɵcodegenFunctionsDirectory(sourceDir: string) { packageJson.dependencies ||= {}; packageJson.dependencies["nitro-output"] = `file:${serverDir}`; - const { - app: { baseURL: baseUrl }, - } = await getConfig(sourceDir); - - return { packageJson, frameworksEntry: "nitro", baseUrl }; + return { packageJson, frameworksEntry: "nitro" }; } export async function getDevModeHandle(cwd: string) { diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index acf58c336cf..4c139fc9ab3 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -18,7 +18,7 @@ import { NPM_COMMAND_TIMEOUT_MILLIES, VALID_LOCALE_FORMATS, } from "./constants"; -import { BUILD_TARGET_PURPOSE } from "./interfaces"; +import { BUILD_TARGET_PURPOSE, RequestHandler } from "./interfaces"; // Use "true &&"" to keep typescript from compiling this file and rewriting // the import statement into a require @@ -63,7 +63,21 @@ export async function warnIfCustomBuildScript( } } -type RequestHandler = (req: IncomingMessage, res: ServerResponse) => Promise; +function proxyResponse(original: ServerResponse, next: () => void) { + return (response: IncomingMessage | ServerResponse) => { + const { statusCode, statusMessage } = response; + if (!statusCode) { + original.end(); + return; + } + if (statusCode === 404) { + return next(); + } + const headers = "getHeaders" in response ? response.getHeaders() : response.headers; + original.writeHead(statusCode, statusMessage, headers); + response.pipe(original); + }; +} export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { const agent = new Agent({ keepAlive: true }); @@ -100,8 +114,12 @@ export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { }; const req = httpRequest(opts, (response) => { const { statusCode, statusMessage, headers } = response; - originalRes.writeHead(statusCode!, statusMessage, headers); - response.pipe(originalRes); + if (statusCode === 404) { + next(); + } else { + originalRes.writeHead(statusCode!, statusMessage, headers); + response.pipe(originalRes); + } }); originalReq.pipe(req); req.on("error", (err) => { @@ -109,7 +127,9 @@ export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { originalRes.end(); }); } else { - await hostOrRequestHandler(originalReq, originalRes); + await Promise.resolve(hostOrRequestHandler(originalReq, originalRes, next)); + const proxiedRes = new ServerResponse(originalReq); + proxyResponse(originalRes, next)(proxiedRes); } }; } From 76f159a23372112900e9e9b39631a6a48a71d4d5 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 1 Jun 2023 22:34:48 +0000 Subject: [PATCH 1008/1699] 12.3.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 860dec4d957..fb524af2aa3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "12.2.1", + "version": "12.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "12.2.1", + "version": "12.3.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 79b3482916a..510bff324d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "12.2.1", + "version": "12.3.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From ac467a17f294183e276f4acc8c205fcb6c5792ee Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 1 Jun 2023 22:35:01 +0000 Subject: [PATCH 1009/1699] [firebase-release] Removed change log and reset repo after 12.3.0 release --- CHANGELOG.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6822691b7c4..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +0,0 @@ -- Fix a bug preventing web framework's dev-mode from working out-of-box with Firebase Authentication. (#5894) -- Address additional cases where we were attempting to deploy a framework's development bundle (#5895) -- NextJS rewrites should be prefixed with the basePath defined in next.config.js (#5923) -- Web Frameworks emulators will again respect existing Cloud Functions rewrites (#5923) -- Web Frameworks rewrites/redirects/headers will only prepend those in firebase.json if there's a baseUrl (#5923) -- Fixes issue where Authentication emulator creates a user if empty email and empty password is provided. (#5639) -- Improve error message raised when `--import` flag directory does not exist. (#5851) -- Switch `ext:dev:init` to default 'billingRequired' to true in `extension.yaml` -- Remove `LOCATION` param from the `extensions.yaml` template for `ext:dev:init` -- Support Astro hybrid rendering (#5898) From e0d0b1b6cb9e1f7355d88cbd31546496dcbc9e0e Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 2 Jun 2023 09:47:50 -0700 Subject: [PATCH 1010/1699] Allow secrets to be set before deploying functions (#5918) * Allow secrets to be set before deploying functions * Changelog * Remove debug prints; run formatter --------- Co-authored-by: Daniel Lee --- CHANGELOG.md | 1 + src/commands/functions-secrets-access.ts | 2 ++ src/commands/functions-secrets-destroy.ts | 1 + src/commands/functions-secrets-get.ts | 2 ++ src/commands/functions-secrets-prune.ts | 5 +++-- src/commands/functions-secrets-set.ts | 14 ++++++++++++++ src/functions/secrets.ts | 10 ++++++++++ 7 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..b09f81abf14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- firebase functions:secrets:\* ensure the secretmanager API is enabled (#5918) diff --git a/src/commands/functions-secrets-access.ts b/src/commands/functions-secrets-access.ts index e82849d01ca..fde041e6b89 100644 --- a/src/commands/functions-secrets-access.ts +++ b/src/commands/functions-secrets-access.ts @@ -3,11 +3,13 @@ import { logger } from "../logger"; import { Options } from "../options"; import { needProjectId } from "../projectUtils"; import { accessSecretVersion } from "../gcp/secretManager"; +import * as secrets from "../functions/secrets"; export const command = new Command("functions:secrets:access [@version]") .description( "Access secret value given secret and its version. Defaults to accessing the latest version." ) + .before(secrets.ensureApi) .action(async (key: string, options: Options) => { const projectId = needProjectId(options); let [name, version] = key.split("@"); diff --git a/src/commands/functions-secrets-destroy.ts b/src/commands/functions-secrets-destroy.ts index d9fadc6af6c..2314620abef 100644 --- a/src/commands/functions-secrets-destroy.ts +++ b/src/commands/functions-secrets-destroy.ts @@ -17,6 +17,7 @@ import * as args from "../deploy/functions/args"; export const command = new Command("functions:secrets:destroy [@version]") .description("Destroy a secret. Defaults to destroying the latest version.") .withForce("Destroys a secret without confirmation.") + .before(secrets.ensureApi) .action(async (key: string, options: Options) => { const projectId = needProjectId(options); const projectNumber = await needProjectNumber(options); diff --git a/src/commands/functions-secrets-get.ts b/src/commands/functions-secrets-get.ts index 48e52403b10..1115db2c086 100644 --- a/src/commands/functions-secrets-get.ts +++ b/src/commands/functions-secrets-get.ts @@ -6,9 +6,11 @@ import { Options } from "../options"; import { needProjectId } from "../projectUtils"; import { listSecretVersions } from "../gcp/secretManager"; import { requirePermissions } from "../requirePermissions"; +import * as secrets from "../functions/secrets"; export const command = new Command("functions:secrets:get ") .description("Get metadata for secret and its versions") + .before(secrets.ensureApi) .before(requirePermissions, ["secretmanager.secrets.get"]) .action(async (key: string, options: Options) => { const projectId = needProjectId(options); diff --git a/src/commands/functions-secrets-prune.ts b/src/commands/functions-secrets-prune.ts index f4ec99db7dc..50fbd9d0f0e 100644 --- a/src/commands/functions-secrets-prune.ts +++ b/src/commands/functions-secrets-prune.ts @@ -3,7 +3,7 @@ import * as backend from "../deploy/functions/backend"; import { Command } from "../command"; import { Options } from "../options"; import { needProjectId, needProjectNumber } from "../projectUtils"; -import { pruneSecrets } from "../functions/secrets"; +import * as secrets from "../functions/secrets"; import { requirePermissions } from "../requirePermissions"; import { isFirebaseManaged } from "../deploymentTool"; import { logBullet, logSuccess } from "../utils"; @@ -13,6 +13,7 @@ import { destroySecretVersion } from "../gcp/secretManager"; export const command = new Command("functions:secrets:prune") .withForce("Destroys unused secrets without prompt") .description("Destroys unused secrets") + .before(secrets.ensureApi) .before(requirePermissions, [ "cloudfunctions.functions.list", "secretmanager.secrets.list", @@ -30,7 +31,7 @@ export const command = new Command("functions:secrets:prune") .allEndpoints(haveBackend) .filter((e) => isFirebaseManaged(e.labels || [])); - const pruned = await pruneSecrets({ projectNumber, projectId }, haveEndpoints); + const pruned = await secrets.pruneSecrets({ projectNumber, projectId }, haveEndpoints); if (pruned.length === 0) { logBullet("All secrets are in use. Nothing to prune today."); diff --git a/src/commands/functions-secrets-set.ts b/src/commands/functions-secrets-set.ts index d8608968f3b..c3a42ebf7ab 100644 --- a/src/commands/functions-secrets-set.ts +++ b/src/commands/functions-secrets-set.ts @@ -3,6 +3,7 @@ import * as fs from "fs"; import * as clc from "colorette"; +import { logger } from "../logger"; import { ensureValidKey, ensureSecret } from "../functions/secrets"; import { Command } from "../command"; import { requirePermissions } from "../requirePermissions"; @@ -14,10 +15,12 @@ import { addVersion, toSecretVersionResourceName } from "../gcp/secretManager"; import * as secrets from "../functions/secrets"; import * as backend from "../deploy/functions/backend"; import * as args from "../deploy/functions/args"; +import { check } from "../ensureApiEnabled"; export const command = new Command("functions:secrets:set ") .description("Create or update a secret for use in Cloud Functions for Firebase.") .withForce("Automatically updates functions to use the new secret.") + .before(secrets.ensureApi) .before(requirePermissions, [ "secretmanager.secrets.create", "secretmanager.secrets.get", @@ -60,6 +63,17 @@ export const command = new Command("functions:secrets:set ") return; } + const functionsEnabled = await check( + projectId, + "cloudfunctions.googleapis.com", + "functions", + /* silent= */ true + ); + if (!functionsEnabled) { + logger.debug("Customer set secrets before enabling functions. Exiting"); + return; + } + const haveBackend = await backend.existingBackend({ projectId } as args.Context); const endpointsToUpdate = backend .allEndpoints(haveBackend) diff --git a/src/functions/secrets.ts b/src/functions/secrets.ts index f2cf45e5e4d..98df34e9852 100644 --- a/src/functions/secrets.ts +++ b/src/functions/secrets.ts @@ -2,6 +2,7 @@ import * as utils from "../utils"; import * as poller from "../operation-poller"; import * as gcf from "../gcp/cloudfunctions"; import * as backend from "../deploy/functions/backend"; +import * as ensureApiEnabled from "../ensureApiEnabled"; import { createSecret, destroySecretVersion, @@ -22,6 +23,7 @@ import { validateKey } from "./env"; import { logger } from "../logger"; import { functionsOrigin } from "../api"; import { assertExhaustive } from "../functional"; +import { needProjectId } from "../projectUtils"; const FIREBASE_MANAGED = "firebase-managed"; @@ -52,6 +54,14 @@ function toUpperSnakeCase(key: string): string { .toUpperCase(); } +/** + * Utility used in the "before" command annotation to enable the API. + */ +export function ensureApi(options: any): Promise { + const projectId = needProjectId(options); + return ensureApiEnabled.ensure(projectId, "secretmanager.googleapis.com", "runtimeconfig", true); +} + /** * Validate and transform keys to match the convention recommended by Firebase. */ From 858695fd07ee0b4d858ba3bf62617de252c28377 Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Fri, 2 Jun 2023 18:32:38 -0700 Subject: [PATCH 1011/1699] Export on exit typing (#5932) * Add some more explicit typing to exportOnExit See a similar example here: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgCoHcD2AhTmA2yA3gFDLJwBcyARnvhHCANxm3V0GMskC+JoSLEQoA8iAi4CxNlU4MmrfjACuIBGGCYQyABYR8+TAAoADnChwAttXGT6ASmS2JUwqXIJtAZy4A6IwBzYwAic0sbEOQAamQAKQBlUQA5P28wKFBA4BgATzMLawcHVnJw6z84ZABeZHh8bwhS5C8QXwYAzGCQq0wAExzgCD7KKNjElLSMrJz88qti5qgIMBUoHXmlElb05DAsN2oMHHoa4ioMlQgAGnZ6xt5WHf8g0OXV9eHRmPik1PTMiBsnljPpDJhkMZ9icCMUSkA --- src/emulator/commandUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index 712288d7e2a..888f1973609 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -190,7 +190,10 @@ export function parseInspectionPort(options: any): number { * export data the first time they start developing on a clean project. * @param options */ -export function setExportOnExitOptions(options: any) { +export function setExportOnExitOptions(options: { + exportOnExit: boolean | string; + import?: string; +}): void { if (options.exportOnExit || typeof options.exportOnExit === "string") { // note that options.exportOnExit may be a bool when used as a flag without a [dir] argument: // --import ./data --export-on-exit From afcbcdd284585ea6523fb672d9cd110ce594543b Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 6 Jun 2023 10:16:31 -0700 Subject: [PATCH 1012/1699] Framework composer (#5839) Internal code to dockerize projects. --- src/commands/index.ts | 2 + .../internaltesting-frameworks-compose.ts | 21 ++ src/frameworks/compose/discover/index.ts | 28 +++ src/frameworks/compose/driver/docker.ts | 209 ++++++++++++++++++ src/frameworks/compose/driver/hooks.ts | 35 +++ src/frameworks/compose/driver/index.ts | 19 ++ src/frameworks/compose/driver/local.ts | 46 ++++ src/frameworks/compose/index.ts | 38 ++++ src/frameworks/compose/interfaces.ts | 59 +++++ .../frameworks/compose/engine/docker.spec.ts | 92 ++++++++ .../frameworks/compose/engine/hooks.spec.ts | 42 ++++ 11 files changed, 591 insertions(+) create mode 100644 src/commands/internaltesting-frameworks-compose.ts create mode 100644 src/frameworks/compose/discover/index.ts create mode 100644 src/frameworks/compose/driver/docker.ts create mode 100644 src/frameworks/compose/driver/hooks.ts create mode 100644 src/frameworks/compose/driver/index.ts create mode 100644 src/frameworks/compose/driver/local.ts create mode 100644 src/frameworks/compose/index.ts create mode 100644 src/frameworks/compose/interfaces.ts create mode 100644 src/test/frameworks/compose/engine/docker.spec.ts create mode 100644 src/test/frameworks/compose/engine/hooks.spec.ts diff --git a/src/commands/index.ts b/src/commands/index.ts index b67a6a20657..8d7de5c2362 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -143,6 +143,8 @@ export function load(client: any): any { client.init = loadCommand("init"); if (experiments.isEnabled("internaltesting")) { client.internaltesting = {}; + client.internaltesting.frameworks = {}; + client.internaltesting.frameworks.compose = loadCommand("internaltesting-frameworks-compose"); client.internaltesting.functions = {}; client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover"); } diff --git a/src/commands/internaltesting-frameworks-compose.ts b/src/commands/internaltesting-frameworks-compose.ts new file mode 100644 index 00000000000..ee85ec27d8a --- /dev/null +++ b/src/commands/internaltesting-frameworks-compose.ts @@ -0,0 +1,21 @@ +import { Command } from "../command"; +import { Options } from "../options"; +import { logger } from "../logger"; +import { Mode, SUPPORTED_MODES } from "../frameworks/compose/driver"; +import { compose } from "../frameworks/compose"; +import { FirebaseError } from "../error"; + +export const command = new Command("internaltesting:frameworks:compose") + .option("-m, --mode ", "Composer mode (local or docker)", "local") + .description("compose framework in current directory") + .action((options: Options) => { + const mode = options.mode as string; + if (!(SUPPORTED_MODES as unknown as string[]).includes(mode)) { + throw new FirebaseError( + `Unsupported mode ${mode}. Supported modes are [${SUPPORTED_MODES.join(", ")}]` + ); + } + const bundle = compose(mode as Mode); + logger.info(JSON.stringify(bundle, null, 2)); + return {}; + }); diff --git a/src/frameworks/compose/discover/index.ts b/src/frameworks/compose/discover/index.ts new file mode 100644 index 00000000000..8d3c8542ab3 --- /dev/null +++ b/src/frameworks/compose/discover/index.ts @@ -0,0 +1,28 @@ +import { AppSpec } from "../interfaces"; + +/** + * Discover framework in the given project directory + */ +export function discover(): AppSpec { + return { + baseImage: "us-docker.pkg.dev/firestack-build/test/run:latest", + environmentVariables: { + NODE_ENV: "PRODUCTION", + }, + installCommand: "npm install", + buildCommand: "npm run build", + startCommand: "npm run start", + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + afterInstall: (b) => { + console.log("HOOK: AFTER INSTALL"); + return { ...b, version: "v1alpha", notes: "afterInstall" }; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + afterBuild(b) { + console.log("HOOK: AFTER BUILD"); + return { ...b, version: "v1alpha", notes: "afterBuild" }; + }, + }; +} diff --git a/src/frameworks/compose/driver/docker.ts b/src/frameworks/compose/driver/docker.ts new file mode 100644 index 00000000000..70d9d4344b9 --- /dev/null +++ b/src/frameworks/compose/driver/docker.ts @@ -0,0 +1,209 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as spawn from "cross-spawn"; + +import { AppBundle, AppSpec, Driver, Hook } from "../interfaces"; +import { BUNDLE_PATH, genHookScript } from "./hooks"; + +const ADAPTER_SCRIPTS_PATH = "./.firebase/adapters" as const; + +const DOCKER_STAGE_INSTALL = "installer" as const; +const DOCKER_STAGE_BUILD = "builder" as const; + +export class DockerfileBuilder { + private dockerfile = ""; + private lastStage = ""; + + from(image: string, name?: string): DockerfileBuilder { + this.dockerfile += `FROM ${image}`; + if (name) { + this.dockerfile += ` AS ${name}`; + this.lastStage = name; + } + this.dockerfile += "\n"; + return this; + } + + fromLastStage(name: string): DockerfileBuilder { + return this.from(this.lastStage, name); + } + + /** + * Last `from` but does not update the lastStage. + */ + tempFrom(image: string, name?: string): DockerfileBuilder { + this.dockerfile += `FROM ${image}`; + if (name) { + this.dockerfile += ` AS ${name}`; + } + this.dockerfile += "\n"; + return this; + } + + workdir(dir: string): DockerfileBuilder { + this.dockerfile += `WORKDIR ${dir}\n`; + return this; + } + + copyForFirebase(src: string, dest: string, from?: string): DockerfileBuilder { + if (from) { + this.dockerfile += `COPY --chown=firebase:firebase --from=${from} ${src} ${dest}\n`; + } else { + this.dockerfile += `COPY --chown=firebase:firebase ${src} ${dest}\n`; + } + return this; + } + + copyFrom(src: string, dest: string, from: string) { + this.dockerfile += `COPY --from=${from} ${src} ${dest}\n`; + return this; + } + + run(cmd: string, mount?: string): DockerfileBuilder { + if (mount) { + this.dockerfile += `RUN --mount=${mount} ${cmd}\n`; + } else { + this.dockerfile += `RUN ${cmd}\n`; + } + return this; + } + + env(key: string, value: string): DockerfileBuilder { + this.dockerfile += `ENV ${key}="${value}"\n`; + return this; + } + + envs(envs: Record): DockerfileBuilder { + for (const [key, value] of Object.entries(envs)) { + this.env(key, value); + } + return this; + } + + cmd(cmds: string[]): DockerfileBuilder { + this.dockerfile += `CMD [${cmds.map((c) => `"${c}"`).join(", ")}]\n`; + return this; + } + + user(user: string): DockerfileBuilder { + this.dockerfile += `USER ${user}\n`; + return this; + } + + toString(): string { + return this.dockerfile; + } +} + +export class DockerDriver implements Driver { + private dockerfileBuilder; + + constructor(readonly spec: AppSpec) { + this.dockerfileBuilder = new DockerfileBuilder(); + this.dockerfileBuilder.from(spec.baseImage, "base").user("firebase"); + } + + private execDockerPush(args: string[]) { + console.log(`executing docker build: ${args.join(" ")}`); + return spawn.sync("docker", ["push", ...args], { + stdio: [/* stdin= */ "pipe", /* stdout= */ "inherit", /* stderr= */ "inherit"], + }); + } + + private execDockerBuild(args: string[], contextDir: string) { + console.log(`executing docker build: ${args.join(" ")} ${contextDir}`); + console.log(this.dockerfileBuilder.toString()); + return spawn.sync("docker", ["buildx", "build", ...args, "-f", "-", contextDir], { + env: { ...process.env, ...this.spec.environmentVariables }, + input: this.dockerfileBuilder.toString(), + stdio: [/* stdin= */ "pipe", /* stdout= */ "inherit", /* stderr= */ "inherit"], + }); + } + + private buildStage(stage: string, contextDir: string, tag?: string): void { + console.log(`Building stage: ${stage}`); + const args = ["--target", stage]; + if (tag) { + args.push("--tag", tag); + } + const ret = this.execDockerBuild(args, contextDir); + if (ret.error || ret.status !== 0) { + throw new Error(`Failed to execute stage ${stage}: error=${ret.error} status=${ret.status}`); + } + } + + private exportBundle(stage: string, contextDir: string): AppBundle { + const exportStage = `${stage}-export`; + this.dockerfileBuilder + .tempFrom("scratch", exportStage) + .copyFrom(BUNDLE_PATH, "/bundle.json", stage); + const ret = this.execDockerBuild( + ["--target", exportStage, "--output", ".firebase/.output"], + contextDir + ); + if (ret.error || ret.status !== 0) { + throw new Error(`Failed to export bundle ${stage}: error=${ret.error} status=${ret.status}`); + } + return JSON.parse(fs.readFileSync("./.firebase/.output/bundle.json", "utf8")) as AppBundle; + } + + install(): void { + this.dockerfileBuilder + .fromLastStage(DOCKER_STAGE_INSTALL) + .workdir("/home/firebase/app") + .envs(this.spec.environmentVariables || {}) + .copyForFirebase("package.json", ".") + .run(this.spec.installCommand); + this.buildStage(DOCKER_STAGE_INSTALL, "."); + } + + build(): void { + this.dockerfileBuilder + .fromLastStage(DOCKER_STAGE_BUILD) + .copyForFirebase(".", ".") + .run(this.spec.buildCommand); + this.buildStage(DOCKER_STAGE_BUILD, "."); + } + + export(bundle: AppBundle): void { + const startCmd = bundle.server?.start.cmd; + if (startCmd) { + const exportStage = "exporter"; + this.dockerfileBuilder + .from(this.spec.baseImage, exportStage) + .workdir("/home/firebase/app") + .copyForFirebase("/home/firebase/app", ".", DOCKER_STAGE_BUILD) + .cmd(startCmd); + const imageName = `us-docker.pkg.dev/${process.env.PROJECT_ID}/test/demo-nodappe`; + this.buildStage(exportStage, ".", imageName); + const ret = this.execDockerPush([imageName]); + if (ret.error || ret.status !== 0) { + throw new Error( + `Failed to push image ${imageName}: error=${ret.error} status=${ret.status}` + ); + } + } + } + + execHook(bundle: AppBundle, hook: Hook): AppBundle { + // Prepare hook execution by writing the node script locally + const hookScript = `hook-${Date.now()}.js`; + const hookScriptSrc = genHookScript(bundle, hook); + + if (!fs.existsSync(ADAPTER_SCRIPTS_PATH)) { + fs.mkdirSync(ADAPTER_SCRIPTS_PATH, { recursive: true }); + } + fs.writeFileSync(path.join(ADAPTER_SCRIPTS_PATH, hookScript), hookScriptSrc); + + // Execute the hook inside the docker sandbox + const hookStage = path.basename(hookScript, ".js"); + this.dockerfileBuilder + .fromLastStage(hookStage) + .run( + `NODE_PATH=./node_modules node /framework/adapters/${hookScript}`, + `source=${ADAPTER_SCRIPTS_PATH},target=/framework/adapters` + ); + this.buildStage(hookStage, "."); + return this.exportBundle(hookStage, "."); + } +} diff --git a/src/frameworks/compose/driver/hooks.ts b/src/frameworks/compose/driver/hooks.ts new file mode 100644 index 00000000000..e4fa8aa7182 --- /dev/null +++ b/src/frameworks/compose/driver/hooks.ts @@ -0,0 +1,35 @@ +import { AppBundle, Hook } from "../interfaces"; + +export const BUNDLE_PATH = "/home/firebase/app/.firebase/.output/bundle.json" as const; + +/** + * Generate a script that wraps the given hook to output the resulting AppBundle + * to a well-known path. + */ +export function genHookScript(bundle: AppBundle, hook: Hook): string { + let hookSrc = hook.toString().trimLeft(); + // Hook must be IIFE-able. All hook functions are IFFE-able without modification + // except for function defined inside an object in the following form: + // + // { + // afterInstall(b) { + // ... + // . } + // } + // + // We detect and transform function defined in this form by prefixing "functions " + if (!hookSrc.startsWith("(") && !hookSrc.startsWith("function ")) { + hookSrc = `function ${hookSrc}`; + } + return ` +const fs = require("node:fs"); +const path = require("node:path"); + +const bundleDir = path.dirname("${BUNDLE_PATH}"); +if (!fs.existsSync(bundleDir)) { + fs.mkdirSync(bundleDir, { recursive: true }); +} +const bundle = (${hookSrc})(${JSON.stringify(bundle)}); +fs.writeFileSync("${BUNDLE_PATH}", JSON.stringify(bundle)); +`; +} diff --git a/src/frameworks/compose/driver/index.ts b/src/frameworks/compose/driver/index.ts new file mode 100644 index 00000000000..8adc6480708 --- /dev/null +++ b/src/frameworks/compose/driver/index.ts @@ -0,0 +1,19 @@ +import { AppSpec, Driver } from "../interfaces"; +import { LocalDriver } from "./local"; +import { DockerDriver } from "./docker"; + +export const SUPPORTED_MODES = ["local", "docker"] as const; +export type Mode = (typeof SUPPORTED_MODES)[number]; + +/** + * Returns the driver that provides the execution context for the composer. + */ +export function getDriver(mode: Mode, app: AppSpec): Driver { + if (mode === "local") { + return new LocalDriver(app); + } else if (mode === "docker") { + return new DockerDriver(app); + } + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new Error(`Unsupported mode ${mode}`); +} diff --git a/src/frameworks/compose/driver/local.ts b/src/frameworks/compose/driver/local.ts new file mode 100644 index 00000000000..da23187bf58 --- /dev/null +++ b/src/frameworks/compose/driver/local.ts @@ -0,0 +1,46 @@ +import * as fs from "node:fs"; +import * as spawn from "cross-spawn"; + +import { AppBundle, AppSpec, Hook, Driver } from "../interfaces"; +import { BUNDLE_PATH, genHookScript } from "./hooks"; + +export class LocalDriver implements Driver { + constructor(readonly spec: AppSpec) {} + + private execCmd(cmd: string, args: string[]) { + const ret = spawn.sync(cmd, args, { + env: { ...process.env, ...this.spec.environmentVariables }, + stdio: [/* stdin= */ "pipe", /* stdout= */ "inherit", /* stderr= */ "inherit"], + }); + if (ret.error) { + throw ret.error; + } + } + + install(): void { + const [cmd, ...args] = this.spec.installCommand.split(" "); + this.execCmd(cmd, args); + } + + build(): void { + const [cmd, ...args] = this.spec.buildCommand.split(" "); + this.execCmd(cmd, args); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + export(bundle: AppBundle): void { + // no-op + } + + execHook(bundle: AppBundle, hook: Hook): AppBundle { + const script = genHookScript(bundle, hook); + this.execCmd("node", ["-e", script]); + if (!fs.existsSync(BUNDLE_PATH)) { + console.warn(`Expected hook to generate app bundle at ${BUNDLE_PATH} but got nothing.`); + console.warn("Returning original bundle."); + return bundle; + } + const newBundle = JSON.parse(fs.readFileSync(BUNDLE_PATH, "utf8")); + return newBundle as AppBundle; + } +} diff --git a/src/frameworks/compose/index.ts b/src/frameworks/compose/index.ts new file mode 100644 index 00000000000..24e45490be1 --- /dev/null +++ b/src/frameworks/compose/index.ts @@ -0,0 +1,38 @@ +import { AppBundle } from "./interfaces"; +import { getDriver, Mode } from "./driver"; +import { discover } from "./discover"; + +/** + * Run composer in the specified execution context. + */ +export function compose(mode: Mode): AppBundle { + let bundle: AppBundle = { version: "v1alpha" }; + const spec = discover(); + const driver = getDriver(mode, spec); + + if (spec.startCommand) { + bundle.server = { + start: { + cmd: spec.startCommand.split(" "), + }, + }; + } + + driver.install(); + if (spec.afterInstall) { + bundle = driver.execHook(bundle, spec.afterInstall); + } + + driver.build(); + if (spec.afterBuild) { + bundle = driver.execHook(bundle, spec.afterBuild); + } + + if (bundle.server) { + // Export container + driver.export(bundle); + } + + // TODO: Update stack config + return bundle; +} diff --git a/src/frameworks/compose/interfaces.ts b/src/frameworks/compose/interfaces.ts new file mode 100644 index 00000000000..db3caee4898 --- /dev/null +++ b/src/frameworks/compose/interfaces.ts @@ -0,0 +1,59 @@ +export interface AppBundle { + version: "v1alpha"; + server?: ServerConfig; +} + +interface ServerConfig { + start: StartConfig; + concurrency?: number; + cpu?: number; + memory?: "256MiB" | "512MiB" | "1GiB" | "2GiB" | "4GiB" | "8GiB" | "16GiB" | string; + timeoutSeconds?: number; + minInstances?: number; + maxInstances?: number; +} + +interface StartConfig { + // Path to local source directory. Defaults to .bundle/server. + dir?: string; + // Command to start the server (e.g. ["npm", "run", "start"]). + cmd: string[]; + // Runtime required to command execution. + runtime?: "nodejs18" | string; +} + +export interface AppSpec { + baseImage: string; + packageManagerInstallCommand?: string; + environmentVariables?: Record; + installCommand: string; + buildCommand: string; + startCommand: string; + + afterInstall?: (b: AppBundle) => AppBundle; + afterBuild?: (b: AppBundle) => AppBundle; +} + +export type Hook = (b: AppBundle) => AppBundle; + +export class Driver { + constructor(readonly spec: AppSpec) {} + + install(): void { + throw new Error("install() not implemented"); + } + + build(): void { + throw new Error("build() not implemented"); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + export(bundle: AppBundle): void { + throw new Error("export() not implemented"); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + execHook(bundle: AppBundle, hook: Hook): AppBundle { + throw new Error("execHook() not implemented"); + } +} diff --git a/src/test/frameworks/compose/engine/docker.spec.ts b/src/test/frameworks/compose/engine/docker.spec.ts new file mode 100644 index 00000000000..f5591bd805d --- /dev/null +++ b/src/test/frameworks/compose/engine/docker.spec.ts @@ -0,0 +1,92 @@ +import { expect } from "chai"; +import { DockerfileBuilder } from "../../../../frameworks/compose/driver/docker"; + +describe("DockerfileBuilder", () => { + describe("from", () => { + it("should add a FROM instruction to the Dockerfile", () => { + const builder = new DockerfileBuilder(); + builder.from("node:18", "base"); + expect(builder.toString()).to.equal("FROM node:18 AS base\n"); + }); + + it("should add a FROM instruction to the Dockerfile without a name", () => { + const builder = new DockerfileBuilder(); + builder.from("node:18"); + expect(builder.toString()).to.equal("FROM node:18\n"); + }); + }); + + describe("fromLastStage", () => { + it("should add a FROM instruction to the Dockerfile using the last stage name", () => { + const builder = new DockerfileBuilder(); + builder.from("node:18", "base").fromLastStage("test"); + expect(builder.toString()).to.equal("FROM node:18 AS base\nFROM base AS test\n"); + }); + }); + + describe("tempFrom", () => { + it("should add a FROM instruction without updating last stage", () => { + const builder = new DockerfileBuilder(); + builder.from("node:18", "base").tempFrom("node:20", "temp").fromLastStage("test"); + expect(builder.toString()).to.equal( + "FROM node:18 AS base\nFROM node:20 AS temp\nFROM base AS test\n" + ); + }); + }); + + describe("workdir", () => { + it("should add a WORKDIR instruction to the Dockerfile", () => { + const builder = new DockerfileBuilder(); + builder.workdir("/app"); + expect(builder.toString()).to.equal("WORKDIR /app\n"); + }); + }); + + describe("run", () => { + it("should add a RUN instruction to the Dockerfile", () => { + const builder = new DockerfileBuilder(); + builder.run('echo "test"'); + expect(builder.toString()).to.equal('RUN echo "test"\n'); + }); + }); + + describe("cmd", () => { + it("should add a CMD instruction to the Dockerfile", () => { + const builder = new DockerfileBuilder(); + builder.cmd(["node", "index.js"]); + expect(builder.toString()).to.equal('CMD ["node", "index.js"]\n'); + }); + }); + + describe("copyForFirebase", () => { + it("should add a COPY instruction to the Dockerfile", () => { + const builder = new DockerfileBuilder(); + builder.copyForFirebase("src", "dest"); + expect(builder.toString()).to.equal("COPY --chown=firebase:firebase src dest\n"); + }); + }); + + describe("copyFrom", () => { + it("should add a COPY instruction to the Dockerfile", () => { + const builder = new DockerfileBuilder(); + builder.copyFrom("src", "dest", "stage"); + expect(builder.toString()).to.equal("COPY --from=stage src dest\n"); + }); + }); + + describe("env", () => { + it("should add an ENV instruction to the Dockerfile", () => { + const builder = new DockerfileBuilder(); + builder.env("NODE_ENV", "production"); + expect(builder.toString()).to.equal('ENV NODE_ENV="production"\n'); + }); + }); + + describe("envs", () => { + it("should add multiple ENV instructions to the Dockerfile", () => { + const builder = new DockerfileBuilder(); + builder.envs({ NODE_ENV: "production", PORT: "8080" }); + expect(builder.toString()).to.equal('ENV NODE_ENV="production"\nENV PORT="8080"\n'); + }); + }); +}); diff --git a/src/test/frameworks/compose/engine/hooks.spec.ts b/src/test/frameworks/compose/engine/hooks.spec.ts new file mode 100644 index 00000000000..c759972293b --- /dev/null +++ b/src/test/frameworks/compose/engine/hooks.spec.ts @@ -0,0 +1,42 @@ +import { expect } from "chai"; +import { genHookScript } from "../../../../frameworks/compose/driver/hooks"; +import { AppBundle } from "../../../../frameworks/compose/interfaces"; + +describe("genHookScript", () => { + const BUNDLE: AppBundle = { + version: "v1alpha", + }; + + it("generates executable script from anonymous functions", () => { + const hookFn = (b: AppBundle): AppBundle => { + return b; + }; + const expectedSnippet = `const bundle = ((b) => { + return b; + })({"version":"v1alpha"});`; + expect(genHookScript(BUNDLE, hookFn)).to.include(expectedSnippet); + }); + + it("generates executable script from a named function", () => { + function hookFn(b: AppBundle): AppBundle { + return b; + } + const expectedSnippet = `const bundle = (function hookFn(b) { + return b; + })({"version":"v1alpha"});`; + expect(genHookScript(BUNDLE, hookFn)).to.include(expectedSnippet); + }); + + it("generates executable script from an object method", () => { + const a = { + hookFn(b: AppBundle) { + return b; + }, + }; + const expectedSnippet = `const bundle = (function hookFn(b) { + return b; + })({"version":"v1alpha"});`; + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(genHookScript(BUNDLE, a.hookFn)).to.include(expectedSnippet); + }); +}); From 6d9047fd4ee910f1370467bd11eb2fdc76a5b01c Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Tue, 6 Jun 2023 13:48:12 -0400 Subject: [PATCH 1013/1699] Delete and re-create v2 function on Cloud Run API quota exhaustion (#5719) * delete function on cloud run quota exhaustion and re-create * add changelog * change retry code name * custom specified retry codes override defaults * allow to disable retries in executor --- CHANGELOG.md | 2 + src/deploy/functions/release/executor.ts | 38 ++++++--- src/deploy/functions/release/fabricator.ts | 77 ++++++++++++------- .../deploy/functions/release/executor.spec.ts | 11 +++ .../functions/release/fabricator.spec.ts | 14 ++++ 5 files changed, 105 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b09f81abf14..318f8cf8184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ +- Delete and re-create v2 function on Cloud Run API quota exhaustion (#5719). +- Address additional cases where we were attempting to deploy a framework's development bundle (#5895) - firebase functions:secrets:\* ensure the secretmanager API is enabled (#5918) diff --git a/src/deploy/functions/release/executor.ts b/src/deploy/functions/release/executor.ts index 271bba5f869..471004fa40d 100644 --- a/src/deploy/functions/release/executor.ts +++ b/src/deploy/functions/release/executor.ts @@ -5,15 +5,32 @@ import { ThrottlerOptions } from "../../../throttler/throttler"; * An Executor runs lambdas (which may be async). */ export interface Executor { - run(func: () => Promise): Promise; + run(func: () => Promise, opts?: RunOptions): Promise; +} + +export interface RunOptions { + retryCodes?: number[]; } interface Operation { func: () => any; + retryCodes: number[]; result?: any; error?: any; } +export const DEFAULT_RETRY_CODES = [429, 409, 503]; + +function parseErrorCode(err: any): number { + return ( + err.status || + err.code || + err.context?.response?.statusCode || + err.original?.code || + err.original?.context?.response?.statusCode + ); +} + async function handler(op: Operation): Promise { try { op.result = await op.func(); @@ -24,15 +41,11 @@ async function handler(op: Operation): Promise { // errors. This can be a raw error with the correct HTTP code, a raw // error with the HTTP code stashed where GCP puts it, or a FirebaseError // wrapping either of the previous two cases. - const code = - err.status || - err.code || - err.context?.response?.statusCode || - err.original?.code || - err.original?.context?.response?.statusCode; - if (code === 429 || code === 409 || code === 503) { + const code = parseErrorCode(err); + if (op.retryCodes.includes(code)) { throw err; } + err.code = code; op.error = err; } return; @@ -49,8 +62,13 @@ export class QueueExecutor implements Executor { this.queue = new Queue({ ...options, handler }); } - async run(func: () => Promise): Promise { - const op: Operation = { func }; + async run(func: () => Promise, opts?: RunOptions): Promise { + const retryCodes = opts?.retryCodes || DEFAULT_RETRY_CODES; + + const op: Operation = { + func, + retryCodes, + }; await this.queue.run(op); if (op.error) { throw op.error; diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 1ca388f9f43..7f282fa9724 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -1,6 +1,6 @@ import * as clc from "colorette"; -import { Executor } from "./executor"; +import { DEFAULT_RETRY_CODES, Executor } from "./executor"; import { FirebaseError } from "../../../error"; import { SourceTokenScraper } from "./sourceTokenScraper"; import { Timer } from "./timer"; @@ -50,6 +50,8 @@ const eventarcPollerOptions: Omit { - const op: { name: string } = await gcfV2.createFunction(apiFunction); - return await poller.pollOperation({ - ...gcfV2PollerOptions, - pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, - operationResourceName: op.name, + let resultFunction: gcfV2.OutputCloudFunction | null = null; + while (!resultFunction) { + resultFunction = await this.functionExecutor + .run(async () => { + const op: { name: string } = await gcfV2.createFunction(apiFunction); + return await poller.pollOperation({ + ...gcfV2PollerOptions, + pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, + operationResourceName: op.name, + }); + }) + .catch(async (err: any) => { + // If the createFunction call returns RPC error code RESOURCE_EXHAUSTED (8), + // we have exhausted the underlying Cloud Run API quota. To retry, we need to + // first delete the GCF function resource, then call createFunction again. + if (err.code === CLOUD_RUN_RESOURCE_EXHAUSTED_CODE) { + // we have to delete the broken function before we can re-create it + await this.deleteV2Function(endpoint); + return null; + } else { + logger.error((err as Error).message); + throw new reporter.DeploymentError(endpoint, "create", err); + } }); - }) - .catch(rethrowAs(endpoint, "create")); + } endpoint.uri = resultFunction.serviceConfig?.uri; const serviceName = resultFunction.serviceConfig?.service; @@ -463,14 +480,17 @@ export class Fabricator { } const resultFunction = await this.functionExecutor - .run(async () => { - const op: { name: string } = await gcfV2.updateFunction(apiFunction); - return await poller.pollOperation({ - ...gcfV2PollerOptions, - pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, - operationResourceName: op.name, - }); - }) + .run( + async () => { + const op: { name: string } = await gcfV2.updateFunction(apiFunction); + return await poller.pollOperation({ + ...gcfV2PollerOptions, + pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, + operationResourceName: op.name, + }); + }, + { retryCodes: [...DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] } + ) .catch(rethrowAs(endpoint, "update")); endpoint.uri = resultFunction.serviceConfig?.uri; @@ -523,15 +543,18 @@ export class Fabricator { async deleteV2Function(endpoint: backend.Endpoint): Promise { const fnName = backend.functionName(endpoint); await this.functionExecutor - .run(async () => { - const op: { name: string } = await gcfV2.deleteFunction(fnName); - const pollerOptions = { - ...gcfV2PollerOptions, - pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, - operationResourceName: op.name, - }; - await poller.pollOperation(pollerOptions); - }) + .run( + async () => { + const op: { name: string } = await gcfV2.deleteFunction(fnName); + const pollerOptions = { + ...gcfV2PollerOptions, + pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, + operationResourceName: op.name, + }; + await poller.pollOperation(pollerOptions); + }, + { retryCodes: [...DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] } + ) .catch(rethrowAs(endpoint, "delete")); } diff --git a/src/test/deploy/functions/release/executor.spec.ts b/src/test/deploy/functions/release/executor.spec.ts index f819b3b3d59..547eda2e4a9 100644 --- a/src/test/deploy/functions/release/executor.spec.ts +++ b/src/test/deploy/functions/release/executor.spec.ts @@ -45,5 +45,16 @@ describe("Executor", () => { }; await expect(exec.run(handler)).to.eventually.be.rejectedWith("Retryable"); }); + + it("retries on custom specified retry codes", async () => { + const handler = (): Promise => { + const err = new Error("Retryable"); + (err as any).code = 8; + throw err; + }; + await expect( + exec.run(handler, { retryCodes: [...executor.DEFAULT_RETRY_CODES, 8] }) + ).to.eventually.be.rejectedWith("Retryable"); + }); }); }); diff --git a/src/test/deploy/functions/release/fabricator.spec.ts b/src/test/deploy/functions/release/fabricator.spec.ts index f5a26a0633e..99b8e5e0517 100644 --- a/src/test/deploy/functions/release/fabricator.spec.ts +++ b/src/test/deploy/functions/release/fabricator.spec.ts @@ -612,6 +612,20 @@ describe("Fabricator", () => { await expect(fab.createV2Function(ep)).to.be.rejectedWith(reporter.DeploymentError, "create"); }); + it("deletes broken function and retries on cloud run quota exhaustion", async () => { + gcfv2.createFunction.onFirstCall().rejects({ message: "Cloud Run quota exhausted", code: 8 }); + gcfv2.createFunction.resolves({ name: "op", done: false }); + + gcfv2.deleteFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ name: "op" }); + + const ep = endpoint({ httpsTrigger: {} }, { platform: "gcfv2" }); + await fab.createV2Function(ep); + + expect(gcfv2.createFunction).to.have.been.calledTwice; + expect(gcfv2.deleteFunction).to.have.been.called; + }); + it("throws on set invoker failure", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); From 78fb757a939c7e7a4de1eb88740b39d19a2a5e1a Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 6 Jun 2023 14:06:38 -0700 Subject: [PATCH 1014/1699] Fix merge conflict (#5939) --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 318f8cf8184..4a19e4466dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,2 @@ - Delete and re-create v2 function on Cloud Run API quota exhaustion (#5719). -- Address additional cases where we were attempting to deploy a framework's development bundle (#5895) - firebase functions:secrets:\* ensure the secretmanager API is enabled (#5918) From 06d5f78750ad6959773772c3ddaa9a45d3221d83 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 6 Jun 2023 21:18:32 +0000 Subject: [PATCH 1015/1699] 12.3.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index fb524af2aa3..44dd1fcdf1a 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "12.3.0", + "version": "12.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "12.3.0", + "version": "12.3.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 510bff324d0..11f013e1d22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "12.3.0", + "version": "12.3.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From dc08ce421417fdec7a3ce01099967fe7d1dd62ff Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 6 Jun 2023 21:18:48 +0000 Subject: [PATCH 1016/1699] [firebase-release] Removed change log and reset repo after 12.3.1 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a19e4466dd..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Delete and re-create v2 function on Cloud Run API quota exhaustion (#5719). -- firebase functions:secrets:\* ensure the secretmanager API is enabled (#5918) From ab0455404b73de5183e3478c4838c4100adbb763 Mon Sep 17 00:00:00 2001 From: aalej Date: Thu, 8 Jun 2023 02:12:56 +0800 Subject: [PATCH 1017/1699] Update experiments:describe description (#5868) * Update experiments:describe description * Update src/commands/experiments-describe.ts --------- Co-authored-by: joehan --- src/commands/experiments-describe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/experiments-describe.ts b/src/commands/experiments-describe.ts index 079bc08ed4a..f46b7195688 100644 --- a/src/commands/experiments-describe.ts +++ b/src/commands/experiments-describe.ts @@ -7,7 +7,7 @@ import { logger } from "../logger"; import { last } from "../utils"; export const command = new Command("experiments:describe ") - .description("enable an experiment on this machine") + .description("describe what an experiment does when enabled") .action((experiment: string) => { if (!experiments.isValidExperiment(experiment)) { let message = `Cannot find experiment ${bold(experiment)}`; From 628aa7525e025f7872f57cc1a823fe7c9e9bab57 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 9 Jun 2023 12:34:59 -0700 Subject: [PATCH 1018/1699] VSCode Plugin initial implementation (#5930) --- .eslintrc.js | 2 + firebase-vscode/.eslintrc.json | 24 + firebase-vscode/.gitignore | 4 + firebase-vscode/.prettierrc.js | 3 + firebase-vscode/.vscode/extensions.json | 7 + firebase-vscode/.vscode/launch.json | 34 + firebase-vscode/.vscode/settings.json | 11 + firebase-vscode/.vscode/tasks.json | 20 + firebase-vscode/.vscodeignore | 18 + firebase-vscode/CHANGELOG.md | 9 + firebase-vscode/LICENSE | 21 + firebase-vscode/README.md | 64 + firebase-vscode/common/firebaserc.ts | 2 + firebase-vscode/common/messaging/broker.ts | 87 + firebase-vscode/common/messaging/protocol.ts | 122 + firebase-vscode/common/messaging/types.d.ts | 17 + firebase-vscode/common/types.d.ts | 8 + firebase-vscode/package-lock.json | 9769 +++++++++++++++++ firebase-vscode/package.json | 115 + firebase-vscode/resources/Monicons.woff | Bin 0 -> 1932 bytes firebase-vscode/scripts/swap-pkg.js | 29 + firebase-vscode/src/cli.ts | 214 + firebase-vscode/src/extension-broker.ts | 41 + firebase-vscode/src/extension.ts | 30 + firebase-vscode/src/html-scaffold.ts | 68 + firebase-vscode/src/logger-wrapper.ts | 15 + firebase-vscode/src/options.ts | 99 + firebase-vscode/src/sidebar.ts | 65 + firebase-vscode/src/stubs/empty-class.js | 3 + firebase-vscode/src/stubs/empty-function.js | 3 + firebase-vscode/src/stubs/inquirer-stub.js | 31 + firebase-vscode/src/stubs/marked.js | 5 + firebase-vscode/src/test/runTest.ts | 23 + .../src/test/suite/extension.test.ts | 15 + firebase-vscode/src/test/suite/index.ts | 38 + firebase-vscode/src/utils.ts | 11 + firebase-vscode/src/workflow.ts | 449 + firebase-vscode/tsconfig.json | 19 + firebase-vscode/webpack.common.js | 199 + firebase-vscode/webpack.dev.js | 6 + firebase-vscode/webpack.prod.js | 7 + firebase-vscode/webviews/SidebarApp.tsx | 161 + .../webviews/components/AccountSection.scss | 20 + .../webviews/components/AccountSection.tsx | 131 + .../webviews/components/DeployPanel.tsx | 186 + .../webviews/components/ProjectSection.tsx | 79 + .../webviews/components/ui/ExternalLink.tsx | 11 + .../webviews/components/ui/Icon.scss | 21 + .../webviews/components/ui/Icon.tsx | 441 + .../webviews/components/ui/IconButton.tsx | 30 + .../webviews/components/ui/PanelSection.scss | 20 + .../webviews/components/ui/PanelSection.tsx | 42 + .../webviews/components/ui/Spacer.scss | 23 + .../webviews/components/ui/Spacer.tsx | 14 + .../webviews/components/ui/Text.scss | 94 + .../webviews/components/ui/Text.tsx | 58 + .../components/ui/popup-menu/PopupMenu.scss | 57 + .../components/ui/popup-menu/PopupMenu.tsx | 57 + .../webviews/globals/html-broker.ts | 22 + firebase-vscode/webviews/globals/index.scss | 12 + firebase-vscode/webviews/globals/tokens.scss | 8 + firebase-vscode/webviews/globals/vscode.scss | 38 + .../webviews/globals/web-logger.ts | 18 + firebase-vscode/webviews/sidebar.entry.scss | 54 + firebase-vscode/webviews/sidebar.entry.tsx | 6 + firebase-vscode/webviews/tsconfig.json | 14 + firebase-vscode/webviews/webview-types.ts | 2 + src/monospace/index.ts | 155 + src/monospace/interfaces.ts | 33 + src/options.ts | 3 + src/requireAuth.ts | 13 +- 71 files changed, 13529 insertions(+), 1 deletion(-) create mode 100644 firebase-vscode/.eslintrc.json create mode 100644 firebase-vscode/.gitignore create mode 100644 firebase-vscode/.prettierrc.js create mode 100644 firebase-vscode/.vscode/extensions.json create mode 100644 firebase-vscode/.vscode/launch.json create mode 100644 firebase-vscode/.vscode/settings.json create mode 100644 firebase-vscode/.vscode/tasks.json create mode 100644 firebase-vscode/.vscodeignore create mode 100644 firebase-vscode/CHANGELOG.md create mode 100644 firebase-vscode/LICENSE create mode 100644 firebase-vscode/README.md create mode 100644 firebase-vscode/common/firebaserc.ts create mode 100644 firebase-vscode/common/messaging/broker.ts create mode 100644 firebase-vscode/common/messaging/protocol.ts create mode 100644 firebase-vscode/common/messaging/types.d.ts create mode 100644 firebase-vscode/common/types.d.ts create mode 100644 firebase-vscode/package-lock.json create mode 100644 firebase-vscode/package.json create mode 100644 firebase-vscode/resources/Monicons.woff create mode 100644 firebase-vscode/scripts/swap-pkg.js create mode 100644 firebase-vscode/src/cli.ts create mode 100644 firebase-vscode/src/extension-broker.ts create mode 100644 firebase-vscode/src/extension.ts create mode 100644 firebase-vscode/src/html-scaffold.ts create mode 100644 firebase-vscode/src/logger-wrapper.ts create mode 100644 firebase-vscode/src/options.ts create mode 100644 firebase-vscode/src/sidebar.ts create mode 100644 firebase-vscode/src/stubs/empty-class.js create mode 100644 firebase-vscode/src/stubs/empty-function.js create mode 100644 firebase-vscode/src/stubs/inquirer-stub.js create mode 100644 firebase-vscode/src/stubs/marked.js create mode 100644 firebase-vscode/src/test/runTest.ts create mode 100644 firebase-vscode/src/test/suite/extension.test.ts create mode 100644 firebase-vscode/src/test/suite/index.ts create mode 100644 firebase-vscode/src/utils.ts create mode 100644 firebase-vscode/src/workflow.ts create mode 100644 firebase-vscode/tsconfig.json create mode 100644 firebase-vscode/webpack.common.js create mode 100644 firebase-vscode/webpack.dev.js create mode 100644 firebase-vscode/webpack.prod.js create mode 100644 firebase-vscode/webviews/SidebarApp.tsx create mode 100644 firebase-vscode/webviews/components/AccountSection.scss create mode 100644 firebase-vscode/webviews/components/AccountSection.tsx create mode 100644 firebase-vscode/webviews/components/DeployPanel.tsx create mode 100644 firebase-vscode/webviews/components/ProjectSection.tsx create mode 100644 firebase-vscode/webviews/components/ui/ExternalLink.tsx create mode 100644 firebase-vscode/webviews/components/ui/Icon.scss create mode 100644 firebase-vscode/webviews/components/ui/Icon.tsx create mode 100644 firebase-vscode/webviews/components/ui/IconButton.tsx create mode 100644 firebase-vscode/webviews/components/ui/PanelSection.scss create mode 100644 firebase-vscode/webviews/components/ui/PanelSection.tsx create mode 100644 firebase-vscode/webviews/components/ui/Spacer.scss create mode 100644 firebase-vscode/webviews/components/ui/Spacer.tsx create mode 100644 firebase-vscode/webviews/components/ui/Text.scss create mode 100644 firebase-vscode/webviews/components/ui/Text.tsx create mode 100644 firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.scss create mode 100644 firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.tsx create mode 100644 firebase-vscode/webviews/globals/html-broker.ts create mode 100644 firebase-vscode/webviews/globals/index.scss create mode 100644 firebase-vscode/webviews/globals/tokens.scss create mode 100644 firebase-vscode/webviews/globals/vscode.scss create mode 100644 firebase-vscode/webviews/globals/web-logger.ts create mode 100644 firebase-vscode/webviews/sidebar.entry.scss create mode 100644 firebase-vscode/webviews/sidebar.entry.tsx create mode 100644 firebase-vscode/webviews/tsconfig.json create mode 100644 firebase-vscode/webviews/webview-types.ts create mode 100644 src/monospace/index.ts create mode 100644 src/monospace/interfaces.ts diff --git a/.eslintrc.js b/.eslintrc.js index 841c3a7a480..129a36e41bf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -128,5 +128,7 @@ module.exports = { "/src/frameworks/docs/**", // This file is taking a very long time to lint, 2-4m "src/emulator/auth/schema.ts", + // TODO(hsubox76): Set up a job to run eslint separately on vscode dir + "firebase-vscode/", ], }; diff --git a/firebase-vscode/.eslintrc.json b/firebase-vscode/.eslintrc.json new file mode 100644 index 00000000000..681109e79a6 --- /dev/null +++ b/firebase-vscode/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + // "react" + ], + "rules": { + "@typescript-eslint/semi": "warn", + "curly": "warn", + "eqeqeq": "warn", + "no-throw-literal": "warn", + "semi": "off" + }, + "ignorePatterns": [ + "out", + "dist", + "**/*.d.ts" + ] +} diff --git a/firebase-vscode/.gitignore b/firebase-vscode/.gitignore new file mode 100644 index 00000000000..08ccd9d7281 --- /dev/null +++ b/firebase-vscode/.gitignore @@ -0,0 +1,4 @@ +*.vsix +dist/ +*.scss.d.ts +resources/dist diff --git a/firebase-vscode/.prettierrc.js b/firebase-vscode/.prettierrc.js new file mode 100644 index 00000000000..e83d7e4ded7 --- /dev/null +++ b/firebase-vscode/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + printWidth: 80, +}; diff --git a/firebase-vscode/.vscode/extensions.json b/firebase-vscode/.vscode/extensions.json new file mode 100644 index 00000000000..d26385a6325 --- /dev/null +++ b/firebase-vscode/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "dbaeumer.vscode-eslint" + ] +} diff --git a/firebase-vscode/.vscode/launch.json b/firebase-vscode/.vscode/launch.json new file mode 100644 index 00000000000..9ade0d5097d --- /dev/null +++ b/firebase-vscode/.vscode/launch.json @@ -0,0 +1,34 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/dist/test/suite/index" + ], + "outFiles": [ + "${workspaceFolder}/dist/test/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + } + ] +} diff --git a/firebase-vscode/.vscode/settings.json b/firebase-vscode/.vscode/settings.json new file mode 100644 index 00000000000..10d3a97a91d --- /dev/null +++ b/firebase-vscode/.vscode/settings.json @@ -0,0 +1,11 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "dist": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "dist": true // set this to false to include "out" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" +} diff --git a/firebase-vscode/.vscode/tasks.json b/firebase-vscode/.vscode/tasks.json new file mode 100644 index 00000000000..74418433df5 --- /dev/null +++ b/firebase-vscode/.vscode/tasks.json @@ -0,0 +1,20 @@ +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": ["$tsc-watch", "$ts-webpack-watch"], + "isBackground": true, + "presentation": { + "reveal": "always" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/firebase-vscode/.vscodeignore b/firebase-vscode/.vscodeignore new file mode 100644 index 00000000000..091caca74cd --- /dev/null +++ b/firebase-vscode/.vscodeignore @@ -0,0 +1,18 @@ +.vscode/** +.vscode-test/** +src/** +webviews/** +common/** +extension/** +public/** +.gitignore +.yarnrc +vsc-extension-quickstart.md +**/tsconfig.json +**/.eslintrc.json +**/*.map +**/*.ts +*.vsix +webpack.*.js +../ +*.zip diff --git a/firebase-vscode/CHANGELOG.md b/firebase-vscode/CHANGELOG.md new file mode 100644 index 00000000000..f73373662c2 --- /dev/null +++ b/firebase-vscode/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log + +All notable changes to the "firebase-vscode" extension will be documented in this file. + +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. + +## [Unreleased] + +- Initial release diff --git a/firebase-vscode/LICENSE b/firebase-vscode/LICENSE new file mode 100644 index 00000000000..3da21db2851 --- /dev/null +++ b/firebase-vscode/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 Firebase + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/firebase-vscode/README.md b/firebase-vscode/README.md new file mode 100644 index 00000000000..086212a4347 --- /dev/null +++ b/firebase-vscode/README.md @@ -0,0 +1,64 @@ +# firebase-vscode README + +This extension is in the development and exploration stage. + +## Running + +1. In order to make sure f5 launches the extension properly, first open your + VS Code session from the `firebase-vscode` subdirectory (not the `firebase-tools` directory). +2. npm i +3. f5 to run opens new window + f5 -> npm run watch defined in tasks.json + My terminal didn't have npm available but yours might + +Workaround if f5 doesnt work: + +1. Execute `npm run watch` from within the vscode directory + Aside: Running `npm run watch` or `npm run build` the extension is compiled into dist (extension.js) + Changing code within extension is hot-reloaded + Modifying extensions.js will not hot-reload + source file src/extension.ts +2. Wait for completion +3. Hit play from the left nav + +New code changes are automatically rebuilt if you have `watch` running, however the new VSCode Plugin-enabled window will not reflect changes until reloaded. +Manual reload from new window: "Developer: Reload Window" Default hotkey: cmd + R + +The communication between UI and extension done via the broker (see webview.postMessage) +Web view uses react (carry-over from the hackweek project courtesy of Roman and Prakhar) + +## Structure + +Extention.ts main entry point, calls sidebar.ts and workflow.ts +sidebar.ts loads the UI from the webviews folder +workflow.ts is the driving component (logic source) +cli.ts wraps CLI methods, importing from firebase-tools/src + +When workflow.ts needs to execute some CLI command, it defers to cli.ts + +## State + +currentOptions maintains the currentState of the plugin and is passed as a whole object to populate calls to the firebase-tools methods +`prepare` in the command includes a lot of + +## Logic + +Calling firebase-tools in general follows the stuff: + +1. instead of calling `before`, call `requireAuth` instead + requireAuth is a prerequisite for the plugin UI, needed + Zero-state (before login) directs the user to sign in with google (using firebase-tools CLI) +2. prepare is an implicit command in the cmd class +3. action + +requireAuth -> login with service account or check that you're already logged in via firebase-tools + +## Open issues + +Login changes in the CLI are not immediately reflected in the Plugin, requires restart +If logged-out in the middle of a plugin session, handle requireAuth errors gracefully +Plugin startup is flaky sometimes +Unit/Integration tests are not developed +Code cleanliness/structure TODOs +tsconfig.json's rootDirs includes ["src", "../src", "common"] which causes some issues with import autocomplete +Three package.jsons - one for monospace and one for the standalone plugin, and then root to copy the correct version diff --git a/firebase-vscode/common/firebaserc.ts b/firebase-vscode/common/firebaserc.ts new file mode 100644 index 00000000000..441be9895fa --- /dev/null +++ b/firebase-vscode/common/firebaserc.ts @@ -0,0 +1,2 @@ +import { RCData } from '../../src/rc'; +export interface FirebaseRC extends Partial {} diff --git a/firebase-vscode/common/messaging/broker.ts b/firebase-vscode/common/messaging/broker.ts new file mode 100644 index 00000000000..2d3c3edfc77 --- /dev/null +++ b/firebase-vscode/common/messaging/broker.ts @@ -0,0 +1,87 @@ +import { MessageParamsMap } from "./protocol"; +import { Listener, Message, MessageListeners } from "./types"; +import { Webview } from "vscode"; + +const isObject = (val: any): boolean => typeof val === "object" && val !== null; + +type Receiver = {} | Webview; + +export abstract class Broker< + OutgoingMessages extends MessageParamsMap, + IncomingMessages extends MessageParamsMap, + R extends Receiver +> { + protected readonly listeners: MessageListeners = {}; + + abstract sendMessage(message: T, data: OutgoingMessages[T]): void; + registerReceiver(receiver: R): void { } + + addListener(message: string, cb: Listener): void { + if (!this.listeners[message]) { + this.listeners[message] = { listeners: [] }; + } + this.listeners[message].listeners.push(cb); + } + + executeListeners(message: Message) { + if (message === undefined || !isObject(message) || !message.command) { + return; + } + + const d = message; + + if (this.listeners[d.command] === undefined) { + return; + } + + for (const listener of this.listeners[d.command].listeners) { + d.data === undefined ? listener() : listener(d.data); + }; + } + + delete(): void { } +} + +export interface BrokerImpl< + OutgoingMessages, + IncomingMessages, + R extends Receiver +> { + send( + message: E, + args?: OutgoingMessages[E] + ): void; + registerReceiver(receiver: R): void; + on( + message: Extract, + listener: (params: IncomingMessages[E]) => void + ): void; + delete(): void; +} + +export function createBroker< + OutgoingMessages extends MessageParamsMap, + IncomingMessages extends MessageParamsMap, + R extends Receiver +>(broker: Broker): BrokerImpl { + return { + send( + message: Extract, + args?: OutgoingMessages[E] + ): void { + broker.sendMessage(message, args); + }, + registerReceiver(receiver: R): void { + broker.registerReceiver(receiver); + }, + on( + message: Extract, + listener: (params: IncomingMessages[E]) => void + ): void { + broker.addListener(message, listener); + }, + delete(): void { + broker.delete(); + } + }; +} diff --git a/firebase-vscode/common/messaging/protocol.ts b/firebase-vscode/common/messaging/protocol.ts new file mode 100644 index 00000000000..087607fdfc4 --- /dev/null +++ b/firebase-vscode/common/messaging/protocol.ts @@ -0,0 +1,122 @@ +/** + * @fileoverview Lists all possible messages that can be passed back and forth + * between two environments (VScode and Webview) + */ + +import { FirebaseConfig } from '../../../src/firebaseConfig'; +import { FirebaseRC } from "../firebaserc"; +import { User } from "../../../src/types/auth"; +import { ServiceAccountUser } from "../types"; + +export interface WebviewToExtensionParamsMap { + /** + * Ask extension for env variables + */ + getEnv: {}; + /** + * User management + */ + getUsers: {}; + addUser: {}; + logout: { email: string }; + + /** Notify extension that current user has been changed in UI. */ + requestChangeUser: { user: User | ServiceAccountUser }; + + /** Trigger project selection */ + selectProject: { email: string }; + /** + * Runs `firebase init hosting` command. + * TODO(hsubox76): Generalize to work for all `firebase init` products. + */ + selectAndInitHostingFolder: { + projectId: string, + email: string, + singleAppSupport: boolean + }; + + /** + * Get hosting channels. + */ + getChannels: {}; + + /** + * Runs `firebase deploy` for hosting. + * TODO(hsubox76): Generalize to work for all `firebase deploy` targets. + */ + hostingDeploy: { + target: string + }; + + /** + * Get currently selected Firebase project from extension runtime. + */ + getSelectedProject: {}; + + /** + * Fetches the contents of the .firebaserc and firebase.json config files. + * If either or both files do not exist, then it will return a default + * value. + */ + getFirebaseJson: {}; + + /** + * Show a UI message using the vscode interface + */ + showMessage: { msg: string, options?: {} }; + + /** + * Write a log to the extension logger. + */ + writeLog: { level: string, args: string[] }; + + /** + * Call extension runtime to open a link (a href does not work in Monospace) + */ + openLink: { + href: string + }; +} + +export interface ExtensionToWebviewParamsMap { + /** Triggered when new environment variables values are found. */ + notifyEnv: { env: { isMonospace: boolean } }; + + /** Triggered when users have been updated. */ + notifyUsers: { users: User[] }; + + /** Triggered when hosting channels have been fetched. */ + notifyChannels: { channels: any[] }; + + /** Triggered when a new project is selected */ + notifyProjectChanged: { projectId: string }; + + /** + * This can potentially call multiple webviews to notify of user selection. + */ + notifyUserChanged: { email: string }; + + /** + * Notifies webview when user has successfully selected a hosting folder + * and it has been written to firebase.json. + */ + notifyHostingFolderReady: { projectId: string, folderPath: string }; + + /** + * Notify webview of status of deployment attempt. + */ + notifyHostingDeploy: { + success: boolean, + consoleUrl?: string, + hostingUrl?: string + }; + + /** + * Notify webview of initial discovery or change in firebase.json or + * .firebaserc + */ + notifyFirebaseConfig: { firebaseJson: FirebaseConfig, firebaseRC: FirebaseRC }; + +} + +export type MessageParamsMap = WebviewToExtensionParamsMap | ExtensionToWebviewParamsMap; diff --git a/firebase-vscode/common/messaging/types.d.ts b/firebase-vscode/common/messaging/types.d.ts new file mode 100644 index 00000000000..cbae270f1c0 --- /dev/null +++ b/firebase-vscode/common/messaging/types.d.ts @@ -0,0 +1,17 @@ +import { Channel } from "../hosting/api"; +import { ExtensionToWebviewParamsMap, MessageParamsMap } from "./protocol"; + +export interface Message { + command: string; + data: M[keyof M]; +} + +export type Listener = (args?: M[keyof M]) => void; + +export interface MessageListeners { + [message: string]: { listeners: Listener[] }; +} + +export interface ChannelWithId extends Channel { + id: string; +} diff --git a/firebase-vscode/common/types.d.ts b/firebase-vscode/common/types.d.ts new file mode 100644 index 00000000000..72f8bf49c5f --- /dev/null +++ b/firebase-vscode/common/types.d.ts @@ -0,0 +1,8 @@ +export interface ServiceAccount { + user: ServiceAccountUser +} + +export interface ServiceAccountUser { + email: string; + type: 'service_account' +} diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json new file mode 100644 index 00000000000..8b4d3788633 --- /dev/null +++ b/firebase-vscode/package-lock.json @@ -0,0 +1,9769 @@ +{ + "name": "firebase-vscode", + "version": "0.0.7", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "firebase-vscode", + "version": "0.0.7", + "devDependencies": { + "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", + "@types/glob": "^8.0.0", + "@types/mocha": "^10.0.1", + "@types/node": "16.x", + "@types/react": "^18.0.9", + "@types/react-dom": "^18.0.4", + "@types/vscode": "^1.69.0", + "@typescript-eslint/eslint-plugin": "^5.45.0", + "@typescript-eslint/parser": "^5.45.0", + "@vscode/codicons": "0.0.30", + "@vscode/test-electron": "^2.2.0", + "@vscode/webview-ui-toolkit": "^1.2.1", + "classnames": "^2.3.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.7.1", + "eslint": "^8.28.0", + "eslint-plugin-react": "^7.32.2", + "fork-ts-checker-webpack-plugin": "^7.3.0", + "glob": "^8.0.3", + "mini-css-extract-plugin": "^2.6.0", + "mocha": "^10.1.0", + "postcss-loader": "^7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.52.0", + "sass-loader": "^13.0.0", + "string-replace-loader": "^3.1.0", + "ts-loader": "^9.4.2", + "typescript": "^4.9.3", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1", + "webpack-merge": "^5.8.0" + }, + "engines": { + "vscode": "^1.69.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@microsoft/fast-element": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.11.0.tgz", + "integrity": "sha512-VKJYMkS5zgzHHb66sY7AFpYv6IfFhXrjQcAyNgi2ivD65My1XOhtjfKez5ELcLFRJfgZNAxvI8kE69apXERTkw==", + "dev": true + }, + "node_modules/@microsoft/fast-foundation": { + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.47.0.tgz", + "integrity": "sha512-EyFuioaZQ9ngjUNRQi8R3dIPPsaNQdUOS+tP0G7b1MJRhXmQWIitBM6IeveQA6ZvXG6H21dqgrfEWlsYrUZ2sw==", + "dev": true, + "dependencies": { + "@microsoft/fast-element": "^1.11.0", + "@microsoft/fast-web-utilities": "^5.4.1", + "tabbable": "^5.2.0", + "tslib": "^1.13.0" + } + }, + "node_modules/@microsoft/fast-react-wrapper": { + "version": "0.1.48", + "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.1.48.tgz", + "integrity": "sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==", + "dev": true, + "dependencies": { + "@microsoft/fast-element": "^1.9.0", + "@microsoft/fast-foundation": "^2.41.1" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@microsoft/fast-web-utilities": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", + "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", + "dev": true, + "dependencies": { + "exenv-es6": "^1.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@teamsupercell/typings-for-css-modules-loader": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.5.2.tgz", + "integrity": "sha512-3sqH2B4itcm5XgV1IHENt4NOaW7bOC1CwJr63vrdKWWyKVxNxtBM+ABVhJZYFCCVAwNy7ulA64z6HyQqw96m4A==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "loader-utils": "^1.4.2", + "schema-utils": "^2.0.1" + }, + "optionalDependencies": { + "prettier": "*" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/eslint": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", + "integrity": "sha512-rc9K8ZpVjNcLs8Fp0dkozd5Pt2Apk1glO4Vgz8ix1u6yFByxfqo5Yavpy65o+93TAe24jr7v+eSBtFLvOQtCRQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.18.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.12.tgz", + "integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.75.1", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.1.tgz", + "integrity": "sha512-emg7wdsTFzdi+elvoyoA+Q8keEautdQHyY5LNmHVM4PTpY8JgOTVADrGVyXGepJ6dVW2OS5/xnLUWh+nZxvdiA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", + "integrity": "sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/type-utils": "5.53.0", + "@typescript-eslint/utils": "5.53.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", + "integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", + "integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz", + "integrity": "sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.53.0", + "@typescript-eslint/utils": "5.53.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", + "integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", + "integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.53.0.tgz", + "integrity": "sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", + "integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.53.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vscode/codicons": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.30.tgz", + "integrity": "sha512-/quu8pLXEyrShoDjTImQwJ2H28y1XhANigyw7E7JvN9NNWc3XCkoIWpcb/tUhdf7XQpopLVVYbkMjXpdPPuMXg==", + "dev": true + }, + "node_modules/@vscode/test-electron": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.2.3.tgz", + "integrity": "sha512-7DmdGYQTqRNaLHKG3j56buc9DkstriY4aV0S3Zj32u0U9/T0L8vwWAC9QGCh1meu1VXDEla1ze27TkqysHGP0Q==", + "dev": true, + "dependencies": { + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "rimraf": "^3.0.2", + "unzipper": "^0.10.11" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@vscode/webview-ui-toolkit": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.2.1.tgz", + "integrity": "sha512-ZpVqLxoFWWk8mmAN7jr1v9yjD6NGBIoflAedNSusmaViqwHZ2znKBwAwcumLOlNlqmST6QMkiTVys7O8rzfd0w==", + "dev": true, + "dependencies": { + "@microsoft/fast-element": "^1.6.2", + "@microsoft/fast-foundation": "^2.38.0", + "@microsoft/fast-react-wrapper": "^0.1.18" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "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, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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 + }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dev": true, + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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 + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "dev": true, + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001457", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", + "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dev": true, + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "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, + "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/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==", + "dev": true + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "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 + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.19", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.308", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.308.tgz", + "integrity": "sha512-qyTx2aDFjEni4UnRWEME9ubd2Xc9c0zerTUl/ZinvD4QPsF0S7kJTV/Es/lPCTkNX6smyYar+z/n8Cl6pFr8yQ==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", + "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz", + "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exenv-es6": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", + "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.3.0.tgz", + "integrity": "sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "vue-template-compiler": "*", + "webpack": "^5.11.0" + }, + "peerDependenciesMeta": { + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "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 + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": 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/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz", + "integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": 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, + "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==", + "dev": 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==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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 + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "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 + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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 + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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 + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "dev": true + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/memfs": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", + "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "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 + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", + "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/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 + }, + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/mocha/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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 + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": 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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "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, + "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/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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "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 + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": 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, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", + "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", + "dev": true, + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true, + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rechoir/node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.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/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.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, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": 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/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sass": { + "version": "1.58.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.3.tgz", + "integrity": "sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-loader": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz", + "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": 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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/string-replace-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz", + "integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "peerDependencies": { + "webpack": "^5" + } + }, + "node_modules/string-replace-loader/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, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-replace-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/string-replace-loader/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tabbable": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", + "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", + "dev": true + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz", + "integrity": "sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": 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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ts-loader": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", + "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==", + "dev": true, + "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" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.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==", + "dev": true + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", + "colorette": "^2.0.14", + "commander": "^9.4.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/webpack-cli/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/webpack-cli/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-cli/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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-cli/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-cli/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@microsoft/fast-element": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.11.0.tgz", + "integrity": "sha512-VKJYMkS5zgzHHb66sY7AFpYv6IfFhXrjQcAyNgi2ivD65My1XOhtjfKez5ELcLFRJfgZNAxvI8kE69apXERTkw==", + "dev": true + }, + "@microsoft/fast-foundation": { + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.47.0.tgz", + "integrity": "sha512-EyFuioaZQ9ngjUNRQi8R3dIPPsaNQdUOS+tP0G7b1MJRhXmQWIitBM6IeveQA6ZvXG6H21dqgrfEWlsYrUZ2sw==", + "dev": true, + "requires": { + "@microsoft/fast-element": "^1.11.0", + "@microsoft/fast-web-utilities": "^5.4.1", + "tabbable": "^5.2.0", + "tslib": "^1.13.0" + } + }, + "@microsoft/fast-react-wrapper": { + "version": "0.1.48", + "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.1.48.tgz", + "integrity": "sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==", + "dev": true, + "requires": { + "@microsoft/fast-element": "^1.9.0", + "@microsoft/fast-foundation": "^2.41.1" + } + }, + "@microsoft/fast-web-utilities": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", + "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", + "dev": true, + "requires": { + "exenv-es6": "^1.1.1" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@teamsupercell/typings-for-css-modules-loader": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.5.2.tgz", + "integrity": "sha512-3sqH2B4itcm5XgV1IHENt4NOaW7bOC1CwJr63vrdKWWyKVxNxtBM+ABVhJZYFCCVAwNy7ulA64z6HyQqw96m4A==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "loader-utils": "^1.4.2", + "prettier": "*", + "schema-utils": "^2.0.1" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@types/eslint": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", + "integrity": "sha512-rc9K8ZpVjNcLs8Fp0dkozd5Pt2Apk1glO4Vgz8ix1u6yFByxfqo5Yavpy65o+93TAe24jr7v+eSBtFLvOQtCRQ==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "requires": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "@types/node": { + "version": "16.18.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.12.tgz", + "integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "@types/react": { + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "@types/vscode": { + "version": "1.75.1", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.1.tgz", + "integrity": "sha512-emg7wdsTFzdi+elvoyoA+Q8keEautdQHyY5LNmHVM4PTpY8JgOTVADrGVyXGepJ6dVW2OS5/xnLUWh+nZxvdiA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", + "integrity": "sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/type-utils": "5.53.0", + "@typescript-eslint/utils": "5.53.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", + "integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", + "integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz", + "integrity": "sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.53.0", + "@typescript-eslint/utils": "5.53.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", + "integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", + "integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/visitor-keys": "5.53.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.53.0.tgz", + "integrity": "sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.53.0", + "@typescript-eslint/types": "5.53.0", + "@typescript-eslint/typescript-estree": "5.53.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", + "integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.53.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "@vscode/codicons": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.30.tgz", + "integrity": "sha512-/quu8pLXEyrShoDjTImQwJ2H28y1XhANigyw7E7JvN9NNWc3XCkoIWpcb/tUhdf7XQpopLVVYbkMjXpdPPuMXg==", + "dev": true + }, + "@vscode/test-electron": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.2.3.tgz", + "integrity": "sha512-7DmdGYQTqRNaLHKG3j56buc9DkstriY4aV0S3Zj32u0U9/T0L8vwWAC9QGCh1meu1VXDEla1ze27TkqysHGP0Q==", + "dev": true, + "requires": { + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "rimraf": "^3.0.2", + "unzipper": "^0.10.11" + } + }, + "@vscode/webview-ui-toolkit": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.2.1.tgz", + "integrity": "sha512-ZpVqLxoFWWk8mmAN7jr1v9yjD6NGBIoflAedNSusmaViqwHZ2znKBwAwcumLOlNlqmST6QMkiTVys7O8rzfd0w==", + "dev": true, + "requires": { + "@microsoft/fast-element": "^1.6.2", + "@microsoft/fast-foundation": "^2.38.0", + "@microsoft/fast-react-wrapper": "^0.1.18" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "requires": {} + }, + "@webpack-cli/serve": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "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==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "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-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, + "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 + }, + "big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dev": true, + "requires": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true + }, + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "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 + }, + "buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "dev": true + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001457", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz", + "integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==", + "dev": true + }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dev": true, + "requires": { + "traverse": ">=0.3.0 <0.4" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.19", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "dev": true + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "electron-to-chromium": { + "version": "1.4.308", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.308.tgz", + "integrity": "sha512-qyTx2aDFjEni4UnRWEME9ubd2Xc9c0zerTUl/ZinvD4QPsF0S7kJTV/Es/lPCTkNX6smyYar+z/n8Cl6pFr8yQ==", + "dev": true + }, + "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==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true + }, + "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, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", + "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "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 + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "eslint-plugin-react": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esquery": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz", + "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "exenv-es6": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", + "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", + "dev": true + }, + "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==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "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 + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "fork-ts-checker-webpack-plugin": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.3.0.tgz", + "integrity": "sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "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 + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "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==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "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 + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "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==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "immutable": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz", + "integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true + }, + "is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + } + }, + "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 + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "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==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "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 + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true + }, + "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 + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "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 + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "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 + }, + "listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "dev": true + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "memfs": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", + "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.3" + } + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "mini-css-extract-plugin": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz", + "integrity": "sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } + } + }, + "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 + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "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 + }, + "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==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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 + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "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, + "requires": { + "@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" + } + }, + "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 + }, + "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 + }, + "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 + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "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, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "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, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "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, + "requires": { + "p-locate": "^4.1.0" + } + }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "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, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + } + } + }, + "postcss-loader": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", + "integrity": "sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.8" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + }, + "dependencies": { + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "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==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "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, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "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 + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "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==", + "dev": true + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "sass": { + "version": "1.58.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.3.tgz", + "integrity": "sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-loader": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz", + "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "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 + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "string-replace-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz", + "integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "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==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + }, + "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 + }, + "tabbable": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", + "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz", + "integrity": "sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "dev": true + }, + "ts-loader": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unzipper": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", + "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==", + "dev": true, + "requires": { + "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" + } + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webpack": { + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-cli": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", + "colorette": "^2.0.14", + "commander": "^9.4.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "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 + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "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==", + "dev": true + } + } +} diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json new file mode 100644 index 00000000000..79a701a59d8 --- /dev/null +++ b/firebase-vscode/package.json @@ -0,0 +1,115 @@ +{ + "name": "firebase-vscode", + "displayName": "firebase-vscode", + "publisher": "firebase", + "description": "VSCode Extension for Firebase", + "version": "0.0.9", + "engines": { + "vscode": "^1.69.0" + }, + "repository": "https://github.com/firebase/firebase-tools", + "sideEffects": false, + "categories": [ + "Other" + ], + "activationEvents": [ + "onStartupFinished" + ], + "main": "./dist/extension.js", + "contributes": { + "configuration": { + "title": "Firebase VS Code Extension", + "properties": { + "firebase-vscode-extension.firebaseRcFolder": { + "type": "string", + "default": "", + "description": "Firebase RC folder" + } + }, + "firebase.debug": { + "type": "boolean", + "default": false, + "description": "Logs debug-level messages to firebase-plugin-debug.log (requires restart)" + } + }, + "viewsContainers": { + "activitybar": [ + { + "id": "firebase", + "title": "Firebase", + "icon": "$(mono-firebase)" + } + ] + }, + "icons": { + "mono-firebase": { + "description": "Firebase icon", + "default": { + "fontPath": "./resources/Monicons.woff", + "fontCharacter": "\\F101" + } + } + }, + "views": { + "firebase": [ + { + "type": "webview", + "id": "firebase.sidebarView", + "name": "Firebase" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run build", + "copyfiles": "cp -r node_modules/@vscode/codicons/dist resources/dist", + "pkg:vsce": "node scripts/swap-pkg.js vsce && vsce package", + "pkg:monospace": "node scripts/swap-pkg.js monospace && vsce package", + "dev": "npm run copyfiles && webpack --config webpack.dev.js", + "dev:extension": "npm run copyfiles && webpack --config webpack.dev.js --config-name extension", + "dev:sidebar": "npm run copyfiles && webpack --config webpack.dev.js --config-name sidebar", + "watch": "node scripts/swap-pkg.js vsce && npm run copyfiles && webpack --config webpack.dev.js --watch", + "build": "npm run copyfiles && webpack --config webpack.prod.js --devtool hidden-source-map", + "build:extension": "webpack --config webpack.prod.js --config-name extension", + "build:sidebar": "npm run copyfiles && webpack --config webpack.prod.js --config-name sidebar", + "pretest": "npm run build && npm run lint", + "lint": "eslint src --ext ts", + "test": "node ./dist/test/runTest.js" + }, + "dependencies": { + "@vscode/codicons": "0.0.30", + "@vscode/webview-ui-toolkit": "^1.2.1", + "classnames": "^2.3.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", + "@types/glob": "^8.0.0", + "@types/mocha": "^10.0.1", + "@types/node": "16.x", + "@types/react": "^18.0.9", + "@types/react-dom": "^18.0.4", + "@types/vscode": "^1.69.0", + "@typescript-eslint/eslint-plugin": "^5.45.0", + "@typescript-eslint/parser": "^5.45.0", + "@vscode/test-electron": "^2.2.0", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.7.1", + "eslint": "^8.28.0", + "eslint-plugin-react": "^7.32.2", + "fork-ts-checker-webpack-plugin": "^7.3.0", + "glob": "^8.0.3", + "mini-css-extract-plugin": "^2.6.0", + "mocha": "^10.1.0", + "postcss-loader": "^7.0.0", + "sass": "^1.52.0", + "sass-loader": "^13.0.0", + "string-replace-loader": "^3.1.0", + "ts-loader": "^9.4.2", + "typescript": "^4.9.3", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1", + "webpack-merge": "^5.8.0" + } +} \ No newline at end of file diff --git a/firebase-vscode/resources/Monicons.woff b/firebase-vscode/resources/Monicons.woff new file mode 100644 index 0000000000000000000000000000000000000000..e6735ccb638c7ef0b6a97b3955a94cbd8238a4f8 GIT binary patch literal 1932 zcmZWpc{o(-1O8@4V`nOqX)Hgv_JrHDbuEP%+gQeOOO`W+2_j3r5ksSsV4Y!Tvjrsw|m`<~~0zVCg{d*1JS&pGFfamM4F-QAr55Y7aW z03PBp03n$Fe^}x1$^f8jVNd`N+8TF_M&a@9MEKrt*ewqO$GW%nm|$LN3Ia}^k> z-~>=0xH!APJ{_3r!Z73?Tr4KId7OlO{V+cbBmQmCc$u@?Q3C)F44A_i0CK+N6G90k z!}-XEFsH-UQl?&MGz0~b1K_nDtO+s=6}?XV9R!Q8<_&W!3{})T;$u)KEf(HapzVjD zf?@;9P;xAshk$i}{sCzhaDf~e2x}aee}?DBQC*&H;bD<9_^g6G5a1RjAF60waWl7pcB z@0@2Ffb$xC-SU_4Jz|y|aj-fgS|!Ly%+Ys3>0@l#jaV4$ol+a!!G)0M3YRIMjm3&> zYBYFi+cZz9niZ5*R!(c+n1{bCIbqXB8g^EC-Y6YnOPce;ee>(P_N_mct?kG5)pfEW zh<#N{xIZ@xeW5umQ=i1p>ix&ZbJc2%*E?b_tN)69v)Xc~Z*IfDw{BdR>QZ=Dg4$27@DPZN*y z4GW-ti~o5IKk>4pFp+H-C1ryV9;iyN^}Bm2*Y#Yv-RX+T@`9QV+g}OWP4rH#lPgsz z>e{7|nU0mt`mK7DW2R)f;C)fSrQMi=stlRKDEK?mH9%|#o+;JcCrIWu8S@xwbYtyU zmS~#AqY9PL;gDzdEBz@7<+n&K6xqZ&>76=zp~fKm0Ex>GN>aPpTz!Nz+clq2@GIV>kZ@{Cm#H86V0WAwLhc=hjdh95IdzMs) zuKKHsyzB9Hq@FylV79t?WYM~;sd(?x<5E*G3$rg{qUePddVj>0fxS`QUV%+D`G(&d zhq>tB0%F7I=IZ^QD~3oHn|t!t1KM7`;;?=Yk9-CLSyuwP*&WHsH}&57|P4~uxHqtDf%(+i7K4= zbQWU8xq{=9mEW)Ar*D}yj4#a$%IIQVubgjZA!(;Z{VpKCh;U6a3_Wt2BqQtzBjHVz z|3Mo@is-)$J>Po2aqJ53ts>j6*-bsJgqWDjDe}d6=2^c{Wwd%VeX>=Ek{1mi%#zw3 zr{WGJU1dAdx&q%saKwhD{5c+4R~?&0W|m?VYKQFW4os9EvniIC(c(q$`WdtI9<=?C zNzeP6fjHtvvqY}`?lb(#j`nP%Ser8~*=~0xtAy4);I`mo!Fp=f^`w_?fRkzyUw1jc zIkMJLwTxLd2_6p537xN@q8r-osU%7K^{%I7UqnQQwAy*2NWy?aw&sCuW6G><}72 ze2x@B8P`bEUC8-wV=8N?1I~b$pOZ2wXc?-dKBQLVvGC$OL*3ZtQ>iobP{)qDO{$>~vmG z_z;9h*W2Fh19@Z}UX6Wf`VokM0o RHC2o_C<$!a_bkEr{{w7sIZOZm literal 0 HcmV?d00001 diff --git a/firebase-vscode/scripts/swap-pkg.js b/firebase-vscode/scripts/swap-pkg.js new file mode 100644 index 00000000000..f8655afa9b3 --- /dev/null +++ b/firebase-vscode/scripts/swap-pkg.js @@ -0,0 +1,29 @@ +const { writeFileSync } = require("fs"); +const path = require("path"); +const pkg = require(path.join(__dirname, "../package.json")); + +let target = "vsce"; + +process.argv.forEach((arg) => { + if (arg === "vsce" || arg === "monospace") { + target = arg; + } +}); + +if (target === "vsce") { + delete pkg.extensionDependencies; + console.log( + "Removing google.monospace extensionDependency for VSCE" + " packaging." + ); +} else if (target === "monospace") { + pkg.extensionDependencies = ["google.monospace"]; + console.log( + "Adding google.monospace extensionDependency for Monospace" + " packaging." + ); +} + +writeFileSync( + path.join(__dirname, "../package.json"), + JSON.stringify(pkg, null, 2), + { encoding: "utf8" } +); diff --git a/firebase-vscode/src/cli.ts b/firebase-vscode/src/cli.ts new file mode 100644 index 00000000000..87ab28f42e8 --- /dev/null +++ b/firebase-vscode/src/cli.ts @@ -0,0 +1,214 @@ +import * as vscode from "vscode"; +import { inspect } from "util"; + +import { + getAllAccounts, + getGlobalDefaultAccount, + loginGoogle, + setGlobalDefaultAccount, +} from "../../src/auth"; +import { logoutAction } from "../../src/commands/logout"; +import { hostingChannelDeployAction } from "../../src/commands/hosting-channel-deploy"; +import { listFirebaseProjects } from "../../src/management/projects"; +import { requireAuth } from "../../src/requireAuth"; +import { deploy } from "../../src/deploy"; +import { FirebaseConfig, HostingSingle } from "../../src/firebaseConfig"; +import { FirebaseRC } from "../common/firebaserc"; +import { getDefaultHostingSite } from "../../src/getDefaultHostingSite"; +import { initAction } from "../../src/commands/init"; +import { Account, User } from "../../src/types/auth"; +import { Options } from "../../src/options"; +import { currentOptions, getCommandOptions } from "./options"; +import { setInquirerOptions } from "./stubs/inquirer-stub"; +import { ServiceAccount } from "../common/types"; +import { listChannels } from "../../src/hosting/api"; +import { ChannelWithId } from "../common/messaging/types"; +import { setEnabled } from "../../src/experiments"; +import { pluginLogger } from "./logger-wrapper"; + +/** + * Wrap the CLI's requireAuth() which is normally run before every command + * requiring user to be logged in. The CLI automatically supplies it with + * account info if found in configstore so we need to fill that part in. + */ +async function requireAuthWrapper(showError: boolean = true) { + // Try to get global default from configstore. For some reason this is + // often overwritten when restarting the extension. + let account = getGlobalDefaultAccount(); + if (!account) { + // If nothing in configstore top level, grab the first "additionalAccount" + const accounts = getAllAccounts(); + if (accounts.length > 0) { + account = accounts[0]; + setGlobalDefaultAccount(account); + } + } + // If account is still null, `requireAuth()` will use google-auth-library + // to look for the service account hopefully. + try { + const commandOptions = await getCommandOptions(undefined, { + ...currentOptions, + ...account, + }); + await requireAuth(commandOptions); + } catch (e) { + if (showError) { + pluginLogger.error('requireAuth error', e.original || e); + vscode.window.showErrorMessage("Not logged in", { + modal: true, + detail: `Log in by clicking "Sign in with Google" in the sidebar.`, + }); + } else { + // If "showError" is false, this may not be an error, just an indication + // no one is logged in. Log to "debug". + pluginLogger.debug('No user found (this may be normal), requireAuth error output:', + e.original || e); + } + return false; + } + // No accounts but no error on requireAuth means it's a service account + // (or glogin - edge case) + return true; +} + +export async function getAccounts(): Promise> { + // Get Firebase login accounts + const accounts: Array = getAllAccounts(); + pluginLogger.debug(`Found ${accounts.length} non-service accounts.`); + // Get other accounts (assuming service account for now, could also be glogin) + const otherAuthExists = await requireAuthWrapper(false); + if (otherAuthExists) { + pluginLogger.debug(`Found service account`); + accounts.push({ + user: { email: "service_account", type: "service_account" }, + }); + } + return accounts; +} + +export async function getChannels(firebaseJSON: FirebaseConfig): Promise { + if (!firebaseJSON) { + return []; + } + const loggedIn = await requireAuthWrapper(false); + if (!loggedIn) { + return []; + } + const options = { ...currentOptions }; + if (!options.project) { + return []; + } + // TODO(hsubox76): handle multiple hosting configs + if (!(firebaseJSON.hosting as HostingSingle).site) { + (firebaseJSON.hosting as HostingSingle).site = + await getDefaultHostingSite(options); + } + pluginLogger.debug( + 'Calling listChannels with params', + options.project, + (firebaseJSON.hosting as HostingSingle).site + ); + try { + const channels = await listChannels(options.project, (firebaseJSON.hosting as HostingSingle).site); + return channels.map(channel => ({ + ...channel, id: channel.name.split("/").pop() + })); + } catch (e) { + pluginLogger.error('Error on listChannels()', e); + vscode.window.showErrorMessage("Error finding hosting channels", { + modal: true, + detail: `Error finding hosting channels: ${e}`, + }); + return []; + } +} + +export async function logoutUser(email: string): Promise { + await logoutAction(email, {} as Options); +} + +/** + * Login with standard Firebase login + */ +export async function login() { + const userCredentials = await loginGoogle(true); + setGlobalDefaultAccount(userCredentials as Account); + return userCredentials as { user: User }; +} + +export async function listProjects() { + const loggedIn = await requireAuthWrapper(false); + if (!loggedIn) { + return []; + } + return listFirebaseProjects(); +} + +export async function initHosting(options: { spa: boolean; public: string }) { + await requireAuthWrapper(); + let webFrameworksOptions = {}; + if (process.env.MONOSPACE_ENV) { + pluginLogger.debug('initHosting found MONOSPACE_ENV, ' + + 'setting web frameworks options'); + // TODO(hsubox76): Also allow VS Code users to enable this manually with a UI + setEnabled('webframeworks', true); + webFrameworksOptions = { + // Should use auto-discovered framework + useDiscoveredFramework: true, + // Should set up a new framework - do not do this on Monospace + useWebFrameworks: false + }; + } + const commandOptions = await getCommandOptions(undefined, currentOptions); + const inquirerOptions = { + ...commandOptions, + ...options, + ...webFrameworksOptions, + // False for now, we can let the user decide if needed + github: false + }; + pluginLogger.debug('Calling hosting init with inquirer options', inspect(inquirerOptions)); + setInquirerOptions(inquirerOptions); + await initAction("hosting", commandOptions); +} + +export async function deployToHosting( + firebaseJSON: FirebaseConfig, + firebaseRC: FirebaseRC, + deployTarget: string +) { + if (!(await requireAuthWrapper())) { + return { success: false, hostingUrl: "", consoleUrl: "" }; + } + + // TODO(hsubox76): throw if it doesn't find firebaseJSON or the hosting field + try { + const options = { ...currentOptions }; + // TODO(hsubox76): handle multiple hosting configs + if (!(firebaseJSON.hosting as HostingSingle).site) { + pluginLogger.debug('Calling getDefaultHostingSite() with options', inspect(options)); + (firebaseJSON.hosting as HostingSingle).site = + await getDefaultHostingSite(options); + } + pluginLogger.debug('Calling getCommandOptions() with options', inspect(options)); + const commandOptions = await getCommandOptions(firebaseJSON, options); + pluginLogger.debug('Calling hosting deploy with command options', inspect(commandOptions)); + if (deployTarget === 'live') { + await deploy(["hosting"], commandOptions); + } else { + await hostingChannelDeployAction(deployTarget, commandOptions); + } + pluginLogger.debug('Hosting deploy complete'); + } catch (e) { + let message = `Error deploying to hosting`; + if (e.message) { + message += `: ${e.message}`; + } + if (e.original) { + message += ` (original: ${e.original})`; + } + pluginLogger.error(message); + return { success: false, hostingUrl: "", consoleUrl: "" }; + } + return { success: true, hostingUrl: "", consoleUrl: "" }; +} diff --git a/firebase-vscode/src/extension-broker.ts b/firebase-vscode/src/extension-broker.ts new file mode 100644 index 00000000000..7bd7d82f8cf --- /dev/null +++ b/firebase-vscode/src/extension-broker.ts @@ -0,0 +1,41 @@ +import { Webview } from "vscode"; + +import { Broker, BrokerImpl } from "../common/messaging/broker"; +import { + ExtensionToWebviewParamsMap, + WebviewToExtensionParamsMap, +} from "../common/messaging/protocol"; +import { Message } from "../common/messaging/types"; + +export type ExtensionBrokerImpl = BrokerImpl< + ExtensionToWebviewParamsMap, + WebviewToExtensionParamsMap, + Webview +>; + +export class ExtensionBroker extends Broker< + ExtensionToWebviewParamsMap, + WebviewToExtensionParamsMap, + Webview +> { + private webviews: Webview[] = []; + + sendMessage(command: string, data: ExtensionToWebviewParamsMap[keyof ExtensionToWebviewParamsMap]): void { + for (const webview of this.webviews) { + webview.postMessage({ command, data }); + } + } + + registerReceiver(receiver: Webview) { + const webview = receiver; + this.webviews.push(webview); + webview.onDidReceiveMessage( + (message: Message) => { + this.executeListeners(message); + }, null); + } + + delete(): void { + this.webviews = []; + } +} diff --git a/firebase-vscode/src/extension.ts b/firebase-vscode/src/extension.ts new file mode 100644 index 00000000000..8dababb29a0 --- /dev/null +++ b/firebase-vscode/src/extension.ts @@ -0,0 +1,30 @@ +// The module 'vscode' contains the VS Code extensibility API +// Import the module and reference it with the alias vscode in your code below +import * as vscode from "vscode"; + +import { ExtensionBroker } from "./extension-broker"; +import { createBroker } from "../common/messaging/broker"; +import { + ExtensionToWebviewParamsMap, + WebviewToExtensionParamsMap, +} from "../common/messaging/protocol"; +import { setupSidebar } from "./sidebar"; +import { setupWorkflow } from "./workflow"; +import { pluginLogger } from "./logger-wrapper"; + +const broker = createBroker< + ExtensionToWebviewParamsMap, + WebviewToExtensionParamsMap, + vscode.Webview +>(new ExtensionBroker()); + +// This method is called when your extension is activated +export function activate(context: vscode.ExtensionContext) { + pluginLogger.debug('Activating Firebase extension.'); + + setupWorkflow(context, broker); + setupSidebar(context, broker); +} + +// This method is called when your extension is deactivated +export function deactivate() {} diff --git a/firebase-vscode/src/html-scaffold.ts b/firebase-vscode/src/html-scaffold.ts new file mode 100644 index 00000000000..14edb018ef6 --- /dev/null +++ b/firebase-vscode/src/html-scaffold.ts @@ -0,0 +1,68 @@ +import { Uri, Webview } from "vscode"; + +export function getHtmlForWebview( + entryName: string, + extensionUri: Uri, + webview: Webview +) { + const scriptUri = webview.asWebviewUri( + Uri.joinPath(extensionUri, `dist/web-${entryName}.js`) + ); + const styleUri = webview.asWebviewUri( + Uri.joinPath(extensionUri, `dist/web-${entryName}.css`) + ); + const moniconWoffUri = webview.asWebviewUri( + Uri.joinPath(extensionUri, "resources/Monicons.woff") + ); + const codiconsUri = webview.asWebviewUri( + Uri.joinPath(extensionUri, "resources/dist/codicon.css") + ); + // Use a nonce to only allow a specific script to be run. + const nonce = getNonce(); + + return ` + + + + + + + + + + + + +
+ + +`; +} + +function getNonce() { + let text = ""; + const possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/firebase-vscode/src/logger-wrapper.ts b/firebase-vscode/src/logger-wrapper.ts new file mode 100644 index 00000000000..40e9d9a0667 --- /dev/null +++ b/firebase-vscode/src/logger-wrapper.ts @@ -0,0 +1,15 @@ +import { logger as cliLogger } from "../../src/logger"; +import { setInquirerLogger } from "./stubs/inquirer-stub"; + +export const pluginLogger: Record void> = {}; + +const logLevels = ['debug', 'info', 'log', 'warn', 'error']; + +for (const logLevel of logLevels) { + pluginLogger[logLevel] = (...args) => { + const prefixedArgs = ['[Firebase Plugin]', ...args]; + cliLogger[logLevel](...prefixedArgs); + }; +} + +setInquirerLogger(pluginLogger); \ No newline at end of file diff --git a/firebase-vscode/src/options.ts b/firebase-vscode/src/options.ts new file mode 100644 index 00000000000..b2734b44abb --- /dev/null +++ b/firebase-vscode/src/options.ts @@ -0,0 +1,99 @@ +import { Config as cliConfig } from "../../src/config"; +import { FirebaseConfig } from "../../src/firebaseConfig"; +import { FirebaseRC } from "../common/firebaserc"; +import { RC } from "../../src/rc"; +import { BaseOptions, Options } from "../../src/options"; +import { Command } from "../../src/command"; +import { ExtensionContext } from "vscode"; +import { setInquirerOptions } from "./stubs/inquirer-stub"; + +/** + * User-facing CLI options + * Passed to command.prepare() + */ + +interface CliOptions extends Omit { + config: string; +} + +/** + * Final options passed to CLI command functions + * Result of command.prepare() + */ +interface CommandOptions extends Options {} + +/** + * User-facing CLI options + */ +export let currentOptions: CliOptions & { isVSCE: boolean } = { + cwd: "", + configPath: "", + only: "", + except: "", + config: "", + filteredTargets: [], + force: true, + + // Options which are present on every command + project: "", + projectAlias: "", + projectId: "", + projectNumber: "", + projectRoot: "", + account: "", + json: true, + nonInteractive: true, + interactive: false, + debug: false, + + rc: null, + isVSCE: true +}; + +export function updateOptions( + context: ExtensionContext, + firebaseJSON: FirebaseConfig, + firebaseRC: FirebaseRC +) { + // const config = new cliConfig(firebaseJSON, options); + // currentOptions.config = config; + if (firebaseJSON) { + currentOptions.configPath = `${currentOptions.cwd}/firebase.json`; + if (firebaseJSON.hosting) { + currentOptions = { + ...currentOptions, + ...firebaseJSON.hosting, + }; + } + } else { + currentOptions.configPath = ""; + } + if (firebaseRC) { + currentOptions.rc = new RC(`${currentOptions.cwd}/.firebaserc`, firebaseRC); + currentOptions.project = firebaseRC.projects?.default; + } else { + currentOptions.rc = null; + currentOptions.project = ""; + } + context.globalState.setKeysForSync(["currentOptions"]); + context.globalState.update("currentOptions", currentOptions); + setInquirerOptions(currentOptions); +} + +/** + * Temporary options to pass to a command, don't write. + * Mostly runs it through the CLI's command.prepare() options formatter. + */ +export async function getCommandOptions( + firebaseJSON: FirebaseConfig = {}, + options: CliOptions = currentOptions +): Promise { + // Use any string, it doesn't affect `prepare()`. + const command = new Command("deploy"); + let newOptions = Object.assign(options); + if (firebaseJSON.hosting) { + newOptions = Object.assign(newOptions, firebaseJSON.hosting); + } + await command.prepare(newOptions); + return newOptions as CommandOptions; +} diff --git a/firebase-vscode/src/sidebar.ts b/firebase-vscode/src/sidebar.ts new file mode 100644 index 00000000000..533bbbf4585 --- /dev/null +++ b/firebase-vscode/src/sidebar.ts @@ -0,0 +1,65 @@ +import { + CancellationToken, + commands, + ExtensionContext, + Uri, + WebviewView, + WebviewViewProvider, + WebviewViewResolveContext, + window, +} from "vscode"; +import { getHtmlForWebview } from "./html-scaffold"; +import { ExtensionBrokerImpl } from "./extension-broker"; + +export function setupSidebar( + context: ExtensionContext, + extensionBroker: ExtensionBrokerImpl +): SidebarViewProvider { + const provider = new SidebarViewProvider( + context.extensionUri, + extensionBroker + ); + context.subscriptions.push( + window.registerWebviewViewProvider( + SidebarViewProvider.viewType, + provider + ) + ); + return provider; +} + +class SidebarViewProvider implements WebviewViewProvider { + public static readonly viewType = "firebase.sidebarView"; + private _view?: WebviewView; + + constructor( + private readonly _extensionUri: Uri, + private readonly extensionBroker: ExtensionBrokerImpl + ) {} + + public resolveWebviewView( + webviewView: WebviewView, + context: WebviewViewResolveContext, + _token: CancellationToken + ) { + this._view = webviewView; + this.extensionBroker.registerReceiver(webviewView.webview); + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [this._extensionUri], + }; + webviewView.webview.html = getHtmlForWebview( + "sidebar", + this._extensionUri, + webviewView.webview + ); + webviewView.webview.onDidReceiveMessage((data) => { + switch (data.type) { + case "executeCommand": { + commands.executeCommand(data.command, ...(data.args || [])); + break; + } + } + }); + } +} diff --git a/firebase-vscode/src/stubs/empty-class.js b/firebase-vscode/src/stubs/empty-class.js new file mode 100644 index 00000000000..23451b57f03 --- /dev/null +++ b/firebase-vscode/src/stubs/empty-class.js @@ -0,0 +1,3 @@ +class Noop {} + +module.exports = Noop; diff --git a/firebase-vscode/src/stubs/empty-function.js b/firebase-vscode/src/stubs/empty-function.js new file mode 100644 index 00000000000..2fc0e18e095 --- /dev/null +++ b/firebase-vscode/src/stubs/empty-function.js @@ -0,0 +1,3 @@ +const noop = () => {}; + +module.exports = noop; diff --git a/firebase-vscode/src/stubs/inquirer-stub.js b/firebase-vscode/src/stubs/inquirer-stub.js new file mode 100644 index 00000000000..c20dfbd871e --- /dev/null +++ b/firebase-vscode/src/stubs/inquirer-stub.js @@ -0,0 +1,31 @@ +const inquirer = module.exports; + +let pluginLogger = { + debug: () => {} +}; +const optionsKey = Symbol('options'); +inquirer[optionsKey] = {}; + +inquirer.setInquirerOptions = (inquirerOptions) => { + inquirer[optionsKey] = inquirerOptions; +}; + +inquirer.setInquirerLogger = (logger) => { + pluginLogger = logger; +}; + +inquirer.prompt = async (prompts) => { + const answers = {}; + for (const prompt of prompts) { + if (inquirer[optionsKey].hasOwnProperty(prompt.name)) { + answers[prompt.name] = inquirer[optionsKey][prompt.name]; + } else { + pluginLogger.debug( + `Didn't find "${prompt.name}" in options (message:` + + ` "${prompt.message}"), defaulting to value "${prompt.default}"` + ); + answers[prompt.name] = prompt.default; + } + } + return answers; +}; diff --git a/firebase-vscode/src/stubs/marked.js b/firebase-vscode/src/stubs/marked.js new file mode 100644 index 00000000000..8a332ace85e --- /dev/null +++ b/firebase-vscode/src/stubs/marked.js @@ -0,0 +1,5 @@ +function marked() {} + +marked.setOptions = () => {}; + +export { marked }; diff --git a/firebase-vscode/src/test/runTest.ts b/firebase-vscode/src/test/runTest.ts new file mode 100644 index 00000000000..e810ed5b2ac --- /dev/null +++ b/firebase-vscode/src/test/runTest.ts @@ -0,0 +1,23 @@ +import * as path from "path"; + +import { runTests } from "@vscode/test-electron"; + +async function main() { + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); + + // The path to test runner + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, "./suite/index"); + + // Download VS Code, unzip it and run the integration test + await runTests({ extensionDevelopmentPath, extensionTestsPath }); + } catch (err) { + console.error("Failed to run tests"); + process.exit(1); + } +} + +main(); diff --git a/firebase-vscode/src/test/suite/extension.test.ts b/firebase-vscode/src/test/suite/extension.test.ts new file mode 100644 index 00000000000..2f671d3c729 --- /dev/null +++ b/firebase-vscode/src/test/suite/extension.test.ts @@ -0,0 +1,15 @@ +import * as assert from "assert"; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +import * as vscode from "vscode"; +// import * as myExtension from '../../extension'; + +suite("Extension Test Suite", () => { + vscode.window.showInformationMessage("Start all tests."); + + test("Sample test", () => { + assert.strictEqual(-1, [1, 2, 3].indexOf(5)); + assert.strictEqual(-1, [1, 2, 3].indexOf(0)); + }); +}); diff --git a/firebase-vscode/src/test/suite/index.ts b/firebase-vscode/src/test/suite/index.ts new file mode 100644 index 00000000000..2cb7d7d8b3b --- /dev/null +++ b/firebase-vscode/src/test/suite/index.ts @@ -0,0 +1,38 @@ +import * as path from "path"; +import * as Mocha from "mocha"; +import * as glob from "glob"; + +export function run(): Promise { + // Create the mocha test + const mocha = new Mocha({ + ui: "tdd", + color: true, + }); + + const testsRoot = path.resolve(__dirname, ".."); + + return new Promise((c, e) => { + glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run((failures) => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + console.error(err); + e(err); + } + }); + }); +} diff --git a/firebase-vscode/src/utils.ts b/firebase-vscode/src/utils.ts new file mode 100644 index 00000000000..e7a95b8d01a --- /dev/null +++ b/firebase-vscode/src/utils.ts @@ -0,0 +1,11 @@ +import * as fs from "fs"; +import { FirebaseRC } from "../../src/firebaserc"; + +// TODO(hsubox76): use `loadRC` and `RC.save` from firebase-tools src/rc.ts +// for RC file operations +export async function writeFirebaseRCFile( + filename: string, + content: FirebaseRC +) { + fs.writeFileSync(filename, JSON.stringify(content, null, 2)); +} diff --git a/firebase-vscode/src/workflow.ts b/firebase-vscode/src/workflow.ts new file mode 100644 index 00000000000..fa799f6e64f --- /dev/null +++ b/firebase-vscode/src/workflow.ts @@ -0,0 +1,449 @@ +import * as path from "path"; +import * as fs from "fs"; +import * as vscode from "vscode"; +import { transports, format } from "winston"; +import stripAnsi from "strip-ansi"; +import { SPLAT } from "triple-beam"; +import { ExtensionContext, workspace } from "vscode"; + +import { FirebaseProjectMetadata } from "../../src/types/project"; +import { writeFirebaseRCFile } from "./utils"; +import { ExtensionBrokerImpl } from "./extension-broker"; +import { + deployToHosting, + getAccounts, + listProjects, + login, + logoutUser, + initHosting, + getChannels, +} from "./cli"; +import { User } from "../../src/types/auth"; +import { FirebaseRC } from "../../src/firebaserc"; +import { FirebaseConfig } from "../../src/firebaseConfig"; +import { currentOptions, updateOptions } from "./options"; +import { ServiceAccountUser } from "./types"; +import { selectProjectInMonospace } from "../../src/monospace"; +import { setupLoggers, tryStringify } from "../../src/utils"; +import { pluginLogger } from "./logger-wrapper"; +import { logger } from '../../src/logger'; + +let firebaseRC: FirebaseRC | null = null; +let firebaseJSON: FirebaseConfig | null = null; +let extensionContext: ExtensionContext = null; +let users: Array = []; +let currentUserEmail = ""; +// Stores a mapping from user email to list of projects for that user +let projectsUserMapping = new Map(); + +async function fetchUsers() { + const accounts = await getAccounts(); + users = accounts.map((account) => account.user); +} + +/** + * Get the user to select a project. + */ +async function promptUserForProject( + broker: ExtensionBrokerImpl, + projects: FirebaseProjectMetadata[] +) { + const items = projects.map(({ projectId }) => projectId); + + return new Promise((resolve, reject) => { + vscode.window.showQuickPick(items).then(async (projectId) => { + const project = projects.find((p) => p.projectId === projectId); + if (!project) { + if (firebaseRC?.projects?.default) { + // Don't show an error message if a project was previously selected, + // just do nothing. + resolve(null); + } + reject("Invalid project selected. Please select a project to proceed"); + } else { + resolve(project.projectId); + } + }); + }); +} + +function updateCurrentUser( + users: User[], + broker: ExtensionBrokerImpl, + newUserEmail?: string +) { + if (newUserEmail) { + if (newUserEmail === currentUserEmail) { + return currentUserEmail; + } else { + currentUserEmail = newUserEmail; + } + } + if (!newUserEmail) { + if (users.length > 0) { + currentUserEmail = users[0].email; + } else { + currentUserEmail = null; + } + } + broker.send("notifyUserChanged", { email: currentUserEmail }); + return currentUserEmail; +} + +function getRootFolders() { + if (!workspace) { + return []; + } + const folders = workspace.workspaceFolders + ? workspace.workspaceFolders.map((wf) => wf.uri.fsPath) + : []; + if (workspace.workspaceFile) { + folders.push(path.dirname(workspace.workspaceFile.fsPath)); + } + return Array.from(new Set(folders)); +} + +function getConfigFile(filename: string): T | null { + const rootFolders = getRootFolders(); + for (const folder of rootFolders) { + const jsonFilePath = path.join(folder, filename); + if (fs.existsSync(jsonFilePath)) { + const fileText = fs.readFileSync(jsonFilePath, "utf-8"); + try { + const result = JSON.parse(fileText); + currentOptions.cwd = folder; + return result; + } catch (e) { + pluginLogger.error(`Error parsing JSON in ${jsonFilePath}`); + return null; + } + } + } + // Usually there's only one root folder unless someone is using a + // multi-root VS Code workspace. + // https://code.visualstudio.com/docs/editor/multi-root-workspaces + // We were trying to play it safe up above by assigning the cwd + // based on where a .firebaserc or firebase.json was found but if + // the user hasn't run firebase init there won't be one, and without + // a cwd we won't know where to put it. + // + // TODO: prompt where we're going to save a new firebase config + // file before we do it so the user can change it + if (!currentOptions.cwd) { + currentOptions.cwd = rootFolders[0]; + } + return null; +} + +export function setupWorkflow( + context: ExtensionContext, + broker: ExtensionBrokerImpl +) { + extensionContext = context; + + // Get user-defined VSCode settings. + const workspaceConfig = workspace.getConfiguration( + 'firebase', + vscode.workspace.workspaceFolders[0].uri + ); + const shouldDebug: boolean = workspaceConfig.get('debug'); + + /** + * Logging setup for logging to console and to file. + */ + // Sets up CLI logger to log to console + process.env.DEBUG = 'true'; + setupLoggers(); + // Re-implement file logger call from ../../src/bin/firebase.ts to not bring + // in the entire firebase.ts file + const rootFolders = getRootFolders(); + const filePath = path.join(rootFolders[0], 'firebase-plugin-debug.log'); + pluginLogger.info('Logging to path', filePath); + // Only log to file if firebase.debug extension setting is true. + if (shouldDebug) { + logger.add( + new transports.File({ + level: "debug", + filename: filePath, + format: format.printf((info) => { + const segments = [info.message, ...(info[SPLAT] || [])] + .map(tryStringify); + return `[${info.level}] ${stripAnsi(segments.join(" "))}`; + }), + }) + ); + } + // Read config files and store in memory. + readFirebaseConfigs(); + // Check current users state + fetchUsers(); + // Get hosting channels + fetchChannels(); + + /** + * Call pluginLogger with log arguments received from webview. + */ + broker.on("writeLog", async ({ level, args }) => { + pluginLogger[level]('(Webview)', ...args); + }); + + broker.on("getEnv", async () => { + pluginLogger.debug(`Value of process.env.MONOSPACE_ENV: ` + + `${process.env.MONOSPACE_ENV}`); + broker.send("notifyEnv", { + env: { + isMonospace: Boolean(process.env.MONOSPACE_ENV), + } + }); + }); + + broker.on("getUsers", async () => { + if (users.length === 0) { + await fetchUsers(); + } + broker.send("notifyUsers", { users }); + currentUserEmail = updateCurrentUser(users, broker); + }); + + broker.on("logout", async ({ email }: { email: string }) => { + try { + await logoutUser(email); + const accounts = await getAccounts(); + users = accounts.map((account) => account.user); + broker.send("notifyUsers", { users }); + currentUserEmail = updateCurrentUser(users, broker); + } catch (e) { + // ignored + } + }); + + broker.on("getSelectedProject", async () => { + // For now, just read the cached value. + // TODO: Extend this to reading from firebaserc + if (firebaseRC?.projects?.default) { + broker.send("notifyProjectChanged", + { projectId: firebaseRC?.projects?.default }); + } + fetchChannels(); + }); + + broker.on("showMessage", async ({ msg, options }) => { + vscode.window.showInformationMessage(msg, options); + }); + + broker.on("openLink", async ({ href }) => { + vscode.env.openExternal(vscode.Uri.parse(href)); + }); + + broker.on("addUser", async () => { + const { user } = await login(); + users.push(user); + if (users) { + broker.send("notifyUsers", { users }); + currentUserEmail = updateCurrentUser( + users, + broker, + user.email + ); + } + }); + + broker.on("requestChangeUser", ( + { user: requestedUser }: + { user: User | ServiceAccountUser } + ) => { + if (users.some((user) => user.email === requestedUser.email)) { + currentUserEmail = requestedUser.email; + broker.send("notifyUserChanged", { email: currentUserEmail }); + } + }); + + broker.on("selectProject", selectProject); + + broker.on("selectAndInitHostingFolder", selectAndInitHosting); + + broker.on("hostingDeploy", async ({ target: deployTarget }) => { + const { success, consoleUrl, hostingUrl } = await deployToHosting( + firebaseJSON, + firebaseRC, + deployTarget + ); + broker.send("notifyHostingDeploy", { success, consoleUrl, hostingUrl }); + if (success) { + fetchChannels(); + } + }); + + broker.on("getFirebaseJson", async () => { + readAndSendFirebaseConfigs(broker); + }); + + context.subscriptions.push( + setupFirebaseJsonAndRcFileSystemWatcher(broker) + ); + + async function fetchChannels() { + const channels = await getChannels(firebaseJSON); + broker.send("notifyChannels", { channels }); + } + + async function selectProject({ email }) { + let projectId; + if (process.env.MONOSPACE_ENV) { + pluginLogger.debug('selectProject: found MONOSPACE_ENV, ' + + 'prompting user using external flow'); + /** + * Monospace case: use Monospace flow + */ + const monospaceExtension = + vscode.extensions.getExtension('google.monospace'); + process.env.MONOSPACE_DAEMON_PORT = + monospaceExtension.exports.getMonospaceDaemonPort(); + try { + projectId = await selectProjectInMonospace({ + projectRoot: currentOptions.cwd, + project: undefined, + isVSCE: true + }); + } catch (e) { + pluginLogger.error(e); + } + } else if (email === 'service_account') { + /** + * Non-Monospace service account case: get the service account's only + * linked project. + */ + pluginLogger.debug('selectProject: MONOSPACE_ENV not found, ' + + ' but service account found'); + const projects = (await listProjects()) as FirebaseProjectMetadata[]; + projectsUserMapping.set(email, projects); + // Service accounts should only have one project. + projectId = projects[0].projectId; + } else { + /** + * Default Firebase login case, let user choose from projects that + * Firebase login has access to. + */ + pluginLogger.debug('selectProject: no service account or MONOSPACE_ENV ' + + 'found, using firebase account to list projects'); + let projects = []; + if (projectsUserMapping.has(email)) { + pluginLogger.info(`using cached projects list for ${email}`); + projects = projectsUserMapping.get(email)!; + } else { + pluginLogger.info(`fetching projects list for ${email}`); + vscode.window.showQuickPick(["Loading...."]); + projects = (await listProjects()) as FirebaseProjectMetadata[]; + projectsUserMapping.set(email, projects); + } + try { + projectId = await promptUserForProject(broker, projects); + } catch (e) { + vscode.window.showErrorMessage(e.message); + } + } + if (projectId) { + await updateFirebaseRC("default", projectId); + broker.send("notifyProjectChanged", { projectId }); + fetchChannels(); + } + } + + async function selectAndInitHosting({ projectId, singleAppSupport }) { + const options: vscode.OpenDialogOptions = { + canSelectMany: false, + openLabel: `Select distribution/public folder for ${projectId}`, + canSelectFiles: false, + canSelectFolders: true, + }; + const fileUri = await vscode.window.showOpenDialog(options); + if (fileUri && fileUri[0] && fileUri[0].fsPath) { + const publicFolderFull = fileUri[0].fsPath; + const publicFolder = publicFolderFull.substring( + currentOptions.cwd.length + 1 + ); + await initHosting({ + spa: singleAppSupport, + public: publicFolder, + }); + readAndSendFirebaseConfigs(broker); + broker.send("notifyHostingFolderReady", + { projectId, folderPath: currentOptions.cwd }); + + await fetchChannels(); + } + } +} + +/** + * Parse firebase.json and .firebaserc from the configured location, if they + * exist, and write to memory. + */ +function readFirebaseConfigs() { + firebaseRC = getConfigFile(".firebaserc"); + firebaseJSON = getConfigFile("firebase.json"); + + updateOptions(extensionContext, firebaseJSON, firebaseRC); +} + +/** + * Read Firebase configs and then send it to webviews through the given broker + */ +async function readAndSendFirebaseConfigs(broker: ExtensionBrokerImpl) { + readFirebaseConfigs(); + broker.send("notifyFirebaseConfig", + { + firebaseJson: firebaseJSON, firebaseRC + }); +} + +/** + * Write new default project to .firebaserc + */ +async function updateFirebaseRC(alias: string, projectId: string) { + if (currentOptions.cwd) { + firebaseRC = { + ...firebaseRC, + projects: { + default: firebaseRC?.projects?.default || "", + ...(firebaseRC?.projects || {}), + [alias]: projectId, + }, + }; + writeFirebaseRCFile(`${currentOptions.cwd}/.firebaserc`, firebaseRC); + updateOptions(extensionContext, firebaseJSON, firebaseRC); + } +} + +/** + * Set up a FileSystemWatcher for .firebaserc and firebase.json Also un-watch and re-watch when the + * configuration for where in the workspace the .firebaserc and firebase.json are. + */ +function setupFirebaseJsonAndRcFileSystemWatcher( + broker: ExtensionBrokerImpl +): vscode.Disposable { + // Create a new watcher + let watcher = newWatcher(); + + // Return a disposable that tears down a watcher if it's active + return { + dispose() { + watcher && watcher.dispose(); + }, + }; + + // HelperFunction to create a new watcher + function newWatcher() { + if (!currentOptions.cwd) { + return null; + } + + let watcher = workspace.createFileSystemWatcher( + path.join(currentOptions.cwd, "{firebase.json,.firebaserc}") + ); + watcher.onDidChange(async () => { + readAndSendFirebaseConfigs(broker); + }); + return watcher; + } +} diff --git a/firebase-vscode/tsconfig.json b/firebase-vscode/tsconfig.json new file mode 100644 index 00000000000..1fff794824a --- /dev/null +++ b/firebase-vscode/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": false, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "typeRoots": ["node_modules/@types", "../src/types"], + "module": "ES2015", + "moduleResolution": "node", + "target": "ES2020", + "outDir": "dist", + "lib": ["ES2020"], + "jsx": "react", + "sourceMap": true, + "rootDirs": ["src", "../src", "common"], + "strict": false /* enable all strict type-checking options */ + }, + "include": ["src/**/*", "common/**/*"] +} diff --git a/firebase-vscode/webpack.common.js b/firebase-vscode/webpack.common.js new file mode 100644 index 00000000000..ca1e75761e2 --- /dev/null +++ b/firebase-vscode/webpack.common.js @@ -0,0 +1,199 @@ +//@ts-check + +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); +const fs = require("fs"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const CopyPlugin = require("copy-webpack-plugin"); +const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); + +/**@type {import('webpack').Configuration}*/ +const extensionConfig = { + name: "extension", + target: "node", // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.js.org/configuration/target/#target + + entry: "./src/extension.ts", // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ + output: { + // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ + path: path.resolve(__dirname, "dist"), + filename: "extension.js", + libraryTarget: "commonjs2", + devtoolModuleFilenameTemplate: "../[resource-path]", + }, + devtool: "source-map", + externals: { + vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ + }, + resolve: { + // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader + // mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules + mainFields: ["main", "module"], + extensions: [".ts", ".js"], + alias: { + // provides alternate implementation for node module and source files + "proxy-agent": path.resolve(__dirname, 'src/stubs/empty-class.js'), + "marked-terminal": path.resolve(__dirname, 'src/stubs/empty-class.js'), + // "ora": path.resolve(__dirname, 'src/stubs/empty-function.js'), + "commander": path.resolve(__dirname, 'src/stubs/empty-class.js'), + "inquirer": path.resolve(__dirname, 'src/stubs/inquirer-stub.js'), + // This is used for Github deploy to hosting - will need to restore + // or find another solution if we add that feature. + "libsodium-wrappers": path.resolve(__dirname, 'src/stubs/empty-class.js'), + "marked": path.resolve(__dirname, 'src/stubs/marked.js') + }, + fallback: { + // Webpack 5 no longer polyfills Node.js core modules automatically. + // see https://webpack.js.org/configuration/resolve/#resolvefallback + // for the list of Node.js core module polyfills. + }, + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: [/node_modules/], + use: [ + { + loader: "ts-loader", + }, + ], + }, + { + test: /\.ts$/, + loader: "string-replace-loader", + options: { + multiple: [ + { + search: /(\.|\.\.)[\.\/]+templates/g, + replace: "./templates", + }, + { + search: /(\.|\.\.)[\.\/]+schema/g, + replace: "./schema", + }, + { + search: /Configstore\(pkg\.name\)/g, + replace: "Configstore('firebase-tools')", + }, + // TODO(hsubox76): replace with something more robust + { + search: "childProcess.spawn(translatedCommand", + replace: "childProcess.spawn(escapedCommand" + } + ], + }, + }, + ], + }, + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: "../templates", + to: "./templates", + }, + { + from: "../schema", + to: "./schema", + } + ], + }) + ], + infrastructureLogging: { + level: "log", // enables logging required for problem matchers + }, +}; + +function makeWebConfig(entryName) { + return { + name: entryName, + mode: "none", // this leaves the source code as close as possible to the original (when packaging we set this to 'production') + entry: `./webviews/${entryName}.entry.tsx`, + output: { + // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ + path: path.resolve(__dirname, "dist"), + filename: `web-${entryName}.js`, + }, + resolve: { + extensions: [".ts", ".js", ".jsx", ".tsx"], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + exclude: /node_modules/, + use: ["ts-loader"], + }, + // SCSS + /** + * This generates d.ts files for the scss. See the + * "WaitForCssTypescriptPlugin" code below for the workaround required + * to prevent a race condition here. + */ + { + test: /\.scss$/, + use: [ + MiniCssExtractPlugin.loader, + { + loader: "@teamsupercell/typings-for-css-modules-loader", + options: { + banner: + "// autogenerated by typings-for-css-modules-loader. \n// Please do not change this file!", + }, + }, + { + loader: "css-loader", + options: { + modules: { + mode: "local", + localIdentName: "[local]-[hash:base64:5]", + exportLocalsConvention: "camelCaseOnly", + }, + url: false, + }, + }, + "postcss-loader", + "sass-loader", + ], + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: `web-${entryName}.css`, + }), + new ForkTsCheckerWebpackPlugin(), + new WaitForCssTypescriptPlugin(), + ], + devtool: "nosources-source-map", + }; +}; + +// Using the workaround for the typings-for-css-modules-loader race condition +// issue. It doesn't seem like you have to put any actual code into the hook, +// the fact that the hook runs at all seems to be enough delay for the scss.d.ts +// files to be generated. See: +// https://github.com/TeamSupercell/typings-for-css-modules-loader#typescript-does-not-find-the-typings +class WaitForCssTypescriptPlugin { + apply(compiler) { + const hooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(compiler); + + hooks.start.tap("WaitForCssTypescriptPlugin", (change) => { + console.log("Ran WaitForCssTypescriptPlugin"); + return change; + }); + } +} + +module.exports = [ + // web extensions is disabled for now. + // webExtensionConfig, + extensionConfig, + ...fs + .readdirSync("webviews") + .filter((filename) => filename.match(/\.entry\.tsx/)) + .map((filename) => filename.replace(/\.entry\.tsx/, "")) + .map((name) => makeWebConfig(name)), +]; diff --git a/firebase-vscode/webpack.dev.js b/firebase-vscode/webpack.dev.js new file mode 100644 index 00000000000..d6fb16eed92 --- /dev/null +++ b/firebase-vscode/webpack.dev.js @@ -0,0 +1,6 @@ +const { merge } = require("webpack-merge"); +const common = require("./webpack.common.js"); + +module.exports = common.map(config => merge(config, { + mode: "development" +})); diff --git a/firebase-vscode/webpack.prod.js b/firebase-vscode/webpack.prod.js new file mode 100644 index 00000000000..e4bb4e4b10f --- /dev/null +++ b/firebase-vscode/webpack.prod.js @@ -0,0 +1,7 @@ +const { merge } = require("webpack-merge"); +const common = require("./webpack.common.js"); + +module.exports = common.map(config => merge(config, { + mode: "production" +})); + diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx new file mode 100644 index 00000000000..fd6b3f59841 --- /dev/null +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -0,0 +1,161 @@ +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; +import React, { useEffect, useState } from "react"; +import { Spacer } from "./components/ui/Spacer"; +import { Body } from "./components/ui/Text"; +import { broker } from "./globals/html-broker"; +import { User } from "../../src/types/auth"; +import { PanelSection } from "./components/ui/PanelSection"; +import { AccountSection } from "./components/AccountSection"; +import { ProjectSection } from "./components/ProjectSection"; +import { ServiceAccountUser } from "../common/types"; +import { DeployPanel } from "./components/DeployPanel"; +import { HostingState } from "./webview-types"; +import { ChannelWithId } from "./messaging/types"; +import { webLogger } from "./globals/web-logger"; + +export function SidebarApp() { + const [projectId, setProjectId] = useState(null); + const [hostingState, setHostingState] = useState(null); + const [env, setEnv] = useState<{ isMonospace: boolean }>(); + const [channels, setChannels] = useState(null); + const [userEmail, setUserEmail] = useState(null); + /** + * null - has not finished checking yet + * empty array - finished checking, no users logged in + * non-empty array - contains logged in users + */ + const [allUsers, setAllUsers] = useState | null>(null); + const [isHostingOnboarded, setHostingOnboarded] = useState(false); + + useEffect(() => { + webLogger.debug("loading SidebarApp component"); + broker.send("getEnv"); + broker.send("getUsers"); + broker.send("getFirebaseJson"); + broker.send("getSelectedProject"); + broker.send("getChannels"); + + broker.on("notifyEnv", ({ env }) => { + webLogger.debug("notifyEnv()"); + setEnv(env); + }); + + broker.on("notifyChannels", ({ channels }) => { + webLogger.debug("notifyChannels()"); + setChannels(channels); + }); + + broker.on("notifyFirebaseConfig", ({ firebaseJson, firebaseRC }) => { + webLogger.debug("got firebase hosting", JSON.stringify(firebaseJson?.hosting)); + if (firebaseJson?.hosting) { + webLogger.debug("Detected hosting setup"); + setHostingOnboarded(true); + broker.send("showMessage", { + msg: "Auto-detected hosting setup in this folder", + }); + } else { + setHostingOnboarded(false); + } + + if (firebaseRC?.projects?.default) { + webLogger.debug("Detected project setup from existing firebaserc"); + setProjectId(firebaseRC.projects.default); + } else { + setProjectId(null); + } + }); + + broker.on("notifyUsers", ({ users }) => { + webLogger.debug("notifyUsers()"); + setAllUsers(users); + }); + + broker.on("notifyProjectChanged", ({ projectId }) => { + webLogger.debug("Project selected", projectId); + setProjectId(projectId); + }); + + broker.on("notifyUserChanged", ({ email }) => { + webLogger.debug("notifyUserChanged:", email); + setUserEmail(email); + }); + + broker.on("notifyHostingFolderReady", ({ projectId, folderPath }) => { + webLogger.debug(`notifyHostingFolderReady: ${projectId}, ${folderPath}`); + setHostingOnboarded(true); + }); + + broker.on("notifyHostingDeploy", ({ success }) => { + webLogger.debug(`notifyHostingDeploy: ${success}`); + setHostingState("deployed"); + }); + }, []); + + function setupHosting() { + broker.send("selectAndInitHostingFolder", { + projectId, + email: userEmail!, // Safe to assume user email is already there + singleAppSupport: true, + }); + }; + + const accountSection = ( + + ); + // Just render the account section loading view if it doesn't know user state + if (allUsers === null) { + return ( + <> + + {accountSection} + + ); + } + + return ( + <> + + {accountSection} + {!!userEmail && ( + + )} + {isHostingOnboarded && !!userEmail && !!projectId && ( + + )} + + {!isHostingOnboarded && !!userEmail && !!projectId && ( + { + setupHosting(); + }} + /> + )} + + ); +} + +function InitFirebasePanel({ onHostingInit }: { onHostingInit: Function }) { + return ( + + Choose a path below to get started + + onHostingInit()}> + Host your web app + + + Free web hosting with a world-class CDN for peak performance + + + ); +} diff --git a/firebase-vscode/webviews/components/AccountSection.scss b/firebase-vscode/webviews/components/AccountSection.scss new file mode 100644 index 00000000000..55f8281f40c --- /dev/null +++ b/firebase-vscode/webviews/components/AccountSection.scss @@ -0,0 +1,20 @@ +.account-row { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + position: relative; + + &-label { + display: flex; + align-items: center; + } + + &-icon { + margin-right: 8px; + } + + &-project { + display: flex; + flex-direction: column; + } +} diff --git a/firebase-vscode/webviews/components/AccountSection.tsx b/firebase-vscode/webviews/components/AccountSection.tsx new file mode 100644 index 00000000000..6b92a4a8d95 --- /dev/null +++ b/firebase-vscode/webviews/components/AccountSection.tsx @@ -0,0 +1,131 @@ +import { + VSCodeLink, + VSCodeDivider, + VSCodeProgressRing, +} from "@vscode/webview-ui-toolkit/react"; +import React, { ReactElement, useState } from "react"; +import { broker } from "../globals/html-broker"; +import { Icon } from "./ui/Icon"; +import { IconButton } from "./ui/IconButton"; +import { PopupMenu, MenuItem } from "./ui/popup-menu/PopupMenu"; +import { Label } from "./ui/Text"; +import styles from "./AccountSection.scss"; +import { ServiceAccountUser } from "../../common/types"; +import { User } from "../../../src/types/auth"; + +export function AccountSection({ + userEmail, + allUsers, + isMonospace, +}: { + userEmail: string | null; + allUsers: Array | null; + isMonospace: boolean; +}) { + const [userDropdownVisible, toggleUserDropdown] = useState(false); + const usersLoaded = !!allUsers; + // Default: initial users check hasn't completed + let currentUserElement: ReactElement | string = "checking login"; + if (usersLoaded && !allUsers.length) { + // Users loaded but no user was found + if (isMonospace) { + // Monospace: this is an error, should have found a workspace + // service account + currentUserElement = "unable to find workspace service account"; + } else { + // VS Code: prompt user to log in with Google account + currentUserElement = ( broker.send("addUser")}> + Sign in with Google + ); + } + } else if (usersLoaded && allUsers.length > 0) { + // Users loaded, at least one user was found + if (isMonospace && userEmail === 'service_account') { + // TODO(hsubox76): Figure out correct wording + currentUserElement = 'workspace logged in'; + } else { + currentUserElement = userEmail; + } + } + return ( +
+ + {!usersLoaded && ( + + )} + {usersLoaded && allUsers.length > 0 && ( + <> + toggleUserDropdown(!userDropdownVisible)} + /> + {userDropdownVisible ? ( + toggleUserDropdown(false)} + /> + ) : null} + + )} +
+ ); +} + +// TODO(roman): Convert to a better menu +function UserSelectionMenu({ + userEmail, + allUsers, + onClose, +}: { + userEmail: string; + allUsers: Array; + onClose: Function; +}) { + return ( + <> + + { + broker.send("addUser"); + onClose(); + }} + > + Sign in another user... + + + {allUsers.map((user) => ( + { + broker.send("requestChangeUser", {user}); + onClose(); + }} + key={user.email} + > + {user.email} + + ))} + + { + // You can't log out of a service account + userEmail !== "service_account" && ( + { + broker.send("logout", {email: userEmail}); + onClose(); + }} + > + Sign Out {userEmail} + + ) + } + + + ); +} diff --git a/firebase-vscode/webviews/components/DeployPanel.tsx b/firebase-vscode/webviews/components/DeployPanel.tsx new file mode 100644 index 00000000000..21dfad5a806 --- /dev/null +++ b/firebase-vscode/webviews/components/DeployPanel.tsx @@ -0,0 +1,186 @@ +import { + VSCodeButton, + VSCodeDivider, + VSCodeProgressRing, + VSCodeLink, + VSCodeRadio, + VSCodeRadioGroup, + VSCodeTextField, +} from "@vscode/webview-ui-toolkit/react"; +import cn from "classnames"; +import React, { useEffect, useState } from "react"; +import { Icon } from "./ui/Icon"; +import { Spacer } from "./ui/Spacer"; +import { Label } from "./ui/Text"; +import { broker } from "../globals/html-broker"; +import styles from "../sidebar.entry.scss"; +import { PanelSection } from "./ui/PanelSection"; +import { HostingState } from "../webview-types"; +import { ChannelWithId } from "../messaging/types"; +import { ExternalLink } from "./ui/ExternalLink"; + +interface DeployInfo { + date: string; + channelId: string; +} + +export function DeployPanel({ + hostingState, + setHostingState, + projectId, + channels, +}: { + hostingState: HostingState; + setHostingState: (hostingState: HostingState) => void; + projectId: string; + channels: ChannelWithId[]; +}) { + const [deployTarget, setDeployTarget] = useState("live"); + const [newPreviewChannel, setNewPreviewChannel] = useState(""); + const [deployedInfo, setDeployedInfo] = useState(null); + + useEffect(() => { + if (hostingState === "deployed") { + setDeployedInfo({ + date: new Date().toLocaleDateString(), + channelId: deployTarget === "new" ? newPreviewChannel : deployTarget, + }); + setNewPreviewChannel(""); + } + }, [hostingState]); + + if (!channels || channels.length === 0) { + return ( + <> + + + + + + + + ); + } + + channels.sort((a, b) => (a.id === "live" ? -1 : 0)); + + const channelOptions = channels.map((channel) => ( + setDeployTarget(e.target.value)} + > + {channel.id} + + )); + let siteLink = null; + + const existingChannel = channels.find( + (channel) => channel.id === deployTarget + ); + + if (existingChannel) { + siteLink = ( + + ); + } + + return ( + <> + + + + <> + { + setHostingState("deploying"); + broker.send("hostingDeploy", { + target: + deployTarget === "new" ? newPreviewChannel : deployTarget, + }); + }} + > + Deploy to channel:{" "} + {deployTarget === "new" ? newPreviewChannel : deployTarget} + + setDeployTarget(e.target.value)} + orientation="vertical" + > + {channelOptions} + + new (type new id below) + + + { + setNewPreviewChannel(e.target.value); + }} + value={newPreviewChannel} + placeholder="new preview channel id" + > + + {hostingState !== "deploying" && ( + <> + +
+ +
+ + )} + {hostingState === "deploying" && ( + <> + +
+ + +
+ + )} + + {siteLink && ()} + +
+ + ); +} diff --git a/firebase-vscode/webviews/components/ProjectSection.tsx b/firebase-vscode/webviews/components/ProjectSection.tsx new file mode 100644 index 00000000000..108b8d9641b --- /dev/null +++ b/firebase-vscode/webviews/components/ProjectSection.tsx @@ -0,0 +1,79 @@ +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; +import { broker } from "../globals/html-broker"; +import { IconButton } from "./ui/IconButton"; +import { Icon } from "./ui/Icon"; +import { Label } from "./ui/Text"; +import React from "react"; +import styles from "./AccountSection.scss"; +import { ExternalLink } from "./ui/ExternalLink"; + +export function ProjectSection({ + userEmail, + projectId, +}: { + userEmail: string | null; + projectId: string | null | undefined; +}) { + return ( +
+ + {!!projectId && ( + initProjectSelection(userEmail)} + /> + )} +
+ ); +} + +export function initProjectSelection(userEmail: string | null) { + if (userEmail) { + broker.send("selectProject", { email: userEmail }); + } else { + broker.send("showMessage", { + msg: "Not logged in", + options: { + modal: true, + detail: `Log in to allow project selection. Click "Sign in with Google" in the sidebar.`, + }, + }); + return; + } +} + +export function ConnectProject({ userEmail }: { userEmail: string | null }) { + return ( + <> + initProjectSelection(userEmail)}> + Connect a Firebase project + + + ); +} + +export function ProjectInfo({ projectId }: { projectId: string }) { + return ( + <> + {projectId} + + + ); +} diff --git a/firebase-vscode/webviews/components/ui/ExternalLink.tsx b/firebase-vscode/webviews/components/ui/ExternalLink.tsx new file mode 100644 index 00000000000..085794a9587 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/ExternalLink.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; +import { broker } from "../../globals/html-broker"; + +export function ExternalLink({ href, text }: { href: string; text: string }) { + return ( + broker.send("openLink", { href })}> + {text} + + ); +} diff --git a/firebase-vscode/webviews/components/ui/Icon.scss b/firebase-vscode/webviews/components/ui/Icon.scss new file mode 100644 index 00000000000..192935d8723 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/Icon.scss @@ -0,0 +1,21 @@ +.monicon { + font-family: "Monicons"; + font-weight: normal; + font-style: normal; + font-size: 16px; + display: inline-block; + width: 1em; + height: 1em; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + font-feature-settings: "liga"; + speak: none; + text-decoration: inherit; +} diff --git a/firebase-vscode/webviews/components/ui/Icon.tsx b/firebase-vscode/webviews/components/ui/Icon.tsx new file mode 100644 index 00000000000..fbf8e0734e2 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/Icon.tsx @@ -0,0 +1,441 @@ +import cn from "classnames"; +import React, { HTMLAttributes, PropsWithChildren } from "react"; +import styles from "./Icon.scss"; + +export type IconName = CodiconName | MoniconName; + +export type MoniconName = "mono-firebase"; + +export type CodiconName = + | "account" + | "activate-breakpoints" + | "add" + | "archive" + | "arrow-both" + | "arrow-circle-down" + | "arrow-circle-left" + | "arrow-circle-right" + | "arrow-circle-up" + | "arrow-down" + | "arrow-left" + | "arrow-right" + | "arrow-small-down" + | "arrow-small-left" + | "arrow-small-right" + | "arrow-small-up" + | "arrow-swap" + | "arrow-up" + | "azure-devops" + | "azure" + | "beaker-stop" + | "beaker" + | "bell-dot" + | "bell" + | "bold" + | "book" + | "bookmark" + | "bracket-dot" + | "bracket-error" + | "briefcase" + | "broadcast" + | "browser" + | "bug" + | "calendar" + | "call-incoming" + | "call-outgoing" + | "case-sensitive" + | "check-all" + | "check" + | "checklist" + | "chevron-down" + | "chevron-left" + | "chevron-right" + | "chevron-up" + | "chrome-close" + | "chrome-maximize" + | "chrome-minimize" + | "chrome-restore" + | "circle-filled" + | "circle-large-filled" + | "circle-large-outline" + | "circle-outline" + | "circle-slash" + | "circuit-board" + | "clear-all" + | "clippy" + | "close-all" + | "close" + | "cloud-download" + | "cloud-upload" + | "cloud" + | "code" + | "collapse-all" + | "color-mode" + | "combine" + | "comment-discussion" + | "comment" + | "compass-active" + | "compass-dot" + | "compass" + | "copy" + | "credit-card" + | "dash" + | "dashboard" + | "database" + | "debug-all" + | "debug-alt-small" + | "debug-alt" + | "debug-breakpoint-conditional-unverified" + | "debug-breakpoint-conditional" + | "debug-breakpoint-data-unverified" + | "debug-breakpoint-data" + | "debug-breakpoint-function-unverified" + | "debug-breakpoint-function" + | "debug-breakpoint-log-unverified" + | "debug-breakpoint-log" + | "debug-breakpoint-unsupported" + | "debug-console" + | "debug-continue-small" + | "debug-continue" + | "debug-coverage" + | "debug-disconnect" + | "debug-line-by-line" + | "debug-pause" + | "debug-rerun" + | "debug-restart-frame" + | "debug-restart" + | "debug-reverse-continue" + | "debug-stackframe-active" + | "debug-stackframe-dot" + | "debug-stackframe" + | "debug-start" + | "debug-step-back" + | "debug-step-into" + | "debug-step-out" + | "debug-step-over" + | "debug-stop" + | "debug" + | "desktop-download" + | "device-camera-video" + | "device-camera" + | "device-mobile" + | "diff-added" + | "diff-ignored" + | "diff-modified" + | "diff-removed" + | "diff-renamed" + | "diff" + | "discard" + | "edit" + | "editor-layout" + | "ellipsis" + | "empty-window" + | "error-small" + | "error" + | "exclude" + | "expand-all" + | "export" + | "extensions" + | "eye-closed" + | "eye" + | "feedback" + | "file-binary" + | "file-code" + | "file-media" + | "file-pdf" + | "file-submodule" + | "file-symlink-directory" + | "file-symlink-file" + | "file-zip" + | "file" + | "files" + | "filter-filled" + | "filter" + | "flame" + | "fold-down" + | "fold-up" + | "fold" + | "folder-active" + | "folder-library" + | "folder-opened" + | "folder" + | "gear" + | "gift" + | "gist-secret" + | "git-commit" + | "git-compare" + | "git-merge" + | "git-pull-request-closed" + | "git-pull-request-create" + | "git-pull-request-draft" + | "git-pull-request" + | "github-action" + | "github-alt" + | "github-inverted" + | "github" + | "globe" + | "go-to-file" + | "grabber" + | "graph-left" + | "graph-line" + | "graph-scatter" + | "graph" + | "gripper" + | "group-by-ref-type" + | "heart" + | "history" + | "home" + | "horizontal-rule" + | "hubot" + | "inbox" + | "indent" + | "info" + | "inspect" + | "issue-draft" + | "issue-reopened" + | "issues" + | "italic" + | "jersey" + | "json" + | "kebab-vertical" + | "key" + | "law" + | "layers-active" + | "layers-dot" + | "layers" + | "layout-activitybar-left" + | "layout-activitybar-right" + | "layout-centered" + | "layout-menubar" + | "layout-panel-center" + | "layout-panel-justify" + | "layout-panel-left" + | "layout-panel-off" + | "layout-panel-right" + | "layout-panel" + | "layout-sidebar-left-off" + | "layout-sidebar-left" + | "layout-sidebar-right-off" + | "layout-sidebar-right" + | "layout-statusbar" + | "layout" + | "library" + | "lightbulb-autofix" + | "lightbulb" + | "link-external" + | "link" + | "list-filter" + | "list-flat" + | "list-ordered" + | "list-selection" + | "list-tree" + | "list-unordered" + | "live-share" + | "loading" + | "location" + | "lock-small" + | "lock" + | "magnet" + | "mail-read" + | "mail" + | "markdown" + | "megaphone" + | "mention" + | "menu" + | "merge" + | "milestone" + | "mirror" + | "mortar-board" + | "move" + | "multiple-windows" + | "mute" + | "new-file" + | "new-folder" + | "newline" + | "no-newline" + | "note" + | "notebook-template" + | "notebook" + | "octoface" + | "open-preview" + | "organization" + | "output" + | "package" + | "paintcan" + | "pass-filled" + | "pass" + | "person-add" + | "person" + | "pie-chart" + | "pin" + | "pinned-dirty" + | "pinned" + | "play-circle" + | "play" + | "plug" + | "preserve-case" + | "preview" + | "primitive-square" + | "project" + | "pulse" + | "question" + | "quote" + | "radio-tower" + | "reactions" + | "record-keys" + | "record-small" + | "record" + | "redo" + | "references" + | "refresh" + | "regex" + | "remote-explorer" + | "remote" + | "remove" + | "replace-all" + | "replace" + | "reply" + | "repo-clone" + | "repo-force-push" + | "repo-forked" + | "repo-pull" + | "repo-push" + | "repo" + | "report" + | "request-changes" + | "rocket" + | "root-folder-opened" + | "root-folder" + | "rss" + | "ruby" + | "run-above" + | "run-all" + | "run-below" + | "run-errors" + | "save-all" + | "save-as" + | "save" + | "screen-full" + | "screen-normal" + | "search-stop" + | "search" + | "server-environment" + | "server-process" + | "server" + | "settings-gear" + | "settings" + | "shield" + | "sign-in" + | "sign-out" + | "smiley" + | "sort-precedence" + | "source-control" + | "split-horizontal" + | "split-vertical" + | "squirrel" + | "star-empty" + | "star-full" + | "star-half" + | "stop-circle" + | "symbol-array" + | "symbol-boolean" + | "symbol-class" + | "symbol-color" + | "symbol-constant" + | "symbol-enum-member" + | "symbol-enum" + | "symbol-event" + | "symbol-field" + | "symbol-file" + | "symbol-interface" + | "symbol-key" + | "symbol-keyword" + | "symbol-method" + | "symbol-misc" + | "symbol-namespace" + | "symbol-numeric" + | "symbol-operator" + | "symbol-parameter" + | "symbol-property" + | "symbol-ruler" + | "symbol-snippet" + | "symbol-string" + | "symbol-structure" + | "symbol-variable" + | "sync-ignored" + | "sync" + | "table" + | "tag" + | "target" + | "tasklist" + | "telescope" + | "terminal-bash" + | "terminal-cmd" + | "terminal-debian" + | "terminal-linux" + | "terminal-powershell" + | "terminal-tmux" + | "terminal-ubuntu" + | "terminal" + | "text-size" + | "three-bars" + | "thumbsdown" + | "thumbsup" + | "tools" + | "trash" + | "triangle-down" + | "triangle-left" + | "triangle-right" + | "triangle-up" + | "twitter" + | "type-hierarchy-sub" + | "type-hierarchy-super" + | "type-hierarchy" + | "unfold" + | "ungroup-by-ref-type" + | "unlock" + | "unmute" + | "unverified" + | "variable-group" + | "verified-filled" + | "verified" + | "versions" + | "vm-active" + | "vm-connect" + | "vm-outline" + | "vm-running" + | "vm" + | "wand" + | "warning" + | "watch" + | "whitespace" + | "whole-word" + | "window" + | "word-wrap" + | "workspace-trusted" + | "workspace-unknown" + | "workspace-untrusted" + | "zoom-in" + | "zoom-out"; + +type IconProps = PropsWithChildren< + T & + HTMLAttributes & { + icon: IconName; + } +>; + +export const Icon: React.FC> = ({ + icon, + className, + ...props +}) => { + let mono = icon.startsWith("mono-"); + return mono ? ( + + {icon} + + ) : ( +
+ ); +}; diff --git a/firebase-vscode/webviews/components/ui/IconButton.tsx b/firebase-vscode/webviews/components/ui/IconButton.tsx new file mode 100644 index 00000000000..50c2c9ebff7 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/IconButton.tsx @@ -0,0 +1,30 @@ +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; +import React, { HTMLAttributes, PropsWithChildren } from "react"; +import { Icon, IconName } from "./Icon"; + +type TextProps = PropsWithChildren< + T & + HTMLAttributes & { + icon: IconName; + tooltip: string; + } +>; + +export const IconButton: React.FC> = ({ + icon, + tooltip, + className, + ...props +}) => { + return ( + + + + ); +}; diff --git a/firebase-vscode/webviews/components/ui/PanelSection.scss b/firebase-vscode/webviews/components/ui/PanelSection.scss new file mode 100644 index 00000000000..046c5983327 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/PanelSection.scss @@ -0,0 +1,20 @@ +.panelExpando { + appearance: none; + background-color: transparent; + display: flex; + align-items: center; + line-height: 16px; + padding: 0; + border: 0; + cursor: pointer; + gap: 4px; + color: var(--vscode-descriptionForeground); + + .panelExpandoIcon { + transition: transform 0.1s ease; + } + + &:not(.isExpanded) .panelExpandoIcon { + transform: rotate(-90deg); + } +} diff --git a/firebase-vscode/webviews/components/ui/PanelSection.tsx b/firebase-vscode/webviews/components/ui/PanelSection.tsx new file mode 100644 index 00000000000..67ef6932281 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/PanelSection.tsx @@ -0,0 +1,42 @@ +import { VSCodeDivider } from "@vscode/webview-ui-toolkit/react"; +import React, { ReactNode, useState } from "react"; +import { Icon } from "./Icon"; +import { Spacer } from "./Spacer"; +import { Heading } from "./Text"; +import cn from "classnames"; +import styles from "./PanelSection.scss"; + +export function PanelSection({ + title, + children, + isLast, +}: { + title?: ReactNode; + children: ReactNode; + isLast?: boolean; +}) { + let [isExpanded, setExpanded] = useState(true); + + return ( + <> + {title && ( + + )} + {isExpanded && ( + <> + {title && } + {children} + + {!isLast && } + + )} + + ); +} diff --git a/firebase-vscode/webviews/components/ui/Spacer.scss b/firebase-vscode/webviews/components/ui/Spacer.scss new file mode 100644 index 00000000000..cee24494810 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/Spacer.scss @@ -0,0 +1,23 @@ +.spacerxsmall { + height: var(--space-xsmall); +} + +.spacersmall { + height: var(--space-small); +} + +.spacermedium { + height: var(--space-medium); +} + +.spacerlarge { + height: var(--space-large); +} + +.spacerxlarge { + height: var(--space-xlarge); +} + +.spacerxxlarge { + height: var(--space-xxlarge); +} diff --git a/firebase-vscode/webviews/components/ui/Spacer.tsx b/firebase-vscode/webviews/components/ui/Spacer.tsx new file mode 100644 index 00000000000..4b05f1c1ae1 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/Spacer.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import styles from "./Spacer.scss"; + +type SpacerSize = + | "xsmall" + | "small" + | "medium" + | "large" + | "xlarge" + | "xxlarge"; + +export function Spacer({ size = "large" }: { size: SpacerSize }) { + return
; +} diff --git a/firebase-vscode/webviews/components/ui/Text.scss b/firebase-vscode/webviews/components/ui/Text.scss new file mode 100644 index 00000000000..ffc736c76cb --- /dev/null +++ b/firebase-vscode/webviews/components/ui/Text.scss @@ -0,0 +1,94 @@ +h1, +h2, +h3, +h4, +h5, +h6, +.text { + margin: 0; + padding: 0; + font-weight: normal; + user-select: none; +} + +p, +ol, +ul { + margin: 0; + padding: 0; + line-height: var(--vscode-line-height); +} + +h1 { + font-size: 26px; + line-height: var(--vscode-line-height); + font-weight: 600; // semibold + margin: 0; +} + +h2 { + font-size: 16px; + line-height: var(--vscode-line-height); + font-weight: 500; // medium +} + +h3 { + font-size: 13px; + line-height: var(--vscode-line-height); + font-weight: 800; // heavy +} + +h4 { + font-size: 11px; + line-height: var(--vscode-line-height); + font-weight: 700; // bold + text-transform: uppercase; +} + +h5 { + font-size: 11px; + line-height: var(--vscode-line-height); + font-weight: 500; // medium + text-transform: uppercase; +} + +h6 { + font-size: 11px; + line-height: var(--vscode-line-height); + font-weight: 800; // heavy +} + +.b1 { + font-size: inherit; + line-height: var(--vscode-line-height); +} + +.b2 { + font-size: 12px; + line-height: var(--vscode-line-height); +} + +.l1 { + font-size: 14px; + line-height: var(--vscode-line-height); + font-weight: 500; // medium +} + +.l2 { + font-size: 12px; + font-weight: 700; // bold +} + +.l3 { + font-size: 11px; + font-weight: 500; // medium +} + +.l4 { + font-size: 9px; + font-weight: 700; // bold +} + +.color-secondary { + color: var(--vscode-descriptionForeground); +} diff --git a/firebase-vscode/webviews/components/ui/Text.tsx b/firebase-vscode/webviews/components/ui/Text.tsx new file mode 100644 index 00000000000..15826cd03fd --- /dev/null +++ b/firebase-vscode/webviews/components/ui/Text.tsx @@ -0,0 +1,58 @@ +import cn from "classnames"; +import React, { HTMLAttributes, PropsWithChildren } from "react"; +import styles from "./Text.scss"; + +type TextProps = PropsWithChildren< + T & + HTMLAttributes & { + secondary?: boolean; + as?: any; + } +>; + +const Text: React.FC> = ({ + secondary, + as: Component = "div", + className, + ...props +}) => { + return ( + + ); +}; + +export const Heading: React.FC> = ({ + level = 1, + ...props +}) => { + return ; +}; + +export const Label: React.FC> = ({ + level = 1, + className, + ...props +}) => { + return ( + + ); +}; + +export const Body: React.FC> = ({ + level = 1, + className, + ...props +}) => { + return ( + + ); +}; diff --git a/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.scss b/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.scss new file mode 100644 index 00000000000..d6d13894c3a --- /dev/null +++ b/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.scss @@ -0,0 +1,57 @@ +.menu { + position: absolute; + right: 0; + background-color: var(--vscode-sideBar-background); + padding: 4px 0; + margin: 0; + border-radius: 3px; + box-shadow: 0 0 0 1px var(--divider-background, black), + 0 4px 12px var(--vscode-widget-shadow); + color: var(--vscode-foreground); + z-index: 2; + list-style: none; + display: flex; + flex-direction: column; + align-items: stretch; +} + +.scrim { + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + cursor: default; + z-index: 1; +} + +.item { + width: 100%; + text-align: left; + background-color: transparent; + appearance: none; + border: 0; + cursor: pointer; + font-family: inherit; + color: var(--color-ink); + padding: 4px 16px; + display: flex; + align-items: center; + + :global(.material-icons) { + margin-right: 12px; + color: var(--color-ink-2); + } + + &[disabled] { + cursor: not-allowed; + color: var(--color-ink-disabled); + } + + &:not([disabled]):hover { + background-color: var(--vscode-list-hoverBackground); + } + &:not([disabled]):active { + background-color: var(--vscode-list-activeSelectionBackground); + } +} diff --git a/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.tsx b/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.tsx new file mode 100644 index 00000000000..a1a6e7b3aa4 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.tsx @@ -0,0 +1,57 @@ +import cn from "classnames"; +import React, { FC, HTMLAttributes, PropsWithChildren } from "react"; +import styles from "./PopupMenu.scss"; + +// TODO(hsubox76): replace this with a real, accessible Menu component + +type PopupMenuProps = PropsWithChildren< + T & + HTMLAttributes & { + show?: boolean; + onClose: Function; + } +>; + +export const PopupMenu: FC> = ({ + children, + className, + show, + onClose, +}) => { + return ( + <> + {show && ( + <> +
onClose()} /> +
    + {children} +
+ + )} + + ); +}; + +type MenuItemProps = PropsWithChildren< + T & + HTMLAttributes & { + onClick: Function; + } +>; + +export const MenuItem: FC> = ({ + className, + onClick, + children, +}) => { + return ( +
  • + +
  • + ); +}; diff --git a/firebase-vscode/webviews/globals/html-broker.ts b/firebase-vscode/webviews/globals/html-broker.ts new file mode 100644 index 00000000000..9f36261b0f6 --- /dev/null +++ b/firebase-vscode/webviews/globals/html-broker.ts @@ -0,0 +1,22 @@ +import { Broker, createBroker } from "../../common/messaging/broker"; +import { + ExtensionToWebviewParamsMap, + WebviewToExtensionParamsMap, +} from "../../common/messaging/protocol"; + +export class HtmlBroker extends Broker< + WebviewToExtensionParamsMap, ExtensionToWebviewParamsMap, {}> { + constructor(readonly vscode: any) { + super(); + window.addEventListener("message", event => this.executeListeners(event.data)); + } + + sendMessage(command: keyof WebviewToExtensionParamsMap, data: WebviewToExtensionParamsMap[keyof WebviewToExtensionParamsMap]): void { + this.vscode.postMessage({ command, data }); + } +} + +const vscode = (window as any)["acquireVsCodeApi"](); +export const broker = createBroker( + new HtmlBroker(vscode) +); diff --git a/firebase-vscode/webviews/globals/index.scss b/firebase-vscode/webviews/globals/index.scss new file mode 100644 index 00000000000..f231687635b --- /dev/null +++ b/firebase-vscode/webviews/globals/index.scss @@ -0,0 +1,12 @@ +@import "./tokens.scss"; +@import "./vscode.scss"; + +body { + background-color: transparent; + cursor: default; +} + +:global #root { + display: flex; + flex-direction: column; +} diff --git a/firebase-vscode/webviews/globals/tokens.scss b/firebase-vscode/webviews/globals/tokens.scss new file mode 100644 index 00000000000..0badec1f079 --- /dev/null +++ b/firebase-vscode/webviews/globals/tokens.scss @@ -0,0 +1,8 @@ +:root { + --space-xsmall: 2px; + --space-small: 4px; + --space-medium: 8px; + --space-large: 12px; + --space-xlarge: 16px; + --space-xxlarge: 24px; +} diff --git a/firebase-vscode/webviews/globals/vscode.scss b/firebase-vscode/webviews/globals/vscode.scss new file mode 100644 index 00000000000..7533bed4747 --- /dev/null +++ b/firebase-vscode/webviews/globals/vscode.scss @@ -0,0 +1,38 @@ +:root { + --container-padding: 20px; + --vscode-line-height: 140%; +} + +body { + margin: 0; + padding: 0 var(--container-padding); + color: var(--vscode-foreground); + font-size: var(--vscode-font-size); + line-height: var(--vscode-line-height); + font-weight: var(--vscode-font-weight); + font-family: var(--vscode-font-family); + background-color: var(--vscode-editor-background); +} + +ol, +ul { + padding-left: var(--container-padding); +} + +*:focus { + outline-color: var(--vscode-focusBorder) !important; +} + +a { + color: var(--vscode-textLink-foreground); +} + +a:hover, +a:active { + color: var(--vscode-textLink-activeForeground); +} + +code { + font-size: var(--vscode-editor-font-size); + font-family: var(--vscode-editor-font-family); +} diff --git a/firebase-vscode/webviews/globals/web-logger.ts b/firebase-vscode/webviews/globals/web-logger.ts new file mode 100644 index 00000000000..b6468e9b2b8 --- /dev/null +++ b/firebase-vscode/webviews/globals/web-logger.ts @@ -0,0 +1,18 @@ +import { broker } from "./html-broker"; + +type Level = 'debug' | 'info' | 'error'; +const levels: Level[] = ['debug', 'info', 'error']; + +type WebLogger = Record void>; + +const tempObject = {}; + +for (const level of levels) { + tempObject[level] = (...args: string[]) => + broker.send('writeLog', { level, args }); +} + +// Recast it now that it's populated. +const webLogger = tempObject as WebLogger; + +export { webLogger }; diff --git a/firebase-vscode/webviews/sidebar.entry.scss b/firebase-vscode/webviews/sidebar.entry.scss new file mode 100644 index 00000000000..07196d8bf56 --- /dev/null +++ b/firebase-vscode/webviews/sidebar.entry.scss @@ -0,0 +1,54 @@ +@import "./globals/index.scss"; + +a:not(:hover):not(:focus) { + text-decoration: none; +} + +.fullWidth { + width: 100%; +} + +.integrationStatus { + padding-left: 28px; + position: relative; + + &-label { + line-height: 16px; + } + + &-icon { + position: absolute; + left: 0; + top: 0; + } + + &-loading { + width: 16px; + height: 16px; + } +} + +.hosting-row { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + position: relative; + + &-label { + display: flex; + align-items: center; + } + + &-icon { + margin-right: 8px; + } + + &-project { + display: flex; + flex-direction: column; + } + + &-test { + display: flex; + } +} diff --git a/firebase-vscode/webviews/sidebar.entry.tsx b/firebase-vscode/webviews/sidebar.entry.tsx new file mode 100644 index 00000000000..40d5c965ff0 --- /dev/null +++ b/firebase-vscode/webviews/sidebar.entry.tsx @@ -0,0 +1,6 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import { SidebarApp } from "./SidebarApp"; + +const root = createRoot(document.getElementById("root")!); +root.render(); diff --git a/firebase-vscode/webviews/tsconfig.json b/firebase-vscode/webviews/tsconfig.json new file mode 100644 index 00000000000..eb59f3c1eac --- /dev/null +++ b/firebase-vscode/webviews/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "None", + "moduleResolution": "Node", + "target": "ES2020", + "lib": ["ES2020", "DOM"], + "esModuleInterop": true, + "jsx": "react", + "sourceMap": true, + "rootDirs": ["./", "../../src", "../common"], + "strict": false /* enable all strict type-checking options */ + }, + "include": ["../webviews/**/*", "../common/**/*"] +} diff --git a/firebase-vscode/webviews/webview-types.ts b/firebase-vscode/webviews/webview-types.ts new file mode 100644 index 00000000000..ebe64febd7a --- /dev/null +++ b/firebase-vscode/webviews/webview-types.ts @@ -0,0 +1,2 @@ + +export type HostingState = null | "deployed" | "deploying"; diff --git a/src/monospace/index.ts b/src/monospace/index.ts new file mode 100644 index 00000000000..410676cfdc4 --- /dev/null +++ b/src/monospace/index.ts @@ -0,0 +1,155 @@ +import fetch from "node-fetch"; + +import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import { loadRC } from "../rc"; + +import type { + GetInitFirebaseResponse, + InitFirebaseResponse, + SetupMonospaceOptions, +} from "./interfaces"; + +const POLL_USER_RESPONSE_MILLIS = 5000; + +/** + * Integrate Firebase Plugin with Monospace’s service Account Authentication + * + * @return null if no project was authorized + * @return string if a project was authorized and isVSCE is true + * @return void if a project was authorized and isVSCE is falsy, creating + * `.firebaserc` with authorized project using the default alias + */ +export async function selectProjectInMonospace({ + projectRoot, + project, + isVSCE, +}: SetupMonospaceOptions): Promise { + const initFirebaseResponse = await initFirebase(project); + + if (initFirebaseResponse.success === false) { + throw new Error(String(initFirebaseResponse.error)); + } + + const { rid } = initFirebaseResponse; + + const authorizedProject = await pollAuthorizedProject(rid); + + if (!authorizedProject) return null; + + if (isVSCE) return authorizedProject; + + if (projectRoot) createFirebaseRc(projectRoot, authorizedProject); +} + +/** + * Since `initFirebase` pops up a dialog and waits for user response, it might + * take some time for the response to become available. Here we poll for user's + * response. + */ +async function pollAuthorizedProject(rid: string): Promise { + const getInitFirebaseRes = await getInitFirebaseResponse(rid); + + // If the user authorizes a project, `userResponse` will be available + if ("userResponse" in getInitFirebaseRes) { + if (getInitFirebaseRes.userResponse.success) { + return getInitFirebaseRes.userResponse.projectId; + } else { + return null; + } + } + + const { error } = getInitFirebaseRes; + + // Wait response: User hasn’t finished the interaction yet + if (error === "WAITING_FOR_RESPONSE") { + // wait and call back + await new Promise((res) => setTimeout(res, POLL_USER_RESPONSE_MILLIS)); + + // TODO: decide how long to ultimately wait before declaring + // that the user is never going to respond. + + return pollAuthorizedProject(rid); + } + + // FIXME: This is not being reached as the process exits before a new call is made + + // Error response: User canceled without authorizing any project + if (error === "USER_CANCELED") { + // The user hasn’t authorized any project. + // Display appropriate error message. + throw new FirebaseError("User canceled without authorizing any project"); + } + + throw new FirebaseError(`Unhandled /get-init-firebase-response error: ${error}`); +} + +/** + * Make call to init Firebase, get request id (rid) or error + */ +async function initFirebase(project?: string): Promise { + const port = getMonospaceDaemonPort(); + if (!port) throw new FirebaseError("Undefined MONOSPACE_DAEMON_PORT"); + + const initFirebaseURL = new URL(`http://localhost:${port}/init-firebase`); + + if (project) { + initFirebaseURL.searchParams.set("known_project", project); + } + + const initFirebaseRes = await fetch(initFirebaseURL.toString(), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + + const initFirebaseResponse = (await initFirebaseRes.json()) as InitFirebaseResponse; + + return initFirebaseResponse; +} + +/** + * Get response from the user - authorized project or error + */ +async function getInitFirebaseResponse(rid: string): Promise { + const port = getMonospaceDaemonPort(); + if (!port) throw new FirebaseError("Undefined MONOSPACE_DAEMON_PORT"); + + const getInitFirebaseRes = await fetch( + `http://localhost:${port}/get-init-firebase-response?rid=${rid}` + ); + + const getInitFirebaseJson = (await getInitFirebaseRes.json()) as GetInitFirebaseResponse; + + logger.debug(`/get-init-firebase-response?rid=${rid} response:`); + logger.debug(getInitFirebaseJson); + + return getInitFirebaseJson; +} + +/** + * Create a .firebaserc in the project's root with the authorized project + * as the default project + */ +function createFirebaseRc(projectDir: string, authorizedProject: string): boolean { + const firebaseRc = loadRC({ cwd: projectDir }); + + firebaseRc.addProjectAlias("default", authorizedProject); + + return firebaseRc.save(); +} + +/** + * Whether this is a Monospace environment + */ +export async function isMonospaceEnv(): Promise { + return Promise.resolve(Boolean(getMonospaceDaemonPort())); +} + +/** + * @return process.env.MONOSPACE_DAEMON_PORT + */ +function getMonospaceDaemonPort(): string | undefined { + return process.env.MONOSPACE_DAEMON_PORT; +} diff --git a/src/monospace/interfaces.ts b/src/monospace/interfaces.ts new file mode 100644 index 00000000000..d0bcd058d8b --- /dev/null +++ b/src/monospace/interfaces.ts @@ -0,0 +1,33 @@ +import type { Options } from "../options"; + +export type SetupMonospaceOptions = { + projectRoot: Options["projectRoot"]; + project: Options["project"]; + isVSCE: Options["isVSCE"]; +}; + +export type GetInitFirebaseResponse = + | { + success: true; + userResponse: { + success: true; + projectId: string; + }; + } + | { + success: true; + userResponse: { + success: false; + }; + } + | { + success: false; + error: "WAITING_FOR_RESPONSE" | "USER_CANCELED" | unknown; // TODO: define all errors + }; + +export type InitFirebaseResponse = + | { + success: true; + rid: string; + } + | { success: false; error: "NOT_INITIALIZED" | unknown }; // TODO: define all errors diff --git a/src/options.ts b/src/options.ts index 230c72bce9e..bbf422c0fc2 100644 --- a/src/options.ts +++ b/src/options.ts @@ -30,4 +30,7 @@ export interface BaseOptions { export interface Options extends BaseOptions { // TODO(samstern): Remove this once options is better typed [key: string]: unknown; + + // whether it's coming from the VS Code Extension + isVSCE?: true; } diff --git a/src/requireAuth.ts b/src/requireAuth.ts index 792fce54df1..c86a8e43324 100644 --- a/src/requireAuth.ts +++ b/src/requireAuth.ts @@ -9,6 +9,8 @@ import * as utils from "./utils"; import * as scopes from "./scopes"; import { Tokens, User } from "./types/auth"; import { setRefreshToken, setActiveAccount } from "./auth"; +import { selectProjectInMonospace, isMonospaceEnv } from "./monospace"; +import type { Options } from "./options"; const AUTH_ERROR_MESSAGE = `Command requires authentication, please run ${clc.bold( "firebase login" @@ -34,10 +36,19 @@ function getAuthClient(config: GoogleAuthOptions): GoogleAuth { * @param options CLI options. * @param authScopes scopes to be obtained. */ -async function autoAuth(options: any, authScopes: string[]): Promise { +async function autoAuth(options: Options, authScopes: string[]): Promise { const client = getAuthClient({ scopes: authScopes, projectId: options.project }); + const token = await client.getAccessToken(); token !== null ? apiv2.setAccessToken(token) : false; + + if (!options.isVSCE && (await isMonospaceEnv())) { + await selectProjectInMonospace({ + projectRoot: options.config.projectDir, + project: options.project, + isVSCE: options.isVSCE, + }); + } } /** From f5671f1d3e28a786b15f0af99efa1a3796df5513 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Mon, 12 Jun 2023 13:14:45 -0700 Subject: [PATCH 1019/1699] Fix firebase-vscode/package-lock.json and add it to linting. (#5965) * Fix firebase-vscode/package-lock.json and add it to linting. * Update node-test.yml --- .github/workflows/node-test.yml | 21 ++++++++- firebase-vscode/package-lock.json | 74 ++++++++++--------------------- 2 files changed, 42 insertions(+), 53 deletions(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 5a089572232..bf1bb24e8b5 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -192,13 +192,30 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - cache: npm - cache-dependency-path: npm-shrinkwrap.json - run: npm i -g npm@9.5 # --ignore-scripts prevents the `prepare` script from being run. - run: npm install --package-lock-only --ignore-scripts - run: "git diff --exit-code -- npm-shrinkwrap.json || (echo 'Error: npm-shrinkwrap.json is changed during npm install! Please make sure to use npm >= 8 and commit npm-shrinkwrap.json.' && false)" + check-package-lock-vsce: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: + - "18" + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm i -g npm@9.5 + # --ignore-scripts prevents the `prepare` script from being run. + - run: "(cd firebase-vscode && npm install --package-lock-only --ignore-scripts)" + - run: "git diff --exit-code -- firebase-vscode/package-lock.json || (echo 'Error: firebase-vscode/package-lock.json is changed during npm install! Please make sure to use npm >= 8 and commit firebase-vscode/package-lock.json.' && false)" + check-json-schema: runs-on: ubuntu-latest diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index 8b4d3788633..a08c836efd6 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,19 @@ { "name": "firebase-vscode", - "version": "0.0.7", + "version": "0.0.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-vscode", - "version": "0.0.7", + "version": "0.0.9", + "dependencies": { + "@vscode/codicons": "0.0.30", + "@vscode/webview-ui-toolkit": "^1.2.1", + "classnames": "^2.3.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, "devDependencies": { "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", "@types/glob": "^8.0.0", @@ -17,10 +24,7 @@ "@types/vscode": "^1.69.0", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", - "@vscode/codicons": "0.0.30", "@vscode/test-electron": "^2.2.0", - "@vscode/webview-ui-toolkit": "^1.2.1", - "classnames": "^2.3.2", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.7.1", "eslint": "^8.28.0", @@ -30,8 +34,6 @@ "mini-css-extract-plugin": "^2.6.0", "mocha": "^10.1.0", "postcss-loader": "^7.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", "sass": "^1.52.0", "sass-loader": "^13.0.0", "string-replace-loader": "^3.1.0", @@ -277,14 +279,12 @@ "node_modules/@microsoft/fast-element": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.11.0.tgz", - "integrity": "sha512-VKJYMkS5zgzHHb66sY7AFpYv6IfFhXrjQcAyNgi2ivD65My1XOhtjfKez5ELcLFRJfgZNAxvI8kE69apXERTkw==", - "dev": true + "integrity": "sha512-VKJYMkS5zgzHHb66sY7AFpYv6IfFhXrjQcAyNgi2ivD65My1XOhtjfKez5ELcLFRJfgZNAxvI8kE69apXERTkw==" }, "node_modules/@microsoft/fast-foundation": { "version": "2.47.0", "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.47.0.tgz", "integrity": "sha512-EyFuioaZQ9ngjUNRQi8R3dIPPsaNQdUOS+tP0G7b1MJRhXmQWIitBM6IeveQA6ZvXG6H21dqgrfEWlsYrUZ2sw==", - "dev": true, "dependencies": { "@microsoft/fast-element": "^1.11.0", "@microsoft/fast-web-utilities": "^5.4.1", @@ -296,7 +296,6 @@ "version": "0.1.48", "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.1.48.tgz", "integrity": "sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==", - "dev": true, "dependencies": { "@microsoft/fast-element": "^1.9.0", "@microsoft/fast-foundation": "^2.41.1" @@ -309,7 +308,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", - "dev": true, "dependencies": { "exenv-es6": "^1.1.1" } @@ -673,8 +671,7 @@ "node_modules/@vscode/codicons": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.30.tgz", - "integrity": "sha512-/quu8pLXEyrShoDjTImQwJ2H28y1XhANigyw7E7JvN9NNWc3XCkoIWpcb/tUhdf7XQpopLVVYbkMjXpdPPuMXg==", - "dev": true + "integrity": "sha512-/quu8pLXEyrShoDjTImQwJ2H28y1XhANigyw7E7JvN9NNWc3XCkoIWpcb/tUhdf7XQpopLVVYbkMjXpdPPuMXg==" }, "node_modules/@vscode/test-electron": { "version": "2.2.3", @@ -695,7 +692,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.2.1.tgz", "integrity": "sha512-ZpVqLxoFWWk8mmAN7jr1v9yjD6NGBIoflAedNSusmaViqwHZ2znKBwAwcumLOlNlqmST6QMkiTVys7O8rzfd0w==", - "dev": true, "dependencies": { "@microsoft/fast-element": "^1.6.2", "@microsoft/fast-foundation": "^2.38.0", @@ -1394,8 +1390,7 @@ "node_modules/classnames": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==", - "dev": true + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, "node_modules/cliui": { "version": "7.0.4", @@ -2210,8 +2205,7 @@ "node_modules/exenv-es6": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", - "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", - "dev": true + "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -3283,8 +3277,7 @@ "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 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -3460,7 +3453,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -4362,7 +4354,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4374,7 +4365,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -4706,7 +4696,6 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -5006,8 +4995,7 @@ "node_modules/tabbable": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", - "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", - "dev": true + "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==" }, "node_modules/tapable": { "version": "2.2.1", @@ -5137,8 +5125,7 @@ "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -5856,14 +5843,12 @@ "@microsoft/fast-element": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.11.0.tgz", - "integrity": "sha512-VKJYMkS5zgzHHb66sY7AFpYv6IfFhXrjQcAyNgi2ivD65My1XOhtjfKez5ELcLFRJfgZNAxvI8kE69apXERTkw==", - "dev": true + "integrity": "sha512-VKJYMkS5zgzHHb66sY7AFpYv6IfFhXrjQcAyNgi2ivD65My1XOhtjfKez5ELcLFRJfgZNAxvI8kE69apXERTkw==" }, "@microsoft/fast-foundation": { "version": "2.47.0", "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.47.0.tgz", "integrity": "sha512-EyFuioaZQ9ngjUNRQi8R3dIPPsaNQdUOS+tP0G7b1MJRhXmQWIitBM6IeveQA6ZvXG6H21dqgrfEWlsYrUZ2sw==", - "dev": true, "requires": { "@microsoft/fast-element": "^1.11.0", "@microsoft/fast-web-utilities": "^5.4.1", @@ -5875,7 +5860,6 @@ "version": "0.1.48", "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.1.48.tgz", "integrity": "sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==", - "dev": true, "requires": { "@microsoft/fast-element": "^1.9.0", "@microsoft/fast-foundation": "^2.41.1" @@ -5885,7 +5869,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", - "dev": true, "requires": { "exenv-es6": "^1.1.1" } @@ -6146,8 +6129,7 @@ "@vscode/codicons": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.30.tgz", - "integrity": "sha512-/quu8pLXEyrShoDjTImQwJ2H28y1XhANigyw7E7JvN9NNWc3XCkoIWpcb/tUhdf7XQpopLVVYbkMjXpdPPuMXg==", - "dev": true + "integrity": "sha512-/quu8pLXEyrShoDjTImQwJ2H28y1XhANigyw7E7JvN9NNWc3XCkoIWpcb/tUhdf7XQpopLVVYbkMjXpdPPuMXg==" }, "@vscode/test-electron": { "version": "2.2.3", @@ -6165,7 +6147,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.2.1.tgz", "integrity": "sha512-ZpVqLxoFWWk8mmAN7jr1v9yjD6NGBIoflAedNSusmaViqwHZ2znKBwAwcumLOlNlqmST6QMkiTVys7O8rzfd0w==", - "dev": true, "requires": { "@microsoft/fast-element": "^1.6.2", "@microsoft/fast-foundation": "^2.38.0", @@ -6693,8 +6674,7 @@ "classnames": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==", - "dev": true + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, "cliui": { "version": "7.0.4", @@ -7306,8 +7286,7 @@ "exenv-es6": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", - "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", - "dev": true + "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==" }, "fast-deep-equal": { "version": "3.1.3", @@ -8079,8 +8058,7 @@ "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 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "4.1.0", @@ -8218,7 +8196,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -8862,7 +8839,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -8871,7 +8847,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -9082,7 +9057,6 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -9309,8 +9283,7 @@ "tabbable": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", - "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", - "dev": true + "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==" }, "tapable": { "version": "2.2.1", @@ -9392,8 +9365,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsutils": { "version": "3.21.0", From e9c312df3595acac78b1452ceb0174a196c26a61 Mon Sep 17 00:00:00 2001 From: Sairam Sakhamuri Date: Wed, 14 Jun 2023 13:39:21 -0700 Subject: [PATCH 1020/1699] Framework discover v3 (#5982) * Added frameworkSpec and different types used for discoving frameworks * Added repository file system to check if file exists and to read file contents * Removed comments and modified error handling. * Resolved indentation issues * Discovery: Added code analyse codebase and match to a frameworkSpec * Added test express app * FileSystem now tests file existance and reads contents from test app * Update filesystem.ts * Update filesystem.spec.ts * Update filesystem.ts * Changes to filesystem.ts * Update filesystem.spec.ts * Update filesystem.spec.ts * Updated file system current working directory * Changes to filesystem.spec.ts * Added code to test if correct frameworkSpec is recognized * Modified next commands * Removed types which aren't focused at this time. * Added mockFileSystem for testing * Added error handling * Resolved code comments * Removed lint errors * Removed lint errors * changes to desription name * Resolved error handling and test cases changes * Resolved comments * Error handling changes * Resolved comments * Resolved comments --- src/frameworks/compose/discover/filesystem.ts | 55 ++++++ .../compose/discover/frameworkMatcher.ts | 109 +++++++++++ .../compose/discover/frameworkSpec.ts | 38 ++++ src/frameworks/compose/discover/types.ts | 80 ++++++++ .../compose/discover/filesystem.spec.ts | 56 ++++++ .../compose/discover/frameworkMatcher.spec.ts | 171 ++++++++++++++++++ .../compose/discover/mockFileSystem.ts | 38 ++++ 7 files changed, 547 insertions(+) create mode 100644 src/frameworks/compose/discover/filesystem.ts create mode 100644 src/frameworks/compose/discover/frameworkMatcher.ts create mode 100644 src/frameworks/compose/discover/frameworkSpec.ts create mode 100644 src/frameworks/compose/discover/types.ts create mode 100644 src/test/frameworks/compose/discover/filesystem.spec.ts create mode 100644 src/test/frameworks/compose/discover/frameworkMatcher.spec.ts create mode 100644 src/test/frameworks/compose/discover/mockFileSystem.ts diff --git a/src/frameworks/compose/discover/filesystem.ts b/src/frameworks/compose/discover/filesystem.ts new file mode 100644 index 00000000000..b2c7b7f9b47 --- /dev/null +++ b/src/frameworks/compose/discover/filesystem.ts @@ -0,0 +1,55 @@ +import { FileSystem } from "./types"; +import { pathExists, readFile } from "fs-extra"; +import * as path from "path"; +import { FirebaseError } from "../../../error"; +import { logger } from "../../../../src/logger"; + +/** + * Find files or read file contents present in the directory. + */ +export class LocalFileSystem implements FileSystem { + private readonly existsCache: Record = {}; + private readonly contentCache: Record = {}; + + constructor(private readonly cwd: string) {} + + async exists(file: string): Promise { + try { + if (!(file in this.contentCache)) { + this.existsCache[file] = await pathExists(path.resolve(this.cwd, file)); + } + + return this.existsCache[file]; + } catch (error) { + throw new FirebaseError(`Error occured while searching for file: ${error}`); + } + } + + async read(file: string): Promise { + try { + if (!(file in this.contentCache)) { + const fileContents = await readFile(path.resolve(this.cwd, file), "utf-8"); + this.contentCache[file] = fileContents; + } + return this.contentCache[file]; + } catch (error) { + logger.error("Error occured while reading file contents."); + throw error; + } + } +} + +/** + * Convert ENOENT errors into null + */ +export async function readOrNull(fs: FileSystem, path: string): Promise { + try { + return fs.read(path); + } catch (err: any) { + if (err && typeof err === "object" && err?.code === "ENOENT") { + logger.debug("ENOENT error occured while reading file."); + return null; + } + throw new Error(`Unknown error occured while reading file: ${err}`); + } +} diff --git a/src/frameworks/compose/discover/frameworkMatcher.ts b/src/frameworks/compose/discover/frameworkMatcher.ts new file mode 100644 index 00000000000..ffe203efa4c --- /dev/null +++ b/src/frameworks/compose/discover/frameworkMatcher.ts @@ -0,0 +1,109 @@ +import { FirebaseError } from "../../../error"; +import { FrameworkSpec, FileSystem } from "./types"; +import { logger } from "../../../logger"; + +/** + * + */ +export function filterFrameworksWithDependencies( + allFrameworkSpecs: FrameworkSpec[], + dependencies: Record +): FrameworkSpec[] { + return allFrameworkSpecs.filter((framework) => { + return framework.requiredDependencies.every((dependency) => { + return dependency.name in dependencies; + }); + }); +} + +/** + * + */ +export async function filterFrameworksWithFiles( + allFrameworkSpecs: FrameworkSpec[], + fs: FileSystem +): Promise { + try { + const filteredFrameworks = []; + for (const framework of allFrameworkSpecs) { + if (!framework.requiredFiles) { + filteredFrameworks.push(framework); + continue; + } + let isRequired = true; + for (let files of framework.requiredFiles) { + files = Array.isArray(files) ? files : [files]; + for (const file of files) { + isRequired = isRequired && (await fs.exists(file)); + if (!isRequired) { + break; + } + } + } + if (isRequired) { + filteredFrameworks.push(framework); + } + } + + return filteredFrameworks; + } catch (error) { + logger.error("Error: Unable to filter frameworks based on required files", error); + throw error; + } +} + +/** + * Embeded frameworks help to resolve tiebreakers when multiple frameworks are discovered. + * Ex: "next" embeds "react", so if both frameworks are discovered, + * we can suggest "next" commands by removing its embeded framework (react). + */ +export function removeEmbededFrameworks(allFrameworkSpecs: FrameworkSpec[]): FrameworkSpec[] { + const embededFrameworkSet: Set = new Set(); + + for (const framework of allFrameworkSpecs) { + if (!framework.embedsFrameworks) { + continue; + } + for (const item of framework.embedsFrameworks) { + embededFrameworkSet.add(item); + } + } + + return allFrameworkSpecs.filter((item) => !embededFrameworkSet.has(item.id)); +} + +/** + * Identifies the best FrameworkSpec for the codebase. + */ +export async function frameworkMatcher( + runtime: string, + fs: FileSystem, + frameworks: FrameworkSpec[], + dependencies: Record +): Promise { + try { + const filterRuntimeFramework = frameworks.filter((framework) => framework.runtime === runtime); + const frameworksWithDependencies = filterFrameworksWithDependencies( + filterRuntimeFramework, + dependencies + ); + const frameworkWithFiles = await filterFrameworksWithFiles(frameworksWithDependencies, fs); + const allMatches = removeEmbededFrameworks(frameworkWithFiles); + + if (allMatches.length === 0) { + return null; + } + if (allMatches.length > 1) { + const frameworkNames = allMatches.map((framework) => framework.id); + throw new FirebaseError( + `Multiple Frameworks are matched: ${frameworkNames.join( + ", " + )} Manually set up override commands in firebase.json` + ); + } + + return allMatches[0]; + } catch (error: any) { + throw new FirebaseError(`Failed to match the correct framework: ${error}`); + } +} diff --git a/src/frameworks/compose/discover/frameworkSpec.ts b/src/frameworks/compose/discover/frameworkSpec.ts new file mode 100644 index 00000000000..70655a85b07 --- /dev/null +++ b/src/frameworks/compose/discover/frameworkSpec.ts @@ -0,0 +1,38 @@ +import { FrameworkSpec } from "./types"; + +export const frameworkSpecs: FrameworkSpec[] = [ + { + id: "express", + runtime: "nodejs", + webFrameworkId: "Express.js", + requiredDependencies: [ + { + name: "express", + }, + ], + }, + { + id: "nextjs", + runtime: "nodejs", + webFrameworkId: "Next.js", + requiredFiles: ["next.config.js", "next.config.ts"], + requiredDependencies: [ + { + name: "next", + }, + ], + commands: { + build: { + cmd: "next build", + }, + dev: { + cmd: "next dev", + env: { NODE_ENV: "dev" }, + }, + run: { + cmd: "next run", + env: { NODE_ENV: "production" }, + }, + }, + }, +]; diff --git a/src/frameworks/compose/discover/types.ts b/src/frameworks/compose/discover/types.ts new file mode 100644 index 00000000000..677e054ec27 --- /dev/null +++ b/src/frameworks/compose/discover/types.ts @@ -0,0 +1,80 @@ +export interface FileSystem { + exists(file: string): Promise; + read(file: string): Promise; +} + +export interface Runtime { + match(fs: FileSystem): Promise; + getRuntimeName(): string; + analyseCodebase(fs: FileSystem, allFrameworkSpecs: FrameworkSpec[]): Promise; +} + +export interface Command { + // Consider: string[] for series of commands that must execute successfully + // in sequence. + cmd: string; + + // Environment in which command is executed. + env?: Record; +} + +export interface LifecycleCommands { + build?: Command; + run?: Command; + dev?: Command; +} + +export interface FrameworkSpec { + id: string; + + // Only analyze Frameworks with a runtime that matches the matched runtime + runtime: string; + + // e.g. nextjs. Used to verify that Web Frameworks' legacy code and the + // FrameworkSpec agree with one another + webFrameworkId?: string; + + // List of dependencies that should be present in the project. + requiredDependencies: Array<{ + name: string; + // Version + semver?: string; + }>; + + // If a requiredFiles is an array, then one of the files in the array must match. + // This supports, for example, a file that can be a js, ts, or mjs file. + requiredFiles?: Array; + + // Any commands that this framework needs that are not standard for the + // runtime. Often times, this can be empty (e.g. depend on npm run build and + // npm run start) + commands?: LifecycleCommands; + + // We must resolve to a single framework when getting build/dev/run commands. + // embedsFrameworks helps decide tiebreakers by saying, for example, that "astro" + // can embed "svelte", so if both frameworks are discovered, monospace can + // suggest both frameworks' plugins, but should run astro's commands. + embedsFrameworks?: string[]; +} + +export interface RuntimeSpec { + // e.g. `nodejs` + id: string; + + // e.g. `node18-slim`. Depends on user code (e.g. engine field in package.json) + baseImage: string; + + // e.g. `npm install yarn typescript` + packageManagerInstallCommand?: string; + + // e.g. `npm ci`, `npm install`, `yarn` + installCommand?: string; + + // Commands to run right before exporting the container image + // e.g. npm prune --omit=dev, yarn install --production=true + exportCommands?: string[]; + + // The runtime has detected a command that should always be run irrespective of + // the framework (e.g. the "build" script always wins in Node) + detectedCommands?: LifecycleCommands; +} diff --git a/src/test/frameworks/compose/discover/filesystem.spec.ts b/src/test/frameworks/compose/discover/filesystem.spec.ts new file mode 100644 index 00000000000..db1212b3393 --- /dev/null +++ b/src/test/frameworks/compose/discover/filesystem.spec.ts @@ -0,0 +1,56 @@ +import { MockFileSystem } from "./mockFileSystem"; +import { expect } from "chai"; + +describe("MockFileSystem", () => { + let fileSystem: MockFileSystem; + + before(() => { + fileSystem = new MockFileSystem({ + "package.json": JSON.stringify({ + name: "expressapp", + version: "1.0.0", + scripts: { + test: 'echo "Error: no test specified" && exit 1', + }, + dependencies: { + express: "^4.18.2", + }, + }), + }); + }); + + describe("exists", () => { + it("should return true if file exists in the directory ", async () => { + const fileExists = await fileSystem.exists("package.json"); + + expect(fileExists).to.be.true; + expect(fileSystem.getExistsCache("package.json")).to.be.true; + }); + + it("should return false if file does not exist in the directory", async () => { + const fileExists = await fileSystem.exists("nonexistent.txt"); + + expect(fileExists).to.be.false; + }); + }); + + describe("read", () => { + it("should read and return the contents of the file", async () => { + const fileContent = await fileSystem.read("package.json"); + + const expected = JSON.stringify({ + name: "expressapp", + version: "1.0.0", + scripts: { + test: 'echo "Error: no test specified" && exit 1', + }, + dependencies: { + express: "^4.18.2", + }, + }); + + expect(fileContent).to.equal(expected); + expect(fileSystem.getContentCache("package.json")).to.equal(expected); + }); + }); +}); diff --git a/src/test/frameworks/compose/discover/frameworkMatcher.spec.ts b/src/test/frameworks/compose/discover/frameworkMatcher.spec.ts new file mode 100644 index 00000000000..bb80b326fe6 --- /dev/null +++ b/src/test/frameworks/compose/discover/frameworkMatcher.spec.ts @@ -0,0 +1,171 @@ +import { MockFileSystem } from "./mockFileSystem"; +import { expect } from "chai"; +import { + frameworkMatcher, + removeEmbededFrameworks, + filterFrameworksWithFiles, + filterFrameworksWithDependencies, +} from "../../../../frameworks/compose/discover/frameworkMatcher"; +import { frameworkSpecs } from "../../../../frameworks/compose/discover/frameworkSpec"; +import { FrameworkSpec } from "../../../../frameworks/compose/discover/types"; + +describe("frameworkMatcher", () => { + let fileSystem: MockFileSystem; + const NODE_ID = "nodejs"; + + before(() => { + fileSystem = new MockFileSystem({ + "package.json": JSON.stringify({ + name: "expressapp", + version: "1.0.0", + scripts: { + test: 'echo "Error: no test specified" && exit 1', + }, + dependencies: { + express: "^4.18.2", + }, + }), + "package-lock.json": "Unused: contents of package-lock file", + }); + }); + + describe("frameworkMatcher", () => { + it("should return express FrameworkSpec after analysing express application", async () => { + const expressDependency: Record = { + express: "^4.18.2", + }; + const matchedFramework = await frameworkMatcher( + NODE_ID, + fileSystem, + frameworkSpecs, + expressDependency + ); + const expressFrameworkSpec: FrameworkSpec = { + id: "express", + runtime: "nodejs", + webFrameworkId: "Express.js", + requiredDependencies: [ + { + name: "express", + }, + ], + }; + + expect(matchedFramework).to.deep.equal(expressFrameworkSpec); + }); + }); + + describe("removeEmbededFrameworks", () => { + it("should return frameworks after removing embeded frameworks", () => { + const allFrameworks: FrameworkSpec[] = [ + { + id: "express", + runtime: "nodejs", + requiredDependencies: [], + }, + { + id: "next", + runtime: "nodejs", + requiredDependencies: [], + embedsFrameworks: ["react"], + }, + { + id: "react", + runtime: "nodejs", + requiredDependencies: [], + }, + ]; + const actual = removeEmbededFrameworks(allFrameworks); + const expected: FrameworkSpec[] = [ + { + id: "express", + runtime: "nodejs", + requiredDependencies: [], + }, + { + id: "next", + runtime: "nodejs", + requiredDependencies: [], + embedsFrameworks: ["react"], + }, + ]; + + expect(actual).to.have.deep.members(expected); + expect(actual).to.have.length(2); + }); + }); + + describe("filterFrameworksWithFiles", () => { + it("should return frameworks having all the required files", async () => { + const allFrameworks: FrameworkSpec[] = [ + { + id: "express", + runtime: "nodejs", + requiredDependencies: [], + requiredFiles: [["package.json", "package-lock.json"]], + }, + { + id: "next", + runtime: "nodejs", + requiredDependencies: [], + requiredFiles: [["next.config.js"], "next.config.ts"], + }, + ]; + const actual = await filterFrameworksWithFiles(allFrameworks, fileSystem); + const expected: FrameworkSpec[] = [ + { + id: "express", + runtime: "nodejs", + requiredDependencies: [], + requiredFiles: [["package.json", "package-lock.json"]], + }, + ]; + + expect(actual).to.have.deep.members(expected); + expect(actual).to.have.length(1); + }); + }); + + describe("filterFrameworksWithDependencies", () => { + it("should return frameworks having required dependencies with in the project dependencies", () => { + const allFrameworks: FrameworkSpec[] = [ + { + id: "express", + runtime: "nodejs", + requiredDependencies: [ + { + name: "express", + }, + ], + }, + { + id: "next", + runtime: "nodejs", + requiredDependencies: [ + { + name: "next", + }, + ], + }, + ]; + const projectDependencies: Record = { + express: "^4.18.2", + }; + const actual = filterFrameworksWithDependencies(allFrameworks, projectDependencies); + const expected: FrameworkSpec[] = [ + { + id: "express", + runtime: "nodejs", + requiredDependencies: [ + { + name: "express", + }, + ], + }, + ]; + + expect(actual).to.have.deep.members(expected); + expect(actual).to.have.length(1); + }); + }); +}); diff --git a/src/test/frameworks/compose/discover/mockFileSystem.ts b/src/test/frameworks/compose/discover/mockFileSystem.ts new file mode 100644 index 00000000000..cdbf21d6159 --- /dev/null +++ b/src/test/frameworks/compose/discover/mockFileSystem.ts @@ -0,0 +1,38 @@ +import { FileSystem } from "../../../../frameworks/compose/discover/types"; + +export class MockFileSystem implements FileSystem { + private readonly existsCache: Record = {}; + private readonly contentCache: Record = {}; + + constructor(private readonly fileSys: Record) {} + + exists(path: string): Promise { + if (!(path in this.existsCache)) { + this.existsCache[path] = path in this.fileSys; + } + + return Promise.resolve(this.existsCache[path]); + } + + read(path: string): Promise { + if (!(path in this.contentCache)) { + if (!(path in this.fileSys)) { + const err = new Error("File path not found"); + err.cause = "ENOENT"; + throw err; + } else { + this.contentCache[path] = this.fileSys[path]; + } + } + + return Promise.resolve(this.contentCache[path]); + } + + getContentCache(path: string): string { + return this.contentCache[path]; + } + + getExistsCache(path: string): boolean { + return this.existsCache[path]; + } +} From d4d1952a119363caf7cbd8118eabc841bb5dcb8a Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 15 Jun 2023 09:59:28 -0700 Subject: [PATCH 1021/1699] Use firebase API to check for default bucket instead of AppEngine API (#5973) --- CHANGELOG.md | 1 + src/api.ts | 4 ---- src/deploy/storage/prepare.ts | 2 +- src/gcp/storage.ts | 14 +++++++------- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..fed1a9acf3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixed an issue where Storage rules could not be deployed to projects without a billing plan. (#5955) diff --git a/src/api.ts b/src/api.ts index dea5f622506..c20ead3091b 100644 --- a/src/api.ts +++ b/src/api.ts @@ -37,10 +37,6 @@ export const appDistributionOrigin = utils.envOverride( "FIREBASE_APP_DISTRIBUTION_URL", "https://firebaseappdistribution.googleapis.com" ); -export const appengineOrigin = utils.envOverride( - "FIREBASE_APPENGINE_URL", - "https://appengine.googleapis.com" -); export const authOrigin = utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com"); export const consoleOrigin = utils.envOverride( "FIREBASE_CONSOLE_URL", diff --git a/src/deploy/storage/prepare.ts b/src/deploy/storage/prepare.ts index 08271c0174b..6c418f59b40 100644 --- a/src/deploy/storage/prepare.ts +++ b/src/deploy/storage/prepare.ts @@ -34,7 +34,7 @@ export default async function (context: any, options: Options): Promise { const rulesDeploy = new RulesDeploy(options, RulesetServiceType.FIREBASE_STORAGE); const rulesConfigsToDeploy: any[] = []; - if (!Array.isArray(rulesConfig)) { + if (!Array.isArray(rulesConfig) && options.project) { const defaultBucket = await gcp.storage.getDefaultBucket(options.project); rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })]; } diff --git a/src/gcp/storage.ts b/src/gcp/storage.ts index e290ae1bf13..b50bf83eaa5 100644 --- a/src/gcp/storage.ts +++ b/src/gcp/storage.ts @@ -1,10 +1,11 @@ import { Readable } from "stream"; import * as path from "path"; -import { appengineOrigin, storageOrigin } from "../api"; +import { storageOrigin } from "../api"; import { Client } from "../apiv2"; -import { logger } from "../logger"; import { FirebaseError } from "../error"; +import { logger } from "../logger"; +import { getFirebaseProject } from "../management/projects"; /** Bucket Interface */ interface BucketResponse { @@ -138,17 +139,16 @@ interface StorageServiceAccountResponse { kind: string; } -export async function getDefaultBucket(projectId?: string): Promise { +export async function getDefaultBucket(projectId: string): Promise { try { - const appengineClient = new Client({ urlPrefix: appengineOrigin, apiVersion: "v1" }); - const resp = await appengineClient.get<{ defaultBucket: string }>(`/apps/${projectId}`); - if (resp.body.defaultBucket === "undefined") { + const metadata = await getFirebaseProject(projectId); + if (!metadata.resources?.storageBucket) { logger.debug("Default storage bucket is undefined."); throw new FirebaseError( "Your project is being set up. Please wait a minute before deploying again." ); } - return resp.body.defaultBucket; + return metadata.resources.storageBucket; } catch (err: any) { logger.info( "\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support." From d7ed2a91d0c011f9d679238fd876c671523a9e22 Mon Sep 17 00:00:00 2001 From: Kai Bolay Date: Thu, 15 Jun 2023 15:58:33 -0400 Subject: [PATCH 1022/1699] App Distribution: Add commands to manage groups (#5978) - Added appdistribution:group:create and appdistribution:group:delete. - Added --group-alias option to appdistribution:testers:add and appdistribution:testers:remove. --- CHANGELOG.md | 4 + src/appdistribution/client.ts | 66 ++++++++++- src/commands/appdistribution-group-create.ts | 17 +++ src/commands/appdistribution-group-delete.ts | 21 ++++ src/commands/appdistribution-testers-add.ts | 13 ++- .../appdistribution-testers-remove.ts | 38 ++++--- src/commands/index.ts | 3 + src/test/appdistro/client.spec.ts | 104 +++++++++++++++++- 8 files changed, 249 insertions(+), 17 deletions(-) create mode 100644 src/commands/appdistribution-group-create.ts create mode 100644 src/commands/appdistribution-group-delete.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fed1a9acf3c..dd655c586b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,5 @@ +- Added `appdistribution:group:create` and + `appdistribution:group:delete`. +- Added `--group-alias` option to `appdistribution:testers:add` and + `appdistribution:testers:remove`. - Fixed an issue where Storage rules could not be deployed to projects without a billing plan. (#5955) diff --git a/src/appdistribution/client.ts b/src/appdistribution/client.ts index b85a5719c1e..76bc0f2ff9d 100644 --- a/src/appdistribution/client.ts +++ b/src/appdistribution/client.ts @@ -64,6 +64,14 @@ export interface BatchRemoveTestersResponse { emails: string[]; } +export interface Group { + name: string; + displayName: string; + testerCount?: number; + releaseCount?: number; + inviteLinkCount?: number; +} + /** * Makes RPCs to the App Distribution server backend. */ @@ -166,7 +174,7 @@ export class AppDistributionClient { utils.logSuccess("distributed to testers/groups successfully"); } - async addTesters(projectName: string, emails: string[]) { + async addTesters(projectName: string, emails: string[]): Promise { try { await this.appDistroV2Client.request({ method: "POST", @@ -196,4 +204,60 @@ export class AppDistributionClient { } return apiResponse.body; } + + async createGroup(projectName: string, displayName: string, alias?: string): Promise { + let apiResponse; + try { + apiResponse = await this.appDistroV2Client.request<{ displayName: string }, Group>({ + method: "POST", + path: + alias === undefined ? `${projectName}/groups` : `${projectName}/groups?groupId=${alias}`, + body: { displayName: displayName }, + }); + } catch (err: any) { + throw new FirebaseError(`Failed to create group ${err}`); + } + return apiResponse.body; + } + + async deleteGroup(groupName: string): Promise { + try { + await this.appDistroV2Client.request({ + method: "DELETE", + path: groupName, + }); + } catch (err: any) { + throw new FirebaseError(`Failed to delete group ${err}`); + } + + utils.logSuccess(`Group deleted successfully`); + } + + async addTestersToGroup(groupName: string, emails: string[]): Promise { + try { + await this.appDistroV2Client.request({ + method: "POST", + path: `${groupName}:batchJoin`, + body: { emails: emails }, + }); + } catch (err: any) { + throw new FirebaseError(`Failed to add testers to group ${err}`); + } + + utils.logSuccess(`Testers added to group successfully`); + } + + async removeTestersFromGroup(groupName: string, emails: string[]): Promise { + try { + await this.appDistroV2Client.request({ + method: "POST", + path: `${groupName}:batchLeave`, + body: { emails: emails }, + }); + } catch (err: any) { + throw new FirebaseError(`Failed to remove testers from group ${err}`); + } + + utils.logSuccess(`Testers removed from group successfully`); + } } diff --git a/src/commands/appdistribution-group-create.ts b/src/commands/appdistribution-group-create.ts new file mode 100644 index 00000000000..493d32af836 --- /dev/null +++ b/src/commands/appdistribution-group-create.ts @@ -0,0 +1,17 @@ +import { Command } from "../command"; +import * as utils from "../utils"; +import { requireAuth } from "../requireAuth"; +import { AppDistributionClient } from "../appdistribution/client"; +import { getProjectName } from "../appdistribution/options-parser-util"; + +export const command = new Command("appdistribution:group:create [alias]") + .description("create group in project") + .before(requireAuth) + .action(async (displayName: string, alias?: string, options?: any) => { + const projectName = await getProjectName(options); + const appDistroClient = new AppDistributionClient(); + utils.logBullet(`Creating group in project`); + const group = await appDistroClient.createGroup(projectName, displayName, alias); + alias = group.name.split("/").pop(); + utils.logSuccess(`Group '${group.displayName}' (alias: ${alias}) created successfully`); + }); diff --git a/src/commands/appdistribution-group-delete.ts b/src/commands/appdistribution-group-delete.ts new file mode 100644 index 00000000000..16651d711f8 --- /dev/null +++ b/src/commands/appdistribution-group-delete.ts @@ -0,0 +1,21 @@ +import { Command } from "../command"; +import * as utils from "../utils"; +import { requireAuth } from "../requireAuth"; +import { FirebaseError } from "../error"; +import { AppDistributionClient } from "../appdistribution/client"; +import { getProjectName } from "../appdistribution/options-parser-util"; + +export const command = new Command("appdistribution:group:delete ") + .description("delete group from a project") + .before(requireAuth) + .action(async (alias: string, options: any) => { + const projectName = await getProjectName(options); + const appDistroClient = new AppDistributionClient(); + try { + utils.logBullet(`Deleting group from project`); + await appDistroClient.deleteGroup(`${projectName}/groups/${alias}`); + } catch (err: any) { + throw new FirebaseError(`Failed to delete group ${err}`); + } + utils.logSuccess(`Group ${alias} has successfully been deleted`); + }); diff --git a/src/commands/appdistribution-testers-add.ts b/src/commands/appdistribution-testers-add.ts index e9833348f2c..66a9dc76e71 100644 --- a/src/commands/appdistribution-testers-add.ts +++ b/src/commands/appdistribution-testers-add.ts @@ -5,8 +5,12 @@ import { AppDistributionClient } from "../appdistribution/client"; import { getEmails, getProjectName } from "../appdistribution/options-parser-util"; export const command = new Command("appdistribution:testers:add [emails...]") - .description("add testers to project") + .description("add testers to project (and possibly group)") .option("--file ", "a path to a file containing a list of tester emails to be added") + .option( + "--group-alias ", + "if specified, the testers are also added to the group identified by this alias" + ) .before(requireAuth) .action(async (emails: string[], options?: any) => { const projectName = await getProjectName(options); @@ -14,4 +18,11 @@ export const command = new Command("appdistribution:testers:add [emails...]") const emailsToAdd = getEmails(emails, options.file); utils.logBullet(`Adding ${emailsToAdd.length} testers to project`); await appDistroClient.addTesters(projectName, emailsToAdd); + if (options.groupAlias) { + utils.logBullet(`Adding ${emailsToAdd.length} testers to group`); + await appDistroClient.addTestersToGroup( + `${projectName}/groups/${options.groupAlias}`, + emailsToAdd + ); + } }); diff --git a/src/commands/appdistribution-testers-remove.ts b/src/commands/appdistribution-testers-remove.ts index 3ecbc1a29d7..c6bc972c8ea 100644 --- a/src/commands/appdistribution-testers-remove.ts +++ b/src/commands/appdistribution-testers-remove.ts @@ -7,25 +7,37 @@ import { getEmails, getProjectName } from "../appdistribution/options-parser-uti import { logger } from "../logger"; export const command = new Command("appdistribution:testers:remove [emails...]") - .description("remove testers from a project") + .description("remove testers from a project (or group)") .option("--file ", "a path to a file containing a list of tester emails to be removed") + .option( + "--group-alias ", + "if specified, the testers are only removed from the group identified by this alias, but not the project" + ) .before(requireAuth) .action(async (emails: string[], options?: any) => { const projectName = await getProjectName(options); const appDistroClient = new AppDistributionClient(); const emailsArr = getEmails(emails, options.file); - let deleteResponse; - try { - utils.logBullet(`Deleting ${emailsArr.length} testers from project`); - deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr); - } catch (err: any) { - throw new FirebaseError(`Failed to remove testers ${err}`); - } + if (options.groupAlias) { + utils.logBullet(`Removing ${emailsArr.length} testers from group`); + await appDistroClient.removeTestersFromGroup( + `${projectName}/groups/${options.groupAlias}`, + emailsArr + ); + } else { + let deleteResponse; + try { + utils.logBullet(`Deleting ${emailsArr.length} testers from project`); + deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr); + } catch (err: any) { + throw new FirebaseError(`Failed to remove testers ${err}`); + } - if (!deleteResponse.emails) { - utils.logSuccess(`Testers did not exist`); - return; + if (!deleteResponse.emails) { + utils.logSuccess(`Testers did not exist`); + return; + } + logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`); + utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`); } - logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`); - utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`); }); diff --git a/src/commands/index.ts b/src/commands/index.ts index 8d7de5c2362..1b3c5d3bc30 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -25,6 +25,9 @@ export function load(client: any): any { client.appdistribution.testers = {}; client.appdistribution.testers.add = loadCommand("appdistribution-testers-add"); client.appdistribution.testers.delete = loadCommand("appdistribution-testers-remove"); + client.appdistribution.group = {}; + client.appdistribution.group.create = loadCommand("appdistribution-group-create"); + client.appdistribution.group.delete = loadCommand("appdistribution-group-delete"); client.apps = {}; client.apps.create = loadCommand("apps-create"); client.apps.list = loadCommand("apps-list"); diff --git a/src/test/appdistro/client.spec.ts b/src/test/appdistro/client.spec.ts index 1390257cf46..db888a9f758 100644 --- a/src/test/appdistro/client.spec.ts +++ b/src/test/appdistro/client.spec.ts @@ -6,7 +6,11 @@ import * as rimraf from "rimraf"; import * as sinon from "sinon"; import * as tmp from "tmp"; -import { AppDistributionClient, BatchRemoveTestersResponse } from "../../appdistribution/client"; +import { + AppDistributionClient, + BatchRemoveTestersResponse, + Group, +} from "../../appdistribution/client"; import { appDistributionOrigin } from "../../api"; import { Distribution } from "../../appdistribution/distribution"; import { FirebaseError } from "../../error"; @@ -17,6 +21,7 @@ describe("distribution", () => { const tempdir = tmp.dirSync(); const projectName = "projects/123456789"; const appName = `${projectName}/apps/1:123456789:ios:abc123def456`; + const groupName = `${projectName}/groups/my-group`; const binaryFile = join(tempdir.name, "app.ipa"); fs.ensureFileSync(binaryFile); const mockDistribution = new Distribution(binaryFile); @@ -61,6 +66,7 @@ describe("distribution", () => { describe("deleteTesters", () => { const emails = ["a@foo.com", "b@foo.com"]; + const mockResponse: BatchRemoveTestersResponse = { emails: emails }; it("should throw error if delete fails", async () => { nock(appDistributionOrigin) @@ -73,7 +79,6 @@ describe("distribution", () => { expect(nock.isDone()).to.be.true; }); - const mockResponse: BatchRemoveTestersResponse = { emails: emails }; it("should resolve when request succeeds", async () => { nock(appDistributionOrigin) .post(`/v1/${projectName}/testers:batchRemove`) @@ -200,4 +205,99 @@ describe("distribution", () => { }); }); }); + + describe("createGroup", () => { + const mockResponse: Group = { name: groupName, displayName: "My Group" }; + + it("should throw error if request fails", async () => { + nock(appDistributionOrigin) + .post(`/v1/${projectName}/groups`) + .reply(400, { error: { status: "FAILED_PRECONDITION" } }); + await expect(appDistributionClient.createGroup(projectName, "My Group")).to.be.rejectedWith( + FirebaseError, + "Failed to create group" + ); + expect(nock.isDone()).to.be.true; + }); + + it("should resolve when request succeeds", async () => { + nock(appDistributionOrigin).post(`/v1/${projectName}/groups`).reply(200, mockResponse); + await expect( + appDistributionClient.createGroup(projectName, "My Group") + ).to.eventually.deep.eq(mockResponse); + expect(nock.isDone()).to.be.true; + }); + + it("should resolve when request with alias succeeds", async () => { + nock(appDistributionOrigin) + .post(`/v1/${projectName}/groups?groupId=my-group`) + .reply(200, mockResponse); + await expect( + appDistributionClient.createGroup(projectName, "My Group", "my-group") + ).to.eventually.deep.eq(mockResponse); + expect(nock.isDone()).to.be.true; + }); + }); + + describe("deleteGroup", () => { + it("should throw error if delete fails", async () => { + nock(appDistributionOrigin) + .delete(`/v1/${groupName}`) + .reply(400, { error: { status: "FAILED_PRECONDITION" } }); + await expect(appDistributionClient.deleteGroup(groupName)).to.be.rejectedWith( + FirebaseError, + "Failed to delete group" + ); + expect(nock.isDone()).to.be.true; + }); + + it("should resolve when request succeeds", async () => { + nock(appDistributionOrigin).delete(`/v1/${groupName}`).reply(200, {}); + await expect(appDistributionClient.deleteGroup(groupName)).to.be.eventually.fulfilled; + expect(nock.isDone()).to.be.true; + }); + }); + + describe("addTestersToGroup", () => { + const emails = ["a@foo.com", "b@foo.com"]; + + it("should throw error if request fails", async () => { + nock(appDistributionOrigin) + .post(`/v1/${groupName}:batchJoin`) + .reply(400, { error: { status: "FAILED_PRECONDITION" } }); + await expect(appDistributionClient.addTestersToGroup(groupName, emails)).to.be.rejectedWith( + FirebaseError, + "Failed to add testers to group" + ); + expect(nock.isDone()).to.be.true; + }); + + it("should resolve when request succeeds", async () => { + nock(appDistributionOrigin).post(`/v1/${groupName}:batchJoin`).reply(200, {}); + await expect(appDistributionClient.addTestersToGroup(groupName, emails)).to.be.eventually + .fulfilled; + expect(nock.isDone()).to.be.true; + }); + }); + + describe("removeTestersFromGroup", () => { + const emails = ["a@foo.com", "b@foo.com"]; + + it("should throw error if request fails", async () => { + nock(appDistributionOrigin) + .post(`/v1/${groupName}:batchLeave`) + .reply(400, { error: { status: "FAILED_PRECONDITION" } }); + await expect( + appDistributionClient.removeTestersFromGroup(groupName, emails) + ).to.be.rejectedWith(FirebaseError, "Failed to remove testers from group"); + expect(nock.isDone()).to.be.true; + }); + + it("should resolve when request succeeds", async () => { + nock(appDistributionOrigin).post(`/v1/${groupName}:batchLeave`).reply(200, {}); + await expect(appDistributionClient.removeTestersFromGroup(groupName, emails)).to.be.eventually + .fulfilled; + expect(nock.isDone()).to.be.true; + }); + }); }); From e989cada10e4ea57d8a27152212362b097ceda2a Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 15 Jun 2023 20:11:02 +0000 Subject: [PATCH 1023/1699] 12.4.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 44dd1fcdf1a..018218f8e08 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "12.3.1", + "version": "12.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "12.3.1", + "version": "12.4.0", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 11f013e1d22..a93b3c2d71d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "12.3.1", + "version": "12.4.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 3f3c1868ef5adce4cc9217fbe54c54ef31b01433 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 15 Jun 2023 20:11:15 +0000 Subject: [PATCH 1024/1699] [firebase-release] Removed change log and reset repo after 12.4.0 release --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd655c586b4..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +0,0 @@ -- Added `appdistribution:group:create` and - `appdistribution:group:delete`. -- Added `--group-alias` option to `appdistribution:testers:add` and - `appdistribution:testers:remove`. -- Fixed an issue where Storage rules could not be deployed to projects without a billing plan. (#5955) From 71c8e7d9ff43e17a0697cd66adf7ac52d7049de6 Mon Sep 17 00:00:00 2001 From: Leonardo Ortiz Date: Fri, 16 Jun 2023 12:26:08 -0300 Subject: [PATCH 1025/1699] Auth integration (#5667) * plugin integration WIP * add getInitFirebaseResponse type * satisfy tsconfig.compile.json * update get-init-firebase-response response format * isMonospaceEnv * set access token only in autoAuth * get monospace daemon port in function * create `.firebaserc` with authorized project * don't run setupMonospace if is vs code extension * return authorized project * isVSCE from options, return null if no project * clean up * use `projectRoot` again, only create rc if ... ... there's a project * lint friendly assertion * `/get-init-firebase-response` responses in logger * handle `userResponse` false * address nit, replace FIXME with TODO * depromisify isMonospaceEnv * remove unnecessary else * poll every 2 seconds instead of 5 --- src/monospace/index.ts | 17 ++++++++++------- src/monospace/interfaces.ts | 2 +- src/requireAuth.ts | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/monospace/index.ts b/src/monospace/index.ts index 410676cfdc4..b9d660127ed 100644 --- a/src/monospace/index.ts +++ b/src/monospace/index.ts @@ -10,7 +10,7 @@ import type { SetupMonospaceOptions, } from "./interfaces"; -const POLL_USER_RESPONSE_MILLIS = 5000; +const POLL_USER_RESPONSE_MILLIS = 2000; /** * Integrate Firebase Plugin with Monospace’s service Account Authentication @@ -54,9 +54,9 @@ async function pollAuthorizedProject(rid: string): Promise { if ("userResponse" in getInitFirebaseRes) { if (getInitFirebaseRes.userResponse.success) { return getInitFirebaseRes.userResponse.projectId; - } else { - return null; } + + return null; } const { error } = getInitFirebaseRes; @@ -72,7 +72,8 @@ async function pollAuthorizedProject(rid: string): Promise { return pollAuthorizedProject(rid); } - // FIXME: This is not being reached as the process exits before a new call is made + // TODO: Review this. It's not being reached as the process exits before a new + // call is made // Error response: User canceled without authorizing any project if (error === "USER_CANCELED") { @@ -81,7 +82,9 @@ async function pollAuthorizedProject(rid: string): Promise { throw new FirebaseError("User canceled without authorizing any project"); } - throw new FirebaseError(`Unhandled /get-init-firebase-response error: ${error}`); + throw new FirebaseError(`Unhandled /get-init-firebase-response error`, { + original: new Error(error), + }); } /** @@ -143,8 +146,8 @@ function createFirebaseRc(projectDir: string, authorizedProject: string): boolea /** * Whether this is a Monospace environment */ -export async function isMonospaceEnv(): Promise { - return Promise.resolve(Boolean(getMonospaceDaemonPort())); +export function isMonospaceEnv(): boolean { + return getMonospaceDaemonPort() !== undefined; } /** diff --git a/src/monospace/interfaces.ts b/src/monospace/interfaces.ts index d0bcd058d8b..c57bb4079e6 100644 --- a/src/monospace/interfaces.ts +++ b/src/monospace/interfaces.ts @@ -22,7 +22,7 @@ export type GetInitFirebaseResponse = } | { success: false; - error: "WAITING_FOR_RESPONSE" | "USER_CANCELED" | unknown; // TODO: define all errors + error: "WAITING_FOR_RESPONSE" | "USER_CANCELED" | string; // TODO: define all errors }; export type InitFirebaseResponse = diff --git a/src/requireAuth.ts b/src/requireAuth.ts index c86a8e43324..caaa985f96f 100644 --- a/src/requireAuth.ts +++ b/src/requireAuth.ts @@ -42,7 +42,7 @@ async function autoAuth(options: Options, authScopes: string[]): Promise { const token = await client.getAccessToken(); token !== null ? apiv2.setAccessToken(token) : false; - if (!options.isVSCE && (await isMonospaceEnv())) { + if (!options.isVSCE && isMonospaceEnv()) { await selectProjectInMonospace({ projectRoot: options.config.projectDir, project: options.project, From ba8a2a39b5baf11cc4b0c402de7273ac50c26276 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 16 Jun 2023 12:15:18 -0700 Subject: [PATCH 1026/1699] Fix flaky convertConfig test (#5981) * Increasing timeout on flaking unit tests * take 2 * Increasing timeouts further * Found the actual cause - flaky tests were making remote calls and catching 403 errors * formats --- src/test/deploy/hosting/convertConfig.spec.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/test/deploy/hosting/convertConfig.spec.ts b/src/test/deploy/hosting/convertConfig.spec.ts index 35f45094b1c..46ce327e153 100644 --- a/src/test/deploy/hosting/convertConfig.spec.ts +++ b/src/test/deploy/hosting/convertConfig.spec.ts @@ -488,6 +488,20 @@ describe("convertConfig", () => { } describe("rewrites errors", () => { + let existingBackendStub: sinon.SinonStub; + + beforeEach(() => { + existingBackendStub = sinon + .stub(backend, "existingBackend") + .rejects( + new FirebaseError("Some permissions 403 error (that should be caught)", { status: 403 }) + ); + }); + + afterEach(() => { + existingBackendStub.restore(); + }); + it("should throw when rewrite points to function in the wrong region", async () => { await expect( convertConfig( @@ -520,7 +534,6 @@ describe("convertConfig", () => { ) ).to.eventually.be.rejectedWith(FirebaseError); }); - it("should throw when rewrite points to function being deleted", async () => { await expect( convertConfig( From 4b28e8a63604ac5b604a14d98c84b307646854fe Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 16 Jun 2023 13:03:39 -0700 Subject: [PATCH 1027/1699] Update plugin README (#5966) --- firebase-vscode/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/firebase-vscode/README.md b/firebase-vscode/README.md index 086212a4347..abaea3ca54f 100644 --- a/firebase-vscode/README.md +++ b/firebase-vscode/README.md @@ -6,8 +6,11 @@ This extension is in the development and exploration stage. 1. In order to make sure f5 launches the extension properly, first open your VS Code session from the `firebase-vscode` subdirectory (not the `firebase-tools` directory). -2. npm i -3. f5 to run opens new window +2. npm i (run this in both `firebase-tools` and `firebase-vscode`) +3. Make sure the extension `amodio.tsl-problem-matcher` is installed - this + enables the watcher to work, otherwise the Extension Development Host + will not automatically open on F5 when the compilation is done. +4. f5 to run opens new window f5 -> npm run watch defined in tasks.json My terminal didn't have npm available but yours might From 4612ef2fa0006dfab964acc51e7159974e3df490 Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Fri, 16 Jun 2023 17:17:58 -0700 Subject: [PATCH 1028/1699] Emulators in the VSCode plugin (#5954) Add emulator support in the VSCode plugin. This is still a prototype version and you may encounter bugs. --- firebase-vscode/common/messaging/protocol.ts | 16 ++ firebase-vscode/common/messaging/types.d.ts | 18 ++ firebase-vscode/src/cli.ts | 37 ++- firebase-vscode/src/extension.ts | 8 +- firebase-vscode/src/options.ts | 9 +- firebase-vscode/src/workflow.ts | 55 +++- firebase-vscode/webviews/EmulatorPanel.tsx | 249 +++++++++++++++++++ firebase-vscode/webviews/SidebarApp.tsx | 17 +- src/config.ts | 2 +- src/emulator/commandUtils.ts | 12 +- src/emulator/controller.ts | 25 +- src/options.ts | 3 + 12 files changed, 418 insertions(+), 33 deletions(-) create mode 100644 firebase-vscode/webviews/EmulatorPanel.tsx diff --git a/firebase-vscode/common/messaging/protocol.ts b/firebase-vscode/common/messaging/protocol.ts index 087607fdfc4..c32f8f1c726 100644 --- a/firebase-vscode/common/messaging/protocol.ts +++ b/firebase-vscode/common/messaging/protocol.ts @@ -7,6 +7,7 @@ import { FirebaseConfig } from '../../../src/firebaseConfig'; import { FirebaseRC } from "../firebaserc"; import { User } from "../../../src/types/auth"; import { ServiceAccountUser } from "../types"; +import { EmulatorUiSelections, RunningEmulatorInfo } from "./types"; export interface WebviewToExtensionParamsMap { /** @@ -76,6 +77,18 @@ export interface WebviewToExtensionParamsMap { openLink: { href: string }; + + /** + * Equivalent to the `firebase emulators:start` command. + */ + launchEmulators : { + emulatorUiSelections: EmulatorUiSelections, + }; + + /** Stops the emulators gracefully allowing for data export if required. */ + stopEmulators: {}; + + selectEmulatorImportFolder: {}; } export interface ExtensionToWebviewParamsMap { @@ -117,6 +130,9 @@ export interface ExtensionToWebviewParamsMap { */ notifyFirebaseConfig: { firebaseJson: FirebaseConfig, firebaseRC: FirebaseRC }; + notifyEmulatorsStopped: {}; + notifyRunningEmulatorInfo: RunningEmulatorInfo ; + notifyEmulatorImportFolder: { folder: string }; } export type MessageParamsMap = WebviewToExtensionParamsMap | ExtensionToWebviewParamsMap; diff --git a/firebase-vscode/common/messaging/types.d.ts b/firebase-vscode/common/messaging/types.d.ts index cbae270f1c0..910b498eabe 100644 --- a/firebase-vscode/common/messaging/types.d.ts +++ b/firebase-vscode/common/messaging/types.d.ts @@ -1,4 +1,5 @@ import { Channel } from "../hosting/api"; +import { EmulatorInfo } from "../emulator/types"; import { ExtensionToWebviewParamsMap, MessageParamsMap } from "./protocol"; export interface Message { @@ -15,3 +16,20 @@ export interface MessageListeners { export interface ChannelWithId extends Channel { id: string; } + +/** + * Info to display in the UI while the emulators are running + */ +export interface RunningEmulatorInfo { + uiUrl: string; + displayInfo: EmulatorInfo[]; +} + +export interface EmulatorUiSelections { + projectId: string + firebaseJsonPath?: string + importStateFolderPath?: string + exportStateOnExit: boolean + mode: "hosting" | "all" + debugLogging: boolean +} diff --git a/firebase-vscode/src/cli.ts b/firebase-vscode/src/cli.ts index 87ab28f42e8..93fbab367a2 100644 --- a/firebase-vscode/src/cli.ts +++ b/firebase-vscode/src/cli.ts @@ -12,17 +12,22 @@ import { hostingChannelDeployAction } from "../../src/commands/hosting-channel-d import { listFirebaseProjects } from "../../src/management/projects"; import { requireAuth } from "../../src/requireAuth"; import { deploy } from "../../src/deploy"; -import { FirebaseConfig, HostingSingle } from "../../src/firebaseConfig"; -import { FirebaseRC } from "../common/firebaserc"; +import { FirebaseRC } from "../../src/firebaserc"; import { getDefaultHostingSite } from "../../src/getDefaultHostingSite"; import { initAction } from "../../src/commands/init"; +import { startAll as startAllEmulators, cleanShutdown as stopAllEmulators } from "../../src/emulator/controller"; +import { EmulatorRegistry } from "../../src/emulator/registry"; +import { EmulatorInfo, Emulators } from "../../src/emulator/types"; +import { FirebaseConfig, HostingSingle } from "../../src/firebaseConfig"; import { Account, User } from "../../src/types/auth"; import { Options } from "../../src/options"; import { currentOptions, getCommandOptions } from "./options"; import { setInquirerOptions } from "./stubs/inquirer-stub"; import { ServiceAccount } from "../common/types"; import { listChannels } from "../../src/hosting/api"; -import { ChannelWithId } from "../common/messaging/types"; +import { ChannelWithId } from "./messaging/types"; +import * as commandUtils from "../../src/emulator/commandUtils"; +import { EmulatorUiSelections } from "../common/messaging/types"; import { setEnabled } from "../../src/experiments"; import { pluginLogger } from "./logger-wrapper"; @@ -172,6 +177,32 @@ export async function initHosting(options: { spa: boolean; public: string }) { await initAction("hosting", commandOptions); } +export async function emulatorsStart(emulatorUiSelections: EmulatorUiSelections) { + const commandOptions = await getCommandOptions(undefined, { + ...currentOptions, + project: emulatorUiSelections.projectId, + exportOnExit: emulatorUiSelections.exportStateOnExit, + import: emulatorUiSelections.importStateFolderPath, + only: emulatorUiSelections.mode === "hosting" ? "hosting" : "" + }); + // Adjusts some options, export on exit can be a boolean or a path. + commandUtils.setExportOnExitOptions(commandOptions as commandUtils.ExportOnExitOptions); + return startAllEmulators(commandOptions, /*showUi=*/ true); +} + +export async function stopEmulators() { + await stopAllEmulators(); +} + +export function listRunningEmulators(): EmulatorInfo[] { + return EmulatorRegistry.listRunningWithInfo(); +} + +export function getEmulatorUiUrl(): string | undefined { + const url: URL = EmulatorRegistry.url(Emulators.UI); + return url.hostname === "unknown" ? undefined : url.toString(); +} + export async function deployToHosting( firebaseJSON: FirebaseConfig, firebaseRC: FirebaseRC, diff --git a/firebase-vscode/src/extension.ts b/firebase-vscode/src/extension.ts index 8dababb29a0..368fecf5afb 100644 --- a/firebase-vscode/src/extension.ts +++ b/firebase-vscode/src/extension.ts @@ -11,6 +11,7 @@ import { import { setupSidebar } from "./sidebar"; import { setupWorkflow } from "./workflow"; import { pluginLogger } from "./logger-wrapper"; +import { onShutdown } from "./workflow"; const broker = createBroker< ExtensionToWebviewParamsMap, @@ -26,5 +27,8 @@ export function activate(context: vscode.ExtensionContext) { setupSidebar(context, broker); } -// This method is called when your extension is deactivated -export function deactivate() {} +// This method is called when the extension is deactivated +export async function deactivate() { + // This await is optimistic but it might wait for a moment longer while we run cleanup activities + await onShutdown(); +} diff --git a/firebase-vscode/src/options.ts b/firebase-vscode/src/options.ts index b2734b44abb..a8d98372bcc 100644 --- a/firebase-vscode/src/options.ts +++ b/firebase-vscode/src/options.ts @@ -1,4 +1,3 @@ -import { Config as cliConfig } from "../../src/config"; import { FirebaseConfig } from "../../src/firebaseConfig"; import { FirebaseRC } from "../common/firebaserc"; import { RC } from "../../src/rc"; @@ -6,6 +5,7 @@ import { BaseOptions, Options } from "../../src/options"; import { Command } from "../../src/command"; import { ExtensionContext } from "vscode"; import { setInquirerOptions } from "./stubs/inquirer-stub"; +import * as commandUtils from "../../src/emulator/commandUtils"; /** * User-facing CLI options @@ -20,7 +20,8 @@ interface CliOptions extends Omit { * Final options passed to CLI command functions * Result of command.prepare() */ -interface CommandOptions extends Options {} +interface CommandOptions extends Options { +} /** * User-facing CLI options @@ -45,8 +46,10 @@ export let currentOptions: CliOptions & { isVSCE: boolean } = { nonInteractive: true, interactive: false, debug: false, - rc: null, + exportOnExit: false, + import: "", + isVSCE: true }; diff --git a/firebase-vscode/src/workflow.ts b/firebase-vscode/src/workflow.ts index fa799f6e64f..3d85312e2eb 100644 --- a/firebase-vscode/src/workflow.ts +++ b/firebase-vscode/src/workflow.ts @@ -11,12 +11,16 @@ import { writeFirebaseRCFile } from "./utils"; import { ExtensionBrokerImpl } from "./extension-broker"; import { deployToHosting, + emulatorsStart, getAccounts, + getChannels, + getEmulatorUiUrl, + initHosting, listProjects, + listRunningEmulators, login, logoutUser, - initHosting, - getChannels, + stopEmulators, } from "./cli"; import { User } from "../../src/types/auth"; import { FirebaseRC } from "../../src/firebaserc"; @@ -90,7 +94,7 @@ function updateCurrentUser( return currentUserEmail; } -function getRootFolders() { +function getRootFolders(): string[] { if (!workspace) { return []; } @@ -369,10 +373,53 @@ export function setupWorkflow( readAndSendFirebaseConfigs(broker); broker.send("notifyHostingFolderReady", { projectId, folderPath: currentOptions.cwd }); - + await fetchChannels(); } } + broker.on( + "launchEmulators", + async ({ emulatorUiSelections }) => { + await emulatorsStart(emulatorUiSelections); + broker.send("notifyRunningEmulatorInfo", { uiUrl: getEmulatorUiUrl(), displayInfo: listRunningEmulators() }); + } + ); + + broker.on( + "stopEmulators", + async () => { + await stopEmulators(); + // Update the UI + broker.send("notifyEmulatorsStopped"); + } + ); + + broker.on( + "selectEmulatorImportFolder", + async () => { + const options: vscode.OpenDialogOptions = { + canSelectMany: false, + openLabel: `Pick an import folder`, + title: `Pick an import folder`, + canSelectFiles: false, + canSelectFolders: true, + }; + const fileUri = await vscode.window.showOpenDialog(options); + // Update the UI of the selection + if (!fileUri || fileUri.length < 1) { + vscode.window.showErrorMessage("Invalid import folder selected."); + return; + } + broker.send("notifyEmulatorImportFolder", { folder: fileUri[0].fsPath }); + } + ); +} + +/** + * Cleans up any open resources before shutting down. + */ +export async function onShutdown() { + await stopEmulators(); } /** diff --git a/firebase-vscode/webviews/EmulatorPanel.tsx b/firebase-vscode/webviews/EmulatorPanel.tsx new file mode 100644 index 00000000000..a043bc66449 --- /dev/null +++ b/firebase-vscode/webviews/EmulatorPanel.tsx @@ -0,0 +1,249 @@ +import { + VSCodeButton, + VSCodeCheckbox, + VSCodeDivider, + VSCodeLink, + VSCodeProgressRing, + VSCodeTextField, +} from "@vscode/webview-ui-toolkit/react"; +import React, { useState } from "react"; +import { Spacer } from "./components/ui/Spacer"; +import { broker } from "./globals/html-broker"; +import { PanelSection } from "./components/ui/PanelSection"; +import { FirebaseConfig } from "../../src/firebaseConfig"; +import { + RunningEmulatorInfo, + EmulatorUiSelections, +} from "../common/messaging/types"; +import { VSCodeDropdown } from "@vscode/webview-ui-toolkit/react"; +import { VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +import { EmulatorInfo } from "../../src/emulator/types"; +import { webLogger } from "./globals/web-logger"; + +const DEFAULT_EMULATOR_UI_SELECTIONS: EmulatorUiSelections = { + projectId: "demo-something", + importStateFolderPath: "", + exportStateOnExit: false, + mode: "all", + debugLogging: false, +}; + +/** + * Emulator panel component for the VSCode extension. Handles start/stop, import/export. + */ +export function EmulatorPanel({ + firebaseJson, +}: { + firebaseJson: FirebaseConfig; +}) { + if (!firebaseJson) { + throw Error("Expected a valid FirebaseConfig."); + } + const [emulatorUiSelections, setEmulatorUiSelections] = + useState(DEFAULT_EMULATOR_UI_SELECTIONS); + + webLogger.debug( + "initial state ui selections:" + JSON.stringify(emulatorUiSelections) + ); + function setEmulatorUiSelectionsAndSaveToWorkspace( + uiSelections: EmulatorUiSelections + ) { + // TODO(christhompson): Save UI selections in the current workspace. + // Requires context object. + setEmulatorUiSelections(uiSelections); + } + const [showEmulatorProgressIndicator, setShowEmulatorProgressIndicator] = + useState(false); + + // TODO(christhompson): Load UI selections from the current workspace. + // Requires context object. + // TODO(christhompson): Check if the emulators are running on extension start. + const [runningEmulatorInfo, setRunningEmulatorInfo] = + useState(); + + broker.on("notifyEmulatorsStopped", () => { + setShowEmulatorProgressIndicator(false); + webLogger.debug(`notifyEmulatorsStopped received in sidebar`); + setRunningEmulatorInfo(null); + }); + + broker.on("notifyRunningEmulatorInfo", (info: RunningEmulatorInfo) => { + setShowEmulatorProgressIndicator(false); + webLogger.debug(`notifyRunningEmulatorInfo received in sidebar`); + setRunningEmulatorInfo(info); + }); + + broker.on("notifyEmulatorImportFolder", ({ folder }) => { + webLogger.debug(`notifyEmulatorImportFolder received in sidebar: ${folder}`); + emulatorUiSelections.importStateFolderPath = folder; + setEmulatorUiSelectionsAndSaveToWorkspace({ ...emulatorUiSelections }); // rerender clone + }); + + function launchEmulators() { + if (!emulatorUiSelections.projectId) { + broker.send("showMessage", { + msg: "Missing project ID", + options: { + modal: true, + detail: `Please specify a project ID before starting the emulator suite.`, + }, + }); + return; + } + if (!firebaseJson) { + // TODO(christhompson): Consider using a default config in the case that + // firebase.json doesnt exist. + broker.send("showMessage", { + msg: "Missing firebase.json", + options: { + modal: true, + detail: `Unable to find firebase.json file.`, + }, + }); + return; + } + setShowEmulatorProgressIndicator(true); + broker.send("launchEmulators", { + emulatorUiSelections, + }); + } + + function stopEmulators() { + setShowEmulatorProgressIndicator(true); + broker.send("stopEmulators"); + } + + /** + * Called when import folder changes. + */ + function selectedImportFolder(event: any) { + event.preventDefault(); + broker.send("selectEmulatorImportFolder"); + } + + function toggleExportOnExit() { + const selections: EmulatorUiSelections = emulatorUiSelections; + selections.exportStateOnExit = !selections.exportStateOnExit; + webLogger.debug(`toggle export on exit : ${!selections.exportStateOnExit}`); + setEmulatorUiSelectionsAndSaveToWorkspace(selections); + } + + function projectIdChanged(event: React.ChangeEvent) { + webLogger.debug("projectIdChanged: " + event.target.value); + const selections: EmulatorUiSelections = emulatorUiSelections; + selections.projectId = event.target.value; + setEmulatorUiSelectionsAndSaveToWorkspace(selections); + } + + function emulatorModeChanged(event: React.ChangeEvent) { + webLogger.debug("emulatorModeChanged: " + event.target.value); + const selections: EmulatorUiSelections = emulatorUiSelections; + selections.mode = event.target.value as typeof emulatorUiSelections.mode; + setEmulatorUiSelectionsAndSaveToWorkspace(selections); + } + + function clearImportFolder() { + console.log(`clearImportFolder`); + emulatorUiSelections.importStateFolderPath = ""; + setEmulatorUiSelectionsAndSaveToWorkspace({ ...emulatorUiSelections }); + } + + // Make it pretty for the screen. Filter out the logging emulator since it's + // an implementation detail. + // TODO(christhompson): Add more info and sort this. + function formatEmulatorRunningInfo(emulatorInfos: EmulatorInfo[]): string { + return emulatorInfos + .map((info) => info.name) + .filter((name) => name !== "logging") + .join("
    "); + } + + return ( + +

    Launch the Emulator Suite

    + {/* TODO(christhompson): Insert some education links or tooltips here. */} + + Current project ID: + {/* TODO(christhompson): convert this into a demo- prefix checkbox or something. */} + projectIdChanged(event)} + > + + Import emulator state from directory: + + + selectedImportFolder(event)} + /> + + + Clear + + + toggleExportOnExit()} + > + Export emulator state on exit + + + {showEmulatorProgressIndicator ? : <>} + Emulator "mode" + emulatorModeChanged(event)} + > + All emulators + {!!firebaseJson.hosting && Only hosting} + + {runningEmulatorInfo ? ( + <> + + + The emulators are running. + + {!!runningEmulatorInfo.uiUrl && ( + + View them in the Emulator Suite UI + + )} + + Running Emulators: + +
    + + stopEmulators()}> + Click to stop the emulators + + + ) : ( + launchEmulators()} + disabled={showEmulatorProgressIndicator ? true : false} + > + Launch emulators + + )} +
    + ); +} diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx index fd6b3f59841..7b1bd1d1e17 100644 --- a/firebase-vscode/webviews/SidebarApp.tsx +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -4,13 +4,17 @@ import { Spacer } from "./components/ui/Spacer"; import { Body } from "./components/ui/Text"; import { broker } from "./globals/html-broker"; import { User } from "../../src/types/auth"; +import { FirebaseRC } from "../../src/firebaserc"; import { PanelSection } from "./components/ui/PanelSection"; import { AccountSection } from "./components/AccountSection"; import { ProjectSection } from "./components/ProjectSection"; +import { FirebaseConfig } from "../../src/firebaseConfig"; import { ServiceAccountUser } from "../common/types"; import { DeployPanel } from "./components/DeployPanel"; import { HostingState } from "./webview-types"; import { ChannelWithId } from "./messaging/types"; +import { EmulatorPanel } from "./EmulatorPanel"; + import { webLogger } from "./globals/web-logger"; export function SidebarApp() { @@ -28,6 +32,7 @@ export function SidebarApp() { ServiceAccountUser | User > | null>(null); const [isHostingOnboarded, setHostingOnboarded] = useState(false); + const [firebaseJson, setFirebaseJson] = useState(); useEffect(() => { webLogger.debug("loading SidebarApp component"); @@ -48,7 +53,14 @@ export function SidebarApp() { }); broker.on("notifyFirebaseConfig", ({ firebaseJson, firebaseRC }) => { - webLogger.debug("got firebase hosting", JSON.stringify(firebaseJson?.hosting)); + webLogger.debug( + "got firebase hosting", + JSON.stringify(firebaseJson?.hosting) + ); + if (firebaseJson) { + setFirebaseJson(firebaseJson); + webLogger.debug("set firebase JSON"); + } if (firebaseJson?.hosting) { webLogger.debug("Detected hosting setup"); setHostingOnboarded(true); @@ -99,7 +111,7 @@ export function SidebarApp() { email: userEmail!, // Safe to assume user email is already there singleAppSupport: true, }); - }; + } const accountSection = ( )} + {(!!userEmail && !!firebaseJson) && } ); } diff --git a/src/config.ts b/src/config.ts index 893392a87cd..85ed4fa4d92 100644 --- a/src/config.ts +++ b/src/config.ts @@ -238,7 +238,7 @@ export class Config { }); } - public static load(options: any, allowMissing?: boolean) { + public static load(options: any, allowMissing?: boolean): Config | null { const pd = detectProjectRoot(options); const filename = options.configPath || Config.FILENAME; if (pd) { diff --git a/src/emulator/commandUtils.ts b/src/emulator/commandUtils.ts index 888f1973609..c5bd1d73e2d 100644 --- a/src/emulator/commandUtils.ts +++ b/src/emulator/commandUtils.ts @@ -59,7 +59,7 @@ export const FLAG_TEST_PARAMS = "--test-params "; export const DESC_TEST_PARAMS = "A .env file containing test param values for your emulated extension."; -const DEFAULT_CONFIG = new Config( +export const DEFAULT_CONFIG = new Config( { eventarc: {}, database: {}, @@ -182,6 +182,11 @@ export function parseInspectionPort(options: any): number { return parsed; } +export interface ExportOnExitOptions { + exportOnExit?: boolean | string; + import?: string; +} + /** * Sets the correct export options based on --import and --export-on-exit. Mutates the options object. * Also validates if we have a correct setting we need to export the data on exit. @@ -190,10 +195,7 @@ export function parseInspectionPort(options: any): number { * export data the first time they start developing on a clean project. * @param options */ -export function setExportOnExitOptions(options: { - exportOnExit: boolean | string; - import?: string; -}): void { +export function setExportOnExitOptions(options: ExportOnExitOptions): void { if (options.exportOnExit || typeof options.exportOnExit === "string") { // note that options.exportOnExit may be a bool when used as a flag without a [dir] argument: // --import ./data --export-on-exit diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 2a0d2da2eab..a837d5d2d7d 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -105,10 +105,9 @@ export async function cleanShutdown(): Promise { * Filters a list of emulators to only those specified in the config * @param options */ -export function filterEmulatorTargets(options: any): Emulators[] { +export function filterEmulatorTargets(options: { only: string; config: any }): Emulators[] { let targets = [...ALL_SERVICE_EMULATORS]; targets.push(Emulators.EXTENSIONS); - targets = targets.filter((e) => { return options.config.has(e) || options.config.has(`emulators.${e}`); }); @@ -318,7 +317,7 @@ export async function startAll( ); } else { // this should not work: - // firebase emulators:start --only doesnotexit + // firebase emulators:start --only doesnotexist throw new FirebaseError( `${name} is not a valid emulator name, valid options are: ${JSON.stringify( ALL_SERVICE_EMULATORS @@ -834,16 +833,6 @@ export async function startAll( await startEmulator(hostingEmulator); } - if (showUI && !shouldStart(options, Emulators.UI)) { - hubLogger.logLabeled( - "WARN", - "emulators", - "The Emulator UI is not starting, either because none of the emulated " + - "products have an interaction layer in Emulator UI or it cannot " + - "determine the Project ID. Pass the --project flag to specify a project." - ); - } - if (listenForEmulator.logging) { const loggingAddr = legacyGetFirstAddr(Emulators.LOGGING); const loggingEmulator = new LoggingEmulator({ @@ -854,6 +843,16 @@ export async function startAll( await startEmulator(loggingEmulator); } + if (showUI && !shouldStart(options, Emulators.UI)) { + hubLogger.logLabeled( + "WARN", + "emulators", + "The Emulator UI is not starting, either because none of the running " + + "emulators have a UI component or the Emulator UI cannot " + + "determine the Project ID. Pass the --project flag to specify a project." + ); + } + if (listenForEmulator.ui) { const ui = new EmulatorUI({ projectId: projectId, diff --git a/src/options.ts b/src/options.ts index bbf422c0fc2..45814867e0c 100644 --- a/src/options.ts +++ b/src/options.ts @@ -25,6 +25,9 @@ export interface BaseOptions { debug: boolean; rc: RC; + // Emulator specific import/export options + exportOnExit?: boolean | string; + import?: string; } export interface Options extends BaseOptions { From 17eb3212d39197f1190e07863ed8cf2a4da267cd Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 20 Jun 2023 11:22:06 -0700 Subject: [PATCH 1029/1699] Fixes to make web frameworks work for plugin (#6000) --- firebase-vscode/tsconfig.json | 2 +- firebase-vscode/webpack.prod.js | 13 +++++++++++-- src/dynamicImport.js | 12 +++++++++++- src/frameworks/constants.ts | 26 +------------------------- src/frameworks/index.ts | 2 +- src/frameworks/utils.ts | 15 +++++++++++++-- 6 files changed, 38 insertions(+), 32 deletions(-) diff --git a/firebase-vscode/tsconfig.json b/firebase-vscode/tsconfig.json index 1fff794824a..d13ff9f0c38 100644 --- a/firebase-vscode/tsconfig.json +++ b/firebase-vscode/tsconfig.json @@ -5,7 +5,7 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "typeRoots": ["node_modules/@types", "../src/types"], - "module": "ES2015", + "module": "es2020", "moduleResolution": "node", "target": "ES2020", "outDir": "dist", diff --git a/firebase-vscode/webpack.prod.js b/firebase-vscode/webpack.prod.js index e4bb4e4b10f..75a648edc85 100644 --- a/firebase-vscode/webpack.prod.js +++ b/firebase-vscode/webpack.prod.js @@ -1,7 +1,16 @@ const { merge } = require("webpack-merge"); +const TerserPlugin = require("terser-webpack-plugin"); const common = require("./webpack.common.js"); module.exports = common.map(config => merge(config, { - mode: "production" + mode: "production", + optimization: { + minimize: true, + minimizer: [new TerserPlugin({ + terserOptions: { + keep_classnames: /AbortSignal/, + keep_fnames: /AbortSignal/ + } + }), '...'] + } })); - diff --git a/src/dynamicImport.js b/src/dynamicImport.js index bad9d0d2b9c..8061b0a23ac 100644 --- a/src/dynamicImport.js +++ b/src/dynamicImport.js @@ -1,10 +1,20 @@ const { pathToFileURL } = require("url"); +// If being compiled with webpack, use non webpack require for these calls. +// (VSCode plugin uses webpack which by default replaces require calls +// with its own require, which doesn't work on files) +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const requireFunc = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore prevent VSCE webpack from erroring on non_webpack_require + // eslint-disable-next-line camelcase + typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; + exports.dynamicImport = function(mod) { if (mod.startsWith("file://")) return import(mod); if (mod.startsWith("/")) return import(pathToFileURL(mod).toString()); try { - const path = require.resolve(mod); + const path = requireFunc.resolve(mod); return import(pathToFileURL(path).toString()); } catch(e) { return Promise.reject(e); diff --git a/src/frameworks/constants.ts b/src/frameworks/constants.ts index 1b424cb187c..63cc6cd18b0 100644 --- a/src/frameworks/constants.ts +++ b/src/frameworks/constants.ts @@ -1,6 +1,4 @@ -import { readdirSync, statSync } from "fs"; -import { join } from "path"; -import { Framework, SupportLevel } from "./interfaces"; +import { SupportLevel } from "./interfaces"; import * as clc from "colorette"; export const NPM_COMMAND_TIMEOUT_MILLIES = 10_000; @@ -47,28 +45,6 @@ export const ALLOWED_SSR_REGIONS = [ export const I18N_ROOT = "/"; -export const WebFrameworks: Record = Object.fromEntries( - readdirSync(__dirname) - .filter((path) => statSync(join(__dirname, path)).isDirectory()) - .map((path) => { - // If not called by the CLI, (e.g., by the VS Code Extension) - // __dirname won't refer to this folder and these files won't be available. - // Instead it may find sibling folders that aren't modules, and this - // require will throw. - // Long term fix may be to bundle this instead of reading files at runtime - // but for now, this prevents crashing. - try { - return [path, require(join(__dirname, path))]; - } catch (e) { - return []; - } - }) - .filter( - ([, obj]) => - obj && obj.name && obj.discover && obj.build && obj.type !== undefined && obj.support - ) -); - export function GET_DEFAULT_BUILD_TARGETS() { return Promise.resolve(["production", "development"]); } diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 42509442101..8ac1b3bf9c8 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -39,7 +39,6 @@ import { NODE_VERSION, SupportLevelWarnings, VALID_ENGINES, - WebFrameworks, } from "./constants"; import { BUILD_TARGET_PURPOSE, @@ -54,6 +53,7 @@ import { ensureTargeted } from "../functions/ensureTargeted"; import { isDeepStrictEqual } from "util"; import { resolveProjectPath } from "../projectPath"; import { logger } from "../logger"; +import { WebFrameworks } from "./frameworks"; export { WebFrameworks }; diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index 4c139fc9ab3..6967534c740 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -246,11 +246,22 @@ export function relativeRequire(dir: string, mod: "@nuxt/kit"): Promise; */ export function relativeRequire(dir: string, mod: string) { try { - const path = require.resolve(mod, { paths: [dir] }); + // If being compiled with webpack, use non webpack require for these calls. + // (VSCode plugin uses webpack which by default replaces require calls + // with its own require, which doesn't work on files) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const requireFunc: typeof require = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore prevent VSCE webpack from erroring on non_webpack_require + // eslint-disable-next-line camelcase + typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore prevent VSCE webpack from erroring on non_webpack_require + const path = requireFunc.resolve(mod, { paths: [dir] }); if (extname(path) === ".mjs") { return dynamicImport(pathToFileURL(path).toString()); } else { - return require(path); + return requireFunc(path); } } catch (e) { const path = relative(process.cwd(), dir); From c552816e2c211326ba2b68123543719a5fa67cc7 Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Tue, 20 Jun 2023 19:27:41 -0400 Subject: [PATCH 1030/1699] Run lifecycle hooks for specific codebases (#6011) * run lifecycle hooks for codebases specified by only * update changelog --- CHANGELOG.md | 1 + src/deploy/lifecycleHooks.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..20d9ab78675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Run lifecycle hooks for specific codebases. (#6011) diff --git a/src/deploy/lifecycleHooks.ts b/src/deploy/lifecycleHooks.ts index 0c08e09a8cb..08851844a25 100644 --- a/src/deploy/lifecycleHooks.ts +++ b/src/deploy/lifecycleHooks.ts @@ -127,7 +127,6 @@ function getReleventConfigs(target: string, options: Options) { let onlyTargets = options.only.split(","); if (onlyTargets.includes(target)) { - // If the target matches entirely then all instances should be included. return targetConfigs; } @@ -140,6 +139,9 @@ function getReleventConfigs(target: string, options: Options) { }); return targetConfigs.filter((config: any) => { + if (target === "functions") { + return onlyTargets.includes(config.codebase); + } return !config.target || onlyTargets.includes(config.target); }); } From b1b513bcb534b63ad913eb54d743af1531b12a78 Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:00:33 -0700 Subject: [PATCH 1031/1699] Fixed a bug in the hosting config for emulators. (#6013) Also fixed an issue where an empty folder creates an issue loading the side panel. --- firebase-vscode/src/workflow.ts | 22 ++++++++----- firebase-vscode/webviews/EmulatorPanel.tsx | 36 +++++++++++++++++++--- firebase-vscode/webviews/SidebarApp.tsx | 3 +- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/firebase-vscode/src/workflow.ts b/firebase-vscode/src/workflow.ts index 3d85312e2eb..fd404affe06 100644 --- a/firebase-vscode/src/workflow.ts +++ b/firebase-vscode/src/workflow.ts @@ -145,12 +145,17 @@ export function setupWorkflow( ) { extensionContext = context; - // Get user-defined VSCode settings. - const workspaceConfig = workspace.getConfiguration( - 'firebase', - vscode.workspace.workspaceFolders[0].uri - ); - const shouldDebug: boolean = workspaceConfig.get('debug'); + var shouldDebug: boolean; + if (vscode.workspace.workspaceFolders) { + // Get user-defined VSCode settings. + const workspaceConfig = workspace.getConfiguration( + 'firebase', + vscode.workspace.workspaceFolders[0].uri + ); + shouldDebug = workspaceConfig.get('debug'); + } else { + shouldDebug = false; + } /** * Logging setup for logging to console and to file. @@ -158,13 +163,14 @@ export function setupWorkflow( // Sets up CLI logger to log to console process.env.DEBUG = 'true'; setupLoggers(); + + // Only log to file if firebase.debug extension setting is true. + if (shouldDebug) { // Re-implement file logger call from ../../src/bin/firebase.ts to not bring // in the entire firebase.ts file const rootFolders = getRootFolders(); const filePath = path.join(rootFolders[0], 'firebase-plugin-debug.log'); pluginLogger.info('Logging to path', filePath); - // Only log to file if firebase.debug extension setting is true. - if (shouldDebug) { logger.add( new transports.File({ level: "debug", diff --git a/firebase-vscode/webviews/EmulatorPanel.tsx b/firebase-vscode/webviews/EmulatorPanel.tsx index a043bc66449..3d35146a1a7 100644 --- a/firebase-vscode/webviews/EmulatorPanel.tsx +++ b/firebase-vscode/webviews/EmulatorPanel.tsx @@ -33,14 +33,20 @@ const DEFAULT_EMULATOR_UI_SELECTIONS: EmulatorUiSelections = { */ export function EmulatorPanel({ firebaseJson, + projectId, }: { firebaseJson: FirebaseConfig; + projectId?: string | undefined; }) { if (!firebaseJson) { throw Error("Expected a valid FirebaseConfig."); } + var defaultState = DEFAULT_EMULATOR_UI_SELECTIONS; + if (projectId) { + defaultState.projectId = getProjectIdForMode(projectId, defaultState.mode); + } const [emulatorUiSelections, setEmulatorUiSelections] = - useState(DEFAULT_EMULATOR_UI_SELECTIONS); + useState(defaultState); webLogger.debug( "initial state ui selections:" + JSON.stringify(emulatorUiSelections) @@ -74,7 +80,9 @@ export function EmulatorPanel({ }); broker.on("notifyEmulatorImportFolder", ({ folder }) => { - webLogger.debug(`notifyEmulatorImportFolder received in sidebar: ${folder}`); + webLogger.debug( + `notifyEmulatorImportFolder received in sidebar: ${folder}` + ); emulatorUiSelections.importStateFolderPath = folder; setEmulatorUiSelectionsAndSaveToWorkspace({ ...emulatorUiSelections }); // rerender clone }); @@ -139,7 +147,8 @@ export function EmulatorPanel({ webLogger.debug("emulatorModeChanged: " + event.target.value); const selections: EmulatorUiSelections = emulatorUiSelections; selections.mode = event.target.value as typeof emulatorUiSelections.mode; - setEmulatorUiSelectionsAndSaveToWorkspace(selections); + selections.projectId = getProjectIdForMode(projectId, selections.mode); + setEmulatorUiSelectionsAndSaveToWorkspace({...selections}); } function clearImportFolder() { @@ -208,7 +217,9 @@ export function EmulatorPanel({ onChange={(event) => emulatorModeChanged(event)} > All emulators - {!!firebaseJson.hosting && Only hosting} + {!!firebaseJson.hosting && ( + Only hosting + )} {runningEmulatorInfo ? ( <> @@ -247,3 +258,20 @@ export function EmulatorPanel({ ); } + +/** + * Formats a project ID with a demo prefix if we're in offline mode, or uses the + * regular ID if we're hosting. + */ +function getProjectIdForMode( + projectId: string | undefined, + mode: "all" | "hosting" +): string { + if (!projectId) { + return "demo-something"; + } + if (mode === "hosting") { + return projectId; + } + return "demo-" + projectId; +} \ No newline at end of file diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx index 7b1bd1d1e17..dfe0663244e 100644 --- a/firebase-vscode/webviews/SidebarApp.tsx +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -4,7 +4,6 @@ import { Spacer } from "./components/ui/Spacer"; import { Body } from "./components/ui/Text"; import { broker } from "./globals/html-broker"; import { User } from "../../src/types/auth"; -import { FirebaseRC } from "../../src/firebaserc"; import { PanelSection } from "./components/ui/PanelSection"; import { AccountSection } from "./components/AccountSection"; import { ProjectSection } from "./components/ProjectSection"; @@ -153,7 +152,7 @@ export function SidebarApp() { }} /> )} - {(!!userEmail && !!firebaseJson) && } + {(!!userEmail && !!firebaseJson) && } ); } From 0561d36b250fa7325029161f896cbc97335dbcce Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 21 Jun 2023 14:33:04 -0700 Subject: [PATCH 1032/1699] VSCode plugin refactoring and UX (#5968) --- firebase-vscode/.vscodeignore | 1 + firebase-vscode/common/firebaserc.ts | 2 - firebase-vscode/common/messaging/protocol.ts | 35 +- firebase-vscode/package-lock.json | 4 +- firebase-vscode/package.json | 26 +- firebase-vscode/src/cli.ts | 52 ++- firebase-vscode/src/config-files.ts | 146 ++++++++ firebase-vscode/src/logger-wrapper.ts | 2 +- firebase-vscode/src/options.ts | 53 +-- firebase-vscode/src/utils.ts | 11 - firebase-vscode/src/workflow.ts | 318 +++++++----------- firebase-vscode/webviews/SidebarApp.tsx | 39 +-- .../webviews/components/AccountSection.tsx | 31 +- .../webviews/components/DeployPanel.tsx | 147 ++++---- .../webviews/components/InitPanel.tsx | 48 +++ .../webviews/components/ProjectSection.tsx | 5 +- .../webviews/components/ui/SplitButton.scss | 38 +++ .../webviews/components/ui/SplitButton.tsx | 53 +++ .../components/ui/popup-menu/PopupMenu.tsx | 5 + firebase-vscode/webviews/globals/ux-text.ts | 22 ++ firebase-vscode/webviews/tsconfig.json | 2 +- firebase-vscode/webviews/webview-types.ts | 2 +- src/config.ts | 1 + src/deploy/hosting/prepare.ts | 17 +- src/detectProjectRoot.ts | 5 +- src/functionsConfig.ts | 4 +- src/management/projects.ts | 6 +- 27 files changed, 640 insertions(+), 435 deletions(-) delete mode 100644 firebase-vscode/common/firebaserc.ts create mode 100644 firebase-vscode/src/config-files.ts delete mode 100644 firebase-vscode/src/utils.ts create mode 100644 firebase-vscode/webviews/components/InitPanel.tsx create mode 100644 firebase-vscode/webviews/components/ui/SplitButton.scss create mode 100644 firebase-vscode/webviews/components/ui/SplitButton.tsx create mode 100644 firebase-vscode/webviews/globals/ux-text.ts diff --git a/firebase-vscode/.vscodeignore b/firebase-vscode/.vscodeignore index 091caca74cd..4dd9a138ef0 100644 --- a/firebase-vscode/.vscodeignore +++ b/firebase-vscode/.vscodeignore @@ -16,3 +16,4 @@ vsc-extension-quickstart.md webpack.*.js ../ *.zip +node_modules/ diff --git a/firebase-vscode/common/firebaserc.ts b/firebase-vscode/common/firebaserc.ts deleted file mode 100644 index 441be9895fa..00000000000 --- a/firebase-vscode/common/firebaserc.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { RCData } from '../../src/rc'; -export interface FirebaseRC extends Partial {} diff --git a/firebase-vscode/common/messaging/protocol.ts b/firebase-vscode/common/messaging/protocol.ts index c32f8f1c726..ec79deefe5e 100644 --- a/firebase-vscode/common/messaging/protocol.ts +++ b/firebase-vscode/common/messaging/protocol.ts @@ -4,20 +4,16 @@ */ import { FirebaseConfig } from '../../../src/firebaseConfig'; -import { FirebaseRC } from "../firebaserc"; import { User } from "../../../src/types/auth"; import { ServiceAccountUser } from "../types"; +import { RCData } from '../../../src/rc'; import { EmulatorUiSelections, RunningEmulatorInfo } from "./types"; export interface WebviewToExtensionParamsMap { /** - * Ask extension for env variables + * Ask extension for initial data */ - getEnv: {}; - /** - * User management - */ - getUsers: {}; + getInitialData: {}; addUser: {}; logout: { email: string }; @@ -36,11 +32,6 @@ export interface WebviewToExtensionParamsMap { singleAppSupport: boolean }; - /** - * Get hosting channels. - */ - getChannels: {}; - /** * Runs `firebase deploy` for hosting. * TODO(hsubox76): Generalize to work for all `firebase deploy` targets. @@ -50,16 +41,9 @@ export interface WebviewToExtensionParamsMap { }; /** - * Get currently selected Firebase project from extension runtime. + * Prompt user for text input */ - getSelectedProject: {}; - - /** - * Fetches the contents of the .firebaserc and firebase.json config files. - * If either or both files do not exist, then it will return a default - * value. - */ - getFirebaseJson: {}; + promptUserForInput: { title: string, prompt: string }; /** * Show a UI message using the vscode interface @@ -113,7 +97,7 @@ export interface ExtensionToWebviewParamsMap { * Notifies webview when user has successfully selected a hosting folder * and it has been written to firebase.json. */ - notifyHostingFolderReady: { projectId: string, folderPath: string }; + notifyHostingInitDone: { projectId: string, folderPath?: string }; /** * Notify webview of status of deployment attempt. @@ -128,7 +112,12 @@ export interface ExtensionToWebviewParamsMap { * Notify webview of initial discovery or change in firebase.json or * .firebaserc */ - notifyFirebaseConfig: { firebaseJson: FirebaseConfig, firebaseRC: FirebaseRC }; + notifyFirebaseConfig: { firebaseJson: FirebaseConfig, firebaseRC: RCData }; + + /** + * Return user-selected preview channel name + */ + notifyPreviewChannelResponse: { id: string }; notifyEmulatorsStopped: {}; notifyRunningEmulatorInfo: RunningEmulatorInfo ; diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index a08c836efd6..a9525311ad3 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-vscode", - "version": "0.0.9", + "version": "0.0.22", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-vscode", - "version": "0.0.9", + "version": "0.0.22", "dependencies": { "@vscode/codicons": "0.0.30", "@vscode/webview-ui-toolkit": "^1.2.1", diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index 79a701a59d8..ad7369c27c9 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -3,7 +3,7 @@ "displayName": "firebase-vscode", "publisher": "firebase", "description": "VSCode Extension for Firebase", - "version": "0.0.9", + "version": "0.0.22-alpha.0", "engines": { "vscode": "^1.69.0" }, @@ -20,16 +20,26 @@ "configuration": { "title": "Firebase VS Code Extension", "properties": { - "firebase-vscode-extension.firebaseRcFolder": { + "firebase.debug": { + "type": "boolean", + "default": false, + "description": "Enable writing debug-level messages to the file provided in firebase.debugLogPath (requires restart)" + }, + "firebase.debugLogPath": { "type": "string", "default": "", - "description": "Firebase RC folder" + "description": "If firebase.debug is true, appends debug-level messages to the provided file (requires restart)" + }, + "firebase.npmPath": { + "type": "string", + "default": "", + "description": "Path to NPM executable in local environment" + }, + "firebase.useFrameworks": { + "type": "boolean", + "default": false, + "description": "Enable web frameworks in a local VSCode environment" } - }, - "firebase.debug": { - "type": "boolean", - "default": false, - "description": "Logs debug-level messages to firebase-plugin-debug.log (requires restart)" } }, "viewsContainers": { diff --git a/firebase-vscode/src/cli.ts b/firebase-vscode/src/cli.ts index 93fbab367a2..74d3a03f64b 100644 --- a/firebase-vscode/src/cli.ts +++ b/firebase-vscode/src/cli.ts @@ -12,24 +12,21 @@ import { hostingChannelDeployAction } from "../../src/commands/hosting-channel-d import { listFirebaseProjects } from "../../src/management/projects"; import { requireAuth } from "../../src/requireAuth"; import { deploy } from "../../src/deploy"; -import { FirebaseRC } from "../../src/firebaserc"; import { getDefaultHostingSite } from "../../src/getDefaultHostingSite"; import { initAction } from "../../src/commands/init"; import { startAll as startAllEmulators, cleanShutdown as stopAllEmulators } from "../../src/emulator/controller"; import { EmulatorRegistry } from "../../src/emulator/registry"; import { EmulatorInfo, Emulators } from "../../src/emulator/types"; -import { FirebaseConfig, HostingSingle } from "../../src/firebaseConfig"; import { Account, User } from "../../src/types/auth"; import { Options } from "../../src/options"; import { currentOptions, getCommandOptions } from "./options"; import { setInquirerOptions } from "./stubs/inquirer-stub"; import { ServiceAccount } from "../common/types"; import { listChannels } from "../../src/hosting/api"; -import { ChannelWithId } from "./messaging/types"; import * as commandUtils from "../../src/emulator/commandUtils"; -import { EmulatorUiSelections } from "../common/messaging/types"; -import { setEnabled } from "../../src/experiments"; +import { EmulatorUiSelections, ChannelWithId } from "../common/messaging/types"; import { pluginLogger } from "./logger-wrapper"; +import { Config } from "../../src/config"; /** * Wrap the CLI's requireAuth() which is normally run before every command @@ -39,6 +36,7 @@ import { pluginLogger } from "./logger-wrapper"; async function requireAuthWrapper(showError: boolean = true) { // Try to get global default from configstore. For some reason this is // often overwritten when restarting the extension. + pluginLogger.debug('requireAuthWrapper'); let account = getGlobalDefaultAccount(); if (!account) { // If nothing in configstore top level, grab the first "additionalAccount" @@ -48,8 +46,9 @@ async function requireAuthWrapper(showError: boolean = true) { setGlobalDefaultAccount(account); } } - // If account is still null, `requireAuth()` will use google-auth-library - // to look for the service account hopefully. + // `requireAuth()` will register the token with apiv2, and if account is + // still null at this point, it will use google-auth-library + // to find the service account. try { const commandOptions = await getCommandOptions(undefined, { ...currentOptions, @@ -71,8 +70,8 @@ async function requireAuthWrapper(showError: boolean = true) { } return false; } - // No accounts but no error on requireAuth means it's a service account - // (or glogin - edge case) + // If we reach here, there is either a google account or no error on + // requireAuth (which means there is a service account or glogin) return true; } @@ -91,7 +90,7 @@ export async function getAccounts(): Promise> { return accounts; } -export async function getChannels(firebaseJSON: FirebaseConfig): Promise { +export async function getChannels(firebaseJSON: Config): Promise { if (!firebaseJSON) { return []; } @@ -103,18 +102,14 @@ export async function getChannels(firebaseJSON: FirebaseConfig): Promise ({ ...channel, id: channel.name.split("/").pop() })); @@ -149,14 +144,13 @@ export async function listProjects() { return listFirebaseProjects(); } -export async function initHosting(options: { spa: boolean; public: string }) { +export async function initHosting( + options: { spa: boolean; public?: string, useFrameworks: boolean } +) { await requireAuthWrapper(); let webFrameworksOptions = {}; - if (process.env.MONOSPACE_ENV) { - pluginLogger.debug('initHosting found MONOSPACE_ENV, ' - + 'setting web frameworks options'); - // TODO(hsubox76): Also allow VS Code users to enable this manually with a UI - setEnabled('webframeworks', true); + if (options.useFrameworks) { + pluginLogger.debug('Setting web frameworks options'); webFrameworksOptions = { // Should use auto-discovered framework useDiscoveredFramework: true, @@ -187,7 +181,7 @@ export async function emulatorsStart(emulatorUiSelections: EmulatorUiSelections) }); // Adjusts some options, export on exit can be a boolean or a path. commandUtils.setExportOnExitOptions(commandOptions as commandUtils.ExportOnExitOptions); - return startAllEmulators(commandOptions, /*showUi=*/ true); + return startAllEmulators(commandOptions, /*showUi=*/ true); } export async function stopEmulators() { @@ -204,8 +198,7 @@ export function getEmulatorUiUrl(): string | undefined { } export async function deployToHosting( - firebaseJSON: FirebaseConfig, - firebaseRC: FirebaseRC, + firebaseJSON: Config, deployTarget: string ) { if (!(await requireAuthWrapper())) { @@ -216,11 +209,8 @@ export async function deployToHosting( try { const options = { ...currentOptions }; // TODO(hsubox76): handle multiple hosting configs - if (!(firebaseJSON.hosting as HostingSingle).site) { - pluginLogger.debug('Calling getDefaultHostingSite() with options', inspect(options)); - (firebaseJSON.hosting as HostingSingle).site = - await getDefaultHostingSite(options); - } + pluginLogger.debug('Calling getDefaultHostingSite() with options', inspect(options)); + firebaseJSON.set('hosting', { ...firebaseJSON.get('hosting'), site: await getDefaultHostingSite(options) }); pluginLogger.debug('Calling getCommandOptions() with options', inspect(options)); const commandOptions = await getCommandOptions(firebaseJSON, options); pluginLogger.debug('Calling hosting deploy with command options', inspect(commandOptions)); diff --git a/firebase-vscode/src/config-files.ts b/firebase-vscode/src/config-files.ts new file mode 100644 index 00000000000..888491cb06d --- /dev/null +++ b/firebase-vscode/src/config-files.ts @@ -0,0 +1,146 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "vscode"; +import { workspace } from "vscode"; +import { ExtensionBrokerImpl } from "./extension-broker"; +import { updateOptions, currentOptions } from "./options"; +import { RC } from "../../src/rc"; +import { Config } from "../../src/config"; +import { pluginLogger } from "./logger-wrapper"; +import isEmpty from "lodash/isEmpty"; + +export function getRootFolders() { + if (!workspace) { + return []; + } + const folders = workspace.workspaceFolders + ? workspace.workspaceFolders.map((wf) => wf.uri.fsPath) + : []; + if (workspace.workspaceFile) { + folders.push(path.dirname(workspace.workspaceFile.fsPath)); + } + return Array.from(new Set(folders)); +} + +function getConfigPath(): string { + // Usually there's only one root folder unless someone is using a + // multi-root VS Code workspace. + // https://code.visualstudio.com/docs/editor/multi-root-workspaces + // We are trying to play it safe by assigning the cwd + // based on where a .firebaserc or firebase.json was found but if + // the user hasn't run firebase init there won't be one, and without + // a cwd we won't know where to put it. + const rootFolders = getRootFolders(); + for (const folder of rootFolders) { + if (fs.existsSync(path.join(folder, '.firebaserc')) + || fs.existsSync(path.join(folder, 'firebase.json'))) { + currentOptions.cwd = folder; + return folder; + } + } + currentOptions.cwd = rootFolders[0]; + return rootFolders[0]; +} + +/** + * Parse firebase.json and .firebaserc from the configured location, if they + * exist, and write to memory. + */ +export function readFirebaseConfigs(context: vscode.ExtensionContext) { + const configPath = getConfigPath(); + let firebaseRC: RC; + let firebaseJSON: Config; + try { + firebaseRC = RC.loadFile(path.join(configPath, '.firebaserc')); + } catch (e) { + pluginLogger.error(e.message); + throw e; + } + + // RC.loadFile doesn't throw if not found, it just returns an empty object + if (isEmpty(firebaseRC.data)) { + firebaseRC = null; + } + + try { + firebaseJSON = Config.load({ configPath: path.join(configPath, 'firebase.json') }); + } + catch (e) { + if (e.status === 404) { + firebaseJSON = null; + } else { + pluginLogger.error(e.message); + throw e; + } + } + updateOptions(context, firebaseJSON, firebaseRC); + return { firebaseJSON, firebaseRC }; + +} + +/** + * Read Firebase configs and then send it to webviews through the given broker + */ +export async function readAndSendFirebaseConfigs( + broker: ExtensionBrokerImpl, + context: vscode.ExtensionContext) { + const { firebaseJSON, firebaseRC } = readFirebaseConfigs(context); + broker.send("notifyFirebaseConfig", + { + firebaseJson: firebaseJSON?.data, firebaseRC: firebaseRC?.data + }); +} + +/** + * Write new default project to .firebaserc + */ +export async function updateFirebaseRCProject( + context: vscode.ExtensionContext, + alias: string, + projectId: string +) { + if (!currentOptions.rc) { + if (!currentOptions.cwd) { + currentOptions.cwd = getConfigPath(); + } + currentOptions.rc = new RC(path.join(currentOptions.cwd, ".firebaserc"), + {}); + } + currentOptions.rc.addProjectAlias(alias, projectId); + currentOptions.rc.save(); + updateOptions(context, undefined, currentOptions.rc); +} + +/** + * Set up a FileSystemWatcher for .firebaserc and firebase.json Also un-watch and re-watch when the + * configuration for where in the workspace the .firebaserc and firebase.json are. + */ +export function setupFirebaseJsonAndRcFileSystemWatcher( + broker: ExtensionBrokerImpl, + context: vscode.ExtensionContext +): vscode.Disposable { + // Create a new watcher + let watcher = newWatcher(); + + // Return a disposable that tears down a watcher if it's active + return { + dispose() { + watcher && watcher.dispose(); + }, + }; + + // HelperFunction to create a new watcher + function newWatcher() { + if (!currentOptions.cwd) { + return null; + } + + let watcher = workspace.createFileSystemWatcher( + path.join(currentOptions.cwd, "{firebase.json,.firebaserc}") + ); + watcher.onDidChange(async () => { + readAndSendFirebaseConfigs(broker, context); + }); + return watcher; + } +} diff --git a/firebase-vscode/src/logger-wrapper.ts b/firebase-vscode/src/logger-wrapper.ts index 40e9d9a0667..f736403f32a 100644 --- a/firebase-vscode/src/logger-wrapper.ts +++ b/firebase-vscode/src/logger-wrapper.ts @@ -12,4 +12,4 @@ for (const logLevel of logLevels) { }; } -setInquirerLogger(pluginLogger); \ No newline at end of file +setInquirerLogger(pluginLogger); diff --git a/firebase-vscode/src/options.ts b/firebase-vscode/src/options.ts index a8d98372bcc..a327a5bfd86 100644 --- a/firebase-vscode/src/options.ts +++ b/firebase-vscode/src/options.ts @@ -1,37 +1,19 @@ -import { FirebaseConfig } from "../../src/firebaseConfig"; -import { FirebaseRC } from "../common/firebaserc"; import { RC } from "../../src/rc"; -import { BaseOptions, Options } from "../../src/options"; +import { Options } from "../../src/options"; import { Command } from "../../src/command"; import { ExtensionContext } from "vscode"; import { setInquirerOptions } from "./stubs/inquirer-stub"; -import * as commandUtils from "../../src/emulator/commandUtils"; +import { Config } from "../../src/config"; /** * User-facing CLI options - * Passed to command.prepare() */ - -interface CliOptions extends Omit { - config: string; -} - -/** - * Final options passed to CLI command functions - * Result of command.prepare() - */ -interface CommandOptions extends Options { -} - -/** - * User-facing CLI options - */ -export let currentOptions: CliOptions & { isVSCE: boolean } = { +export let currentOptions: Options & { isVSCE: boolean } = { cwd: "", configPath: "", only: "", except: "", - config: "", + config: new Config({}), filteredTargets: [], force: true, @@ -55,24 +37,23 @@ export let currentOptions: CliOptions & { isVSCE: boolean } = { export function updateOptions( context: ExtensionContext, - firebaseJSON: FirebaseConfig, - firebaseRC: FirebaseRC + firebaseJSON: Config, + firebaseRC: RC ) { - // const config = new cliConfig(firebaseJSON, options); - // currentOptions.config = config; if (firebaseJSON) { + currentOptions.config = firebaseJSON; currentOptions.configPath = `${currentOptions.cwd}/firebase.json`; - if (firebaseJSON.hosting) { + if (firebaseJSON.has('hosting')) { currentOptions = { ...currentOptions, - ...firebaseJSON.hosting, + ...firebaseJSON.get('hosting'), }; } } else { currentOptions.configPath = ""; } if (firebaseRC) { - currentOptions.rc = new RC(`${currentOptions.cwd}/.firebaserc`, firebaseRC); + currentOptions.rc = firebaseRC; currentOptions.project = firebaseRC.projects?.default; } else { currentOptions.rc = null; @@ -88,15 +69,15 @@ export function updateOptions( * Mostly runs it through the CLI's command.prepare() options formatter. */ export async function getCommandOptions( - firebaseJSON: FirebaseConfig = {}, - options: CliOptions = currentOptions -): Promise { + firebaseJSON: Config, + options: Options = currentOptions +): Promise { // Use any string, it doesn't affect `prepare()`. const command = new Command("deploy"); - let newOptions = Object.assign(options); - if (firebaseJSON.hosting) { - newOptions = Object.assign(newOptions, firebaseJSON.hosting); + let newOptions = Object.assign(options, { config: options.configPath }); + if (firebaseJSON?.has('hosting')) { + newOptions = Object.assign(newOptions, firebaseJSON.get('hosting')); } await command.prepare(newOptions); - return newOptions as CommandOptions; + return newOptions as Options; } diff --git a/firebase-vscode/src/utils.ts b/firebase-vscode/src/utils.ts deleted file mode 100644 index e7a95b8d01a..00000000000 --- a/firebase-vscode/src/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as fs from "fs"; -import { FirebaseRC } from "../../src/firebaserc"; - -// TODO(hsubox76): use `loadRC` and `RC.save` from firebase-tools src/rc.ts -// for RC file operations -export async function writeFirebaseRCFile( - filename: string, - content: FirebaseRC -) { - fs.writeFileSync(filename, JSON.stringify(content, null, 2)); -} diff --git a/firebase-vscode/src/workflow.ts b/firebase-vscode/src/workflow.ts index fd404affe06..ce23f0b23d1 100644 --- a/firebase-vscode/src/workflow.ts +++ b/firebase-vscode/src/workflow.ts @@ -1,5 +1,4 @@ import * as path from "path"; -import * as fs from "fs"; import * as vscode from "vscode"; import { transports, format } from "winston"; import stripAnsi from "strip-ansi"; @@ -7,7 +6,6 @@ import { SPLAT } from "triple-beam"; import { ExtensionContext, workspace } from "vscode"; import { FirebaseProjectMetadata } from "../../src/types/project"; -import { writeFirebaseRCFile } from "./utils"; import { ExtensionBrokerImpl } from "./extension-broker"; import { deployToHosting, @@ -23,22 +21,26 @@ import { stopEmulators, } from "./cli"; import { User } from "../../src/types/auth"; -import { FirebaseRC } from "../../src/firebaserc"; -import { FirebaseConfig } from "../../src/firebaseConfig"; -import { currentOptions, updateOptions } from "./options"; -import { ServiceAccountUser } from "./types"; +import { currentOptions } from "./options"; import { selectProjectInMonospace } from "../../src/monospace"; import { setupLoggers, tryStringify } from "../../src/utils"; import { pluginLogger } from "./logger-wrapper"; import { logger } from '../../src/logger'; +import { discover } from "../../src/frameworks"; +import { setEnabled } from "../../src/experiments"; +import { + readAndSendFirebaseConfigs, + setupFirebaseJsonAndRcFileSystemWatcher, + updateFirebaseRCProject, + getRootFolders +} from "./config-files"; +import { ServiceAccountUser } from "../common/types"; -let firebaseRC: FirebaseRC | null = null; -let firebaseJSON: FirebaseConfig | null = null; -let extensionContext: ExtensionContext = null; let users: Array = []; let currentUserEmail = ""; // Stores a mapping from user email to list of projects for that user let projectsUserMapping = new Map(); +let channels = null; async function fetchUsers() { const accounts = await getAccounts(); @@ -49,7 +51,6 @@ async function fetchUsers() { * Get the user to select a project. */ async function promptUserForProject( - broker: ExtensionBrokerImpl, projects: FirebaseProjectMetadata[] ) { const items = projects.map(({ projectId }) => projectId); @@ -58,7 +59,7 @@ async function promptUserForProject( vscode.window.showQuickPick(items).then(async (projectId) => { const project = projects.find((p) => p.projectId === projectId); if (!project) { - if (firebaseRC?.projects?.default) { + if (currentOptions.rc?.projects?.default) { // Don't show an error message if a project was previously selected, // just do nothing. resolve(null); @@ -94,69 +95,33 @@ function updateCurrentUser( return currentUserEmail; } -function getRootFolders(): string[] { - if (!workspace) { - return []; - } - const folders = workspace.workspaceFolders - ? workspace.workspaceFolders.map((wf) => wf.uri.fsPath) - : []; - if (workspace.workspaceFile) { - folders.push(path.dirname(workspace.workspaceFile.fsPath)); - } - return Array.from(new Set(folders)); -} - -function getConfigFile(filename: string): T | null { - const rootFolders = getRootFolders(); - for (const folder of rootFolders) { - const jsonFilePath = path.join(folder, filename); - if (fs.existsSync(jsonFilePath)) { - const fileText = fs.readFileSync(jsonFilePath, "utf-8"); - try { - const result = JSON.parse(fileText); - currentOptions.cwd = folder; - return result; - } catch (e) { - pluginLogger.error(`Error parsing JSON in ${jsonFilePath}`); - return null; - } - } - } - // Usually there's only one root folder unless someone is using a - // multi-root VS Code workspace. - // https://code.visualstudio.com/docs/editor/multi-root-workspaces - // We were trying to play it safe up above by assigning the cwd - // based on where a .firebaserc or firebase.json was found but if - // the user hasn't run firebase init there won't be one, and without - // a cwd we won't know where to put it. - // - // TODO: prompt where we're going to save a new firebase config - // file before we do it so the user can change it - if (!currentOptions.cwd) { - currentOptions.cwd = rootFolders[0]; - } - return null; -} - -export function setupWorkflow( +export async function setupWorkflow( context: ExtensionContext, broker: ExtensionBrokerImpl ) { - extensionContext = context; - var shouldDebug: boolean; + // Get user-defined VSCode settings if workspace is found. + let shouldWriteDebug: boolean = false; + let debugLogPath: string = ''; + let useFrameworks: boolean = false; + let npmPath: string = ''; if (vscode.workspace.workspaceFolders) { - // Get user-defined VSCode settings. const workspaceConfig = workspace.getConfiguration( 'firebase', vscode.workspace.workspaceFolders[0].uri ); - shouldDebug = workspaceConfig.get('debug'); - } else { - shouldDebug = false; + shouldWriteDebug = workspaceConfig.get('debug'); + debugLogPath= workspaceConfig.get('debugLogPath'); + useFrameworks = workspaceConfig.get('useFrameworks'); + npmPath = workspaceConfig.get('npmPath'); + if (npmPath) { + process.env.PATH += `:${npmPath}`; + } } + if (useFrameworks) { + setEnabled('webframeworks', true); + } /** * Logging setup for logging to console and to file. */ @@ -165,30 +130,24 @@ export function setupWorkflow( setupLoggers(); // Only log to file if firebase.debug extension setting is true. - if (shouldDebug) { - // Re-implement file logger call from ../../src/bin/firebase.ts to not bring - // in the entire firebase.ts file - const rootFolders = getRootFolders(); - const filePath = path.join(rootFolders[0], 'firebase-plugin-debug.log'); - pluginLogger.info('Logging to path', filePath); - logger.add( - new transports.File({ - level: "debug", - filename: filePath, - format: format.printf((info) => { - const segments = [info.message, ...(info[SPLAT] || [])] - .map(tryStringify); - return `[${info.level}] ${stripAnsi(segments.join(" "))}`; - }), - }) - ); - } - // Read config files and store in memory. - readFirebaseConfigs(); - // Check current users state - fetchUsers(); - // Get hosting channels - fetchChannels(); + if (shouldWriteDebug) { + // Re-implement file logger call from ../../src/bin/firebase.ts to not bring + // in the entire firebase.ts file + const rootFolders = getRootFolders(); + const filePath = debugLogPath || path.join(rootFolders[0], 'firebase-plugin-debug.log'); + pluginLogger.info('Logging to path', filePath); + logger.add( + new transports.File({ + level: "debug", + filename: filePath, + format: format.printf((info) => { + const segments = [info.message, ...(info[SPLAT] || [])] + .map(tryStringify); + return `[${info.level}] ${stripAnsi(segments.join(" "))}`; + }), + }) + ); + } /** * Call pluginLogger with log arguments received from webview. @@ -197,7 +156,8 @@ export function setupWorkflow( pluginLogger[level]('(Webview)', ...args); }); - broker.on("getEnv", async () => { + broker.on("getInitialData", async () => { + // Env pluginLogger.debug(`Value of process.env.MONOSPACE_ENV: ` + `${process.env.MONOSPACE_ENV}`); broker.send("notifyEnv", { @@ -205,14 +165,24 @@ export function setupWorkflow( isMonospace: Boolean(process.env.MONOSPACE_ENV), } }); - }); - broker.on("getUsers", async () => { - if (users.length === 0) { - await fetchUsers(); - } + // Firebase JSON and RC + readAndSendFirebaseConfigs(broker, context); + + // User login state + await fetchUsers(); broker.send("notifyUsers", { users }); currentUserEmail = updateCurrentUser(users, broker); + if (users.length > 0) { + await fetchChannels(); + } + + // Project + if (currentOptions.rc?.projects?.default) { + broker.send("notifyProjectChanged", { + projectId: currentOptions.rc.projects.default + }); + } }); broker.on("logout", async ({ email }: { email: string }) => { @@ -227,16 +197,6 @@ export function setupWorkflow( } }); - broker.on("getSelectedProject", async () => { - // For now, just read the cached value. - // TODO: Extend this to reading from firebaserc - if (firebaseRC?.projects?.default) { - broker.send("notifyProjectChanged", - { projectId: firebaseRC?.projects?.default }); - } - fetchChannels(); - }); - broker.on("showMessage", async ({ msg, options }) => { vscode.window.showInformationMessage(msg, options); }); @@ -274,26 +234,32 @@ export function setupWorkflow( broker.on("hostingDeploy", async ({ target: deployTarget }) => { const { success, consoleUrl, hostingUrl } = await deployToHosting( - firebaseJSON, - firebaseRC, + currentOptions.config, deployTarget ); broker.send("notifyHostingDeploy", { success, consoleUrl, hostingUrl }); if (success) { - fetchChannels(); + fetchChannels(true); } }); - broker.on("getFirebaseJson", async () => { - readAndSendFirebaseConfigs(broker); + broker.on("promptUserForInput", async () => { + const response = await vscode.window.showInputBox({ + title: "New Preview Channel", + prompt: "Enter a name for the new preview channel" + }); + broker.send("notifyPreviewChannelResponse", { id: response }); }); context.subscriptions.push( - setupFirebaseJsonAndRcFileSystemWatcher(broker) + setupFirebaseJsonAndRcFileSystemWatcher(broker, context) ); - async function fetchChannels() { - const channels = await getChannels(firebaseJSON); + async function fetchChannels(force = false) { + if (force || !channels) { + pluginLogger.debug('Fetching hosting channels'); + channels = await getChannels(currentOptions.config); + }; broker.send("notifyChannels", { channels }); } @@ -347,41 +313,56 @@ export function setupWorkflow( projectsUserMapping.set(email, projects); } try { - projectId = await promptUserForProject(broker, projects); + projectId = await promptUserForProject(projects); } catch (e) { vscode.window.showErrorMessage(e.message); } } if (projectId) { - await updateFirebaseRC("default", projectId); + await updateFirebaseRCProject(context, "default", projectId); broker.send("notifyProjectChanged", { projectId }); - fetchChannels(); + fetchChannels(true); } } async function selectAndInitHosting({ projectId, singleAppSupport }) { - const options: vscode.OpenDialogOptions = { - canSelectMany: false, - openLabel: `Select distribution/public folder for ${projectId}`, - canSelectFiles: false, - canSelectFolders: true, - }; - const fileUri = await vscode.window.showOpenDialog(options); - if (fileUri && fileUri[0] && fileUri[0].fsPath) { - const publicFolderFull = fileUri[0].fsPath; - const publicFolder = publicFolderFull.substring( - currentOptions.cwd.length + 1 - ); + let discoveredFramework; + // Note: discover() takes a few seconds. No need to block users that don't + // have frameworks support enabled. + if (useFrameworks) { + discoveredFramework = useFrameworks && await discover(currentOptions.cwd, false); + pluginLogger.debug('Searching for a web framework in this project.'); + } + if (discoveredFramework) { + pluginLogger.debug('Detected web framework, launching frameworks init.'); await initHosting({ spa: singleAppSupport, - public: publicFolder, + useFrameworks: true }); - readAndSendFirebaseConfigs(broker); - broker.send("notifyHostingFolderReady", - { projectId, folderPath: currentOptions.cwd }); - - await fetchChannels(); + } else { + const options: vscode.OpenDialogOptions = { + canSelectMany: false, + openLabel: `Select distribution/public folder for ${projectId}`, + canSelectFiles: false, + canSelectFolders: true, + }; + const fileUri = await vscode.window.showOpenDialog(options); + if (fileUri && fileUri[0] && fileUri[0].fsPath) { + const publicFolderFull = fileUri[0].fsPath; + const publicFolder = publicFolderFull.substring( + currentOptions.cwd.length + 1 + ); + await initHosting({ + spa: singleAppSupport, + public: publicFolder, + useFrameworks: false + }); + } } + readAndSendFirebaseConfigs(broker, context); + broker.send("notifyHostingInitDone", + { projectId, folderPath: currentOptions.cwd }); + await fetchChannels(true); } broker.on( "launchEmulators", @@ -427,76 +408,3 @@ export function setupWorkflow( export async function onShutdown() { await stopEmulators(); } - -/** - * Parse firebase.json and .firebaserc from the configured location, if they - * exist, and write to memory. - */ -function readFirebaseConfigs() { - firebaseRC = getConfigFile(".firebaserc"); - firebaseJSON = getConfigFile("firebase.json"); - - updateOptions(extensionContext, firebaseJSON, firebaseRC); -} - -/** - * Read Firebase configs and then send it to webviews through the given broker - */ -async function readAndSendFirebaseConfigs(broker: ExtensionBrokerImpl) { - readFirebaseConfigs(); - broker.send("notifyFirebaseConfig", - { - firebaseJson: firebaseJSON, firebaseRC - }); -} - -/** - * Write new default project to .firebaserc - */ -async function updateFirebaseRC(alias: string, projectId: string) { - if (currentOptions.cwd) { - firebaseRC = { - ...firebaseRC, - projects: { - default: firebaseRC?.projects?.default || "", - ...(firebaseRC?.projects || {}), - [alias]: projectId, - }, - }; - writeFirebaseRCFile(`${currentOptions.cwd}/.firebaserc`, firebaseRC); - updateOptions(extensionContext, firebaseJSON, firebaseRC); - } -} - -/** - * Set up a FileSystemWatcher for .firebaserc and firebase.json Also un-watch and re-watch when the - * configuration for where in the workspace the .firebaserc and firebase.json are. - */ -function setupFirebaseJsonAndRcFileSystemWatcher( - broker: ExtensionBrokerImpl -): vscode.Disposable { - // Create a new watcher - let watcher = newWatcher(); - - // Return a disposable that tears down a watcher if it's active - return { - dispose() { - watcher && watcher.dispose(); - }, - }; - - // HelperFunction to create a new watcher - function newWatcher() { - if (!currentOptions.cwd) { - return null; - } - - let watcher = workspace.createFileSystemWatcher( - path.join(currentOptions.cwd, "{firebase.json,.firebaserc}") - ); - watcher.onDidChange(async () => { - readAndSendFirebaseConfigs(broker); - }); - return watcher; - } -} diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx index dfe0663244e..86a9ef0a3f6 100644 --- a/firebase-vscode/webviews/SidebarApp.tsx +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -1,10 +1,7 @@ -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import React, { useEffect, useState } from "react"; import { Spacer } from "./components/ui/Spacer"; -import { Body } from "./components/ui/Text"; import { broker } from "./globals/html-broker"; import { User } from "../../src/types/auth"; -import { PanelSection } from "./components/ui/PanelSection"; import { AccountSection } from "./components/AccountSection"; import { ProjectSection } from "./components/ProjectSection"; import { FirebaseConfig } from "../../src/firebaseConfig"; @@ -15,6 +12,7 @@ import { ChannelWithId } from "./messaging/types"; import { EmulatorPanel } from "./EmulatorPanel"; import { webLogger } from "./globals/web-logger"; +import { InitFirebasePanel } from "./components/InitPanel"; export function SidebarApp() { const [projectId, setProjectId] = useState(null); @@ -35,19 +33,15 @@ export function SidebarApp() { useEffect(() => { webLogger.debug("loading SidebarApp component"); - broker.send("getEnv"); - broker.send("getUsers"); - broker.send("getFirebaseJson"); - broker.send("getSelectedProject"); - broker.send("getChannels"); + broker.send("getInitialData"); broker.on("notifyEnv", ({ env }) => { - webLogger.debug("notifyEnv()"); + webLogger.debug(`notifyEnv() returned ${JSON.stringify(env)}`); setEnv(env); }); broker.on("notifyChannels", ({ channels }) => { - webLogger.debug("notifyChannels()"); + webLogger.debug(`notifyChannels() returned ${JSON.stringify(channels)}`); setChannels(channels); }); @@ -61,7 +55,7 @@ export function SidebarApp() { webLogger.debug("set firebase JSON"); } if (firebaseJson?.hosting) { - webLogger.debug("Detected hosting setup"); + webLogger.debug("Detected firebase.json"); setHostingOnboarded(true); broker.send("showMessage", { msg: "Auto-detected hosting setup in this folder", @@ -79,7 +73,7 @@ export function SidebarApp() { }); broker.on("notifyUsers", ({ users }) => { - webLogger.debug("notifyUsers()"); + webLogger.debug(`notifyUsers() returned ${JSON.stringify(users)}`); setAllUsers(users); }); @@ -93,14 +87,14 @@ export function SidebarApp() { setUserEmail(email); }); - broker.on("notifyHostingFolderReady", ({ projectId, folderPath }) => { - webLogger.debug(`notifyHostingFolderReady: ${projectId}, ${folderPath}`); + broker.on("notifyHostingInitDone", ({ projectId, folderPath }) => { + webLogger.debug(`notifyHostingInitDone: ${projectId}, ${folderPath}`); setHostingOnboarded(true); }); broker.on("notifyHostingDeploy", ({ success }) => { webLogger.debug(`notifyHostingDeploy: ${success}`); - setHostingState("deployed"); + setHostingState(success ? 'success' : 'failure'); }); }, []); @@ -156,18 +150,3 @@ export function SidebarApp() { ); } - -function InitFirebasePanel({ onHostingInit }: { onHostingInit: Function }) { - return ( - - Choose a path below to get started - - onHostingInit()}> - Host your web app - - - Free web hosting with a world-class CDN for peak performance - - - ); -} diff --git a/firebase-vscode/webviews/components/AccountSection.tsx b/firebase-vscode/webviews/components/AccountSection.tsx index 6b92a4a8d95..47bd62054ac 100644 --- a/firebase-vscode/webviews/components/AccountSection.tsx +++ b/firebase-vscode/webviews/components/AccountSection.tsx @@ -12,6 +12,7 @@ import { Label } from "./ui/Text"; import styles from "./AccountSection.scss"; import { ServiceAccountUser } from "../../common/types"; import { User } from "../../../src/types/auth"; +import { TEXT } from "../globals/ux-text"; export function AccountSection({ userEmail, @@ -25,24 +26,25 @@ export function AccountSection({ const [userDropdownVisible, toggleUserDropdown] = useState(false); const usersLoaded = !!allUsers; // Default: initial users check hasn't completed - let currentUserElement: ReactElement | string = "checking login"; + let currentUserElement: ReactElement | string = TEXT.LOGIN_PROGRESS; if (usersLoaded && !allUsers.length) { // Users loaded but no user was found if (isMonospace) { // Monospace: this is an error, should have found a workspace // service account - currentUserElement = "unable to find workspace service account"; + currentUserElement = TEXT.MONOSPACE_LOGIN_FAIL; } else { // VS Code: prompt user to log in with Google account - currentUserElement = ( broker.send("addUser")}> - Sign in with Google - ); + currentUserElement = ( + broker.send("addUser")}> + {TEXT.GOOGLE_SIGN_IN} + + ); } } else if (usersLoaded && allUsers.length > 0) { // Users loaded, at least one user was found - if (isMonospace && userEmail === 'service_account') { - // TODO(hsubox76): Figure out correct wording - currentUserElement = 'workspace logged in'; + if (isMonospace && userEmail === "service_account") { + currentUserElement = TEXT.MONOSPACE_LOGGED_IN; } else { currentUserElement = userEmail; } @@ -51,7 +53,7 @@ export function AccountSection({
    {!usersLoaded && (
    @@ -170,15 +187,17 @@ export function DeployPanel({ )} - {siteLink && ()} + {siteLink && ( + + )} diff --git a/firebase-vscode/webviews/components/InitPanel.tsx b/firebase-vscode/webviews/components/InitPanel.tsx new file mode 100644 index 00000000000..19de44d77b7 --- /dev/null +++ b/firebase-vscode/webviews/components/InitPanel.tsx @@ -0,0 +1,48 @@ +import { VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; +import cn from "classnames"; + +import { Body, Label } from "./ui/Text"; +import React, { useState } from "react"; +import { TEXT } from "../globals/ux-text"; +import { PanelSection } from "./ui/PanelSection"; +import { Spacer } from "./ui/Spacer"; +import styles from "../sidebar.entry.scss"; + +export function InitFirebasePanel({ + onHostingInit, +}: { + onHostingInit: Function; +}) { + const [ initInProgress, setInitInProgress ] = useState(false); + if (initInProgress) { + return ( + + +
    + + +
    +
    + ); + } + return ( + + { + onHostingInit(); + setInitInProgress(true); + }} + > + {TEXT.INIT_HOSTING_BUTTON} + + + {TEXT.INIT_HOSTING_DESCRIPTION} + + + ); +} diff --git a/firebase-vscode/webviews/components/ProjectSection.tsx b/firebase-vscode/webviews/components/ProjectSection.tsx index 108b8d9641b..40ca46c3b29 100644 --- a/firebase-vscode/webviews/components/ProjectSection.tsx +++ b/firebase-vscode/webviews/components/ProjectSection.tsx @@ -6,6 +6,7 @@ import { Label } from "./ui/Text"; import React from "react"; import styles from "./AccountSection.scss"; import { ExternalLink } from "./ui/ExternalLink"; +import { TEXT } from "../globals/ux-text"; export function ProjectSection({ userEmail, @@ -60,7 +61,7 @@ export function ConnectProject({ userEmail }: { userEmail: string | null }) { return ( <> initProjectSelection(userEmail)}> - Connect a Firebase project + {TEXT.CONNECT_FIREBASE_PROJECT} ); @@ -72,7 +73,7 @@ export function ProjectInfo({ projectId }: { projectId: string }) { {projectId} ); diff --git a/firebase-vscode/webviews/components/ui/SplitButton.scss b/firebase-vscode/webviews/components/ui/SplitButton.scss new file mode 100644 index 00000000000..951be50ce25 --- /dev/null +++ b/firebase-vscode/webviews/components/ui/SplitButton.scss @@ -0,0 +1,38 @@ +.split-button { + display: flex; + position: relative; +} + +.main-target, +.menu-target { + &:focus { + z-index: 1; + } +} + +.main-target { + flex: 1 1 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.menu-target { + position: relative; + padding: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + --divider-vert-margin: 4px; + --button-padding-horizontal: 4px; + + &::before { + position: absolute; + left: 0; + top: var(--divider-vert-margin); + width: 1px; + height: calc(100% - var(--divider-vert-margin) * 2); + content: ''; + background-color: var(--vscode-button-foreground); + opacity: 0.2; + pointer-events: none; + } +} diff --git a/firebase-vscode/webviews/components/ui/SplitButton.tsx b/firebase-vscode/webviews/components/ui/SplitButton.tsx new file mode 100644 index 00000000000..a60144cd57e --- /dev/null +++ b/firebase-vscode/webviews/components/ui/SplitButton.tsx @@ -0,0 +1,53 @@ +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; +import cn from "classnames"; +import React, { HTMLAttributes, PropsWithChildren, useState } from "react"; +import { Icon } from "./Icon"; +import styles from "./SplitButton.scss"; +import { PopupMenu } from "./popup-menu/PopupMenu"; + +type SplitButtonProps = PropsWithChildren< + HTMLAttributes & { + appearance?: "primary" | "secondary"; + onClick: Function; + popupMenuContent: React.ReactNode; + } +>; + +export const SplitButton: React.FC = ({ + children, + onClick, + className, + popupMenuContent, + appearance, + ...props +}) => { + const [menuOpen, setMenuOpen] = useState(false); + + return ( + <> +
    + {menuOpen && ( + setMenuOpen(false)}> + {popupMenuContent} + + )} + + {children} + + setMenuOpen(true)} + appearance={appearance || "secondary"} + {...(props as any)} + > + + +
    + + ); +}; diff --git a/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.tsx b/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.tsx index a1a6e7b3aa4..a77ceee7213 100644 --- a/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.tsx +++ b/firebase-vscode/webviews/components/ui/popup-menu/PopupMenu.tsx @@ -9,11 +9,13 @@ type PopupMenuProps = PropsWithChildren< HTMLAttributes & { show?: boolean; onClose: Function; + autoClose: boolean; } >; export const PopupMenu: FC> = ({ children, + autoClose, className, show, onClose, @@ -26,6 +28,9 @@ export const PopupMenu: FC> = ({
      { + autoClose && onClose(); + }} > {children}
    diff --git a/firebase-vscode/webviews/globals/ux-text.ts b/firebase-vscode/webviews/globals/ux-text.ts new file mode 100644 index 00000000000..d4d84521a1c --- /dev/null +++ b/firebase-vscode/webviews/globals/ux-text.ts @@ -0,0 +1,22 @@ +export const TEXT = { + INIT_HOSTING_BUTTON: "Host your Web App", + + INIT_HOSTING_DESCRIPTION: "Deploy your app with Firebase Hosting" + + ", a high-performance static web host backed by a global CDN", + + INIT_HOSTING_PROGRESS: "Initializing...", + + LOGIN_PROGRESS: "Checking login", + + MONOSPACE_LOGGED_IN: "Using default credentials", + + MONOSPACE_LOGIN_SELECTION_ITEM: "Default credentials", + + MONOSPACE_LOGIN_FAIL: "Unable to find default credentials", + + GOOGLE_SIGN_IN: "Sign in with Google", + + CONSOLE_LINK_DESCRIPTION: "Open in Firebase console", + + CONNECT_FIREBASE_PROJECT: "Connect a Firebase project" +}; diff --git a/firebase-vscode/webviews/tsconfig.json b/firebase-vscode/webviews/tsconfig.json index eb59f3c1eac..e689c6113ab 100644 --- a/firebase-vscode/webviews/tsconfig.json +++ b/firebase-vscode/webviews/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "None", + "module": "es2020", "moduleResolution": "Node", "target": "ES2020", "lib": ["ES2020", "DOM"], diff --git a/firebase-vscode/webviews/webview-types.ts b/firebase-vscode/webviews/webview-types.ts index ebe64febd7a..372d6ffbee6 100644 --- a/firebase-vscode/webviews/webview-types.ts +++ b/firebase-vscode/webviews/webview-types.ts @@ -1,2 +1,2 @@ -export type HostingState = null | "deployed" | "deploying"; +export type HostingState = null | "success" | "failure" | "deploying"; diff --git a/src/config.ts b/src/config.ts index 85ed4fa4d92..9da92c32d28 100644 --- a/src/config.ts +++ b/src/config.ts @@ -273,6 +273,7 @@ export class Config { throw new FirebaseError("Not in a Firebase app directory (could not locate firebase.json)", { exit: 1, + status: 404, }); } } diff --git a/src/deploy/hosting/prepare.ts b/src/deploy/hosting/prepare.ts index df55b527330..dabb448243e 100644 --- a/src/deploy/hosting/prepare.ts +++ b/src/deploy/hosting/prepare.ts @@ -12,6 +12,7 @@ import * as utils from "../../utils"; import { HostingSource, RunRewrite } from "../../firebaseConfig"; import * as backend from "../functions/backend"; import { ensureTargeted } from "../../functions/ensureTargeted"; +import { normalizeAndValidate } from "../../functions/projectConfig"; function handlePublicDirectoryFlag(options: HostingOptions & Options): void { // Allow the public directory to be overridden by the --public flag @@ -75,8 +76,20 @@ export async function addPinnedFunctionsToOnlyString( if (endpoint) { options.only = ensureTargeted(options.only, endpoint.codebase || "default", endpoint.id); } else { - // This endpoint is just being added in this push. We don't know what codebase it is. - options.only = ensureTargeted(options.only, r.function.functionId); + const functionsConfig = normalizeAndValidate(options.config.src.functions); + const codebasesFromConfig = [ + ...new Set(Object.values(functionsConfig).map((c) => c.codebase)), + ]; + if (codebasesFromConfig.length > 0) { + options.only = ensureTargeted( + options.only, + codebasesFromConfig[0], + r.function.functionId + ); + } else { + // This endpoint is just being added in this push. We don't know what codebase it is. + options.only = ensureTargeted(options.only, r.function.functionId); + } } addedFunctionsPerSite.push(r.function.functionId); } diff --git a/src/detectProjectRoot.ts b/src/detectProjectRoot.ts index de39911690a..a336b38d468 100644 --- a/src/detectProjectRoot.ts +++ b/src/detectProjectRoot.ts @@ -7,7 +7,10 @@ export function detectProjectRoot(options: { cwd?: string; configPath?: string } if (options.configPath) { const fullPath = resolve(projectRootDir, options.configPath); if (!fileExistsSync(fullPath)) { - throw new FirebaseError(`Could not load config file ${options.configPath}.`, { exit: 1 }); + throw new FirebaseError(`Could not load config file ${options.configPath}.`, { + exit: 1, + status: 404, + }); } return dirname(fullPath); diff --git a/src/functionsConfig.ts b/src/functionsConfig.ts index 7d984e8574e..169570640c8 100644 --- a/src/functionsConfig.ts +++ b/src/functionsConfig.ts @@ -113,7 +113,7 @@ export async function setVariablesRecursive( export async function materializeConfig(configName: string, output: any): Promise { const materializeVariable = async function (varName: string) { const variable = await runtimeconfig.variables.get(varName); - const id = exports.varNameToIds(variable.name); + const id = varNameToIds(variable.name); const key = id.config + "." + id.variable.split("/").join("."); _.set(output, key, variable.text); }; @@ -143,7 +143,7 @@ export async function materializeAll(projectId: string): Promise Date: Wed, 21 Jun 2023 15:24:09 -0700 Subject: [PATCH 1033/1699] Missed a few changes from comments (#6015) * Fixed a bug in the hosting config for emulators. Also fixed an issue where an empty folder creates an issue loading the side panel. * Missed some changes * Update firebase-vscode/webviews/components/EmulatorPanel.tsx Co-authored-by: joehan --------- Co-authored-by: joehan --- firebase-vscode/webviews/SidebarApp.tsx | 2 +- .../{ => components}/EmulatorPanel.tsx | 45 +++++++++++-------- 2 files changed, 28 insertions(+), 19 deletions(-) rename firebase-vscode/webviews/{ => components}/EmulatorPanel.tsx (87%) diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx index 86a9ef0a3f6..c5f9e9bd77d 100644 --- a/firebase-vscode/webviews/SidebarApp.tsx +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -9,7 +9,7 @@ import { ServiceAccountUser } from "../common/types"; import { DeployPanel } from "./components/DeployPanel"; import { HostingState } from "./webview-types"; import { ChannelWithId } from "./messaging/types"; -import { EmulatorPanel } from "./EmulatorPanel"; +import { EmulatorPanel } from "./components/EmulatorPanel"; import { webLogger } from "./globals/web-logger"; import { InitFirebasePanel } from "./components/InitPanel"; diff --git a/firebase-vscode/webviews/EmulatorPanel.tsx b/firebase-vscode/webviews/components/EmulatorPanel.tsx similarity index 87% rename from firebase-vscode/webviews/EmulatorPanel.tsx rename to firebase-vscode/webviews/components/EmulatorPanel.tsx index 3d35146a1a7..a894376b989 100644 --- a/firebase-vscode/webviews/EmulatorPanel.tsx +++ b/firebase-vscode/webviews/components/EmulatorPanel.tsx @@ -7,18 +7,18 @@ import { VSCodeTextField, } from "@vscode/webview-ui-toolkit/react"; import React, { useState } from "react"; -import { Spacer } from "./components/ui/Spacer"; -import { broker } from "./globals/html-broker"; -import { PanelSection } from "./components/ui/PanelSection"; -import { FirebaseConfig } from "../../src/firebaseConfig"; +import { Spacer } from "./ui/Spacer"; +import { broker } from "../globals/html-broker"; +import { PanelSection } from "./ui/PanelSection"; +import { FirebaseConfig } from "../../../src/firebaseConfig"; import { RunningEmulatorInfo, EmulatorUiSelections, -} from "../common/messaging/types"; +} from "../../common/messaging/types"; import { VSCodeDropdown } from "@vscode/webview-ui-toolkit/react"; import { VSCodeOption } from "@vscode/webview-ui-toolkit/react"; -import { EmulatorInfo } from "../../src/emulator/types"; -import { webLogger } from "./globals/web-logger"; +import { EmulatorInfo } from "../../../src/emulator/types"; +import { webLogger } from "../globals/web-logger"; const DEFAULT_EMULATOR_UI_SELECTIONS: EmulatorUiSelections = { projectId: "demo-something", @@ -41,7 +41,7 @@ export function EmulatorPanel({ if (!firebaseJson) { throw Error("Expected a valid FirebaseConfig."); } - var defaultState = DEFAULT_EMULATOR_UI_SELECTIONS; + const defaultState = DEFAULT_EMULATOR_UI_SELECTIONS; if (projectId) { defaultState.projectId = getProjectIdForMode(projectId, defaultState.mode); } @@ -83,8 +83,11 @@ export function EmulatorPanel({ webLogger.debug( `notifyEmulatorImportFolder received in sidebar: ${folder}` ); - emulatorUiSelections.importStateFolderPath = folder; - setEmulatorUiSelectionsAndSaveToWorkspace({ ...emulatorUiSelections }); // rerender clone + const newSelections = { + ...emulatorUiSelections, + importStateFolderPath: folder, + }; + setEmulatorUiSelectionsAndSaveToWorkspace(newSelections); }); function launchEmulators() { @@ -145,16 +148,22 @@ export function EmulatorPanel({ function emulatorModeChanged(event: React.ChangeEvent) { webLogger.debug("emulatorModeChanged: " + event.target.value); - const selections: EmulatorUiSelections = emulatorUiSelections; - selections.mode = event.target.value as typeof emulatorUiSelections.mode; - selections.projectId = getProjectIdForMode(projectId, selections.mode); - setEmulatorUiSelectionsAndSaveToWorkspace({...selections}); + const newSelections: EmulatorUiSelections = { ...emulatorUiSelections }; + newSelections.mode = event.target.value as typeof emulatorUiSelections.mode; + newSelections.projectId = getProjectIdForMode( + projectId, + newSelections.mode + ); + setEmulatorUiSelectionsAndSaveToWorkspace(newSelections); } function clearImportFolder() { console.log(`clearImportFolder`); - emulatorUiSelections.importStateFolderPath = ""; - setEmulatorUiSelectionsAndSaveToWorkspace({ ...emulatorUiSelections }); + const newSelections = { + ...emulatorUiSelections, + importStateFolderPath: "", + }; + setEmulatorUiSelectionsAndSaveToWorkspace(newSelections); } // Make it pretty for the screen. Filter out the logging emulator since it's @@ -261,7 +270,7 @@ export function EmulatorPanel({ /** * Formats a project ID with a demo prefix if we're in offline mode, or uses the - * regular ID if we're hosting. + * regular ID if we're in hosting only mode. */ function getProjectIdForMode( projectId: string | undefined, @@ -274,4 +283,4 @@ function getProjectIdForMode( return projectId; } return "demo-" + projectId; -} \ No newline at end of file +} From 05c3013131f42e9d61952a9da9e20da5a8a0e20f Mon Sep 17 00:00:00 2001 From: Chalo Salvador Date: Thu, 22 Jun 2023 15:29:49 +0200 Subject: [PATCH 1034/1699] Fix firebase emulators:start crashing (#6005) * Update proxyResponse method * Add proper types to proxyResponse function * Changelog * Changelog formatting * Update CHANGELOG.md * Update firebase-vscode/package-lock.json --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + firebase-vscode/package-lock.json | 4 +- src/frameworks/utils.ts | 110 +++++++++++++++++++++++++----- 3 files changed, 96 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d9ab78675..c93ac2e5b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Run lifecycle hooks for specific codebases. (#6011) +- Fixed issue causing `firebase emulators:start` to crash in Next.js apps (#6005) diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index a9525311ad3..56f8dbfade9 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-vscode", - "version": "0.0.22", + "version": "0.0.22-alpha.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-vscode", - "version": "0.0.22", + "version": "0.0.22-alpha.0", "dependencies": { "@vscode/codicons": "0.0.30", "@vscode/webview-ui-toolkit": "^1.2.1", diff --git a/src/frameworks/utils.ts b/src/frameworks/utils.ts index 6967534c740..299232a9572 100644 --- a/src/frameworks/utils.ts +++ b/src/frameworks/utils.ts @@ -63,20 +63,97 @@ export async function warnIfCustomBuildScript( } } -function proxyResponse(original: ServerResponse, next: () => void) { - return (response: IncomingMessage | ServerResponse) => { - const { statusCode, statusMessage } = response; - if (!statusCode) { - original.end(); - return; - } - if (statusCode === 404) { - return next(); - } - const headers = "getHeaders" in response ? response.getHeaders() : response.headers; - original.writeHead(statusCode, statusMessage, headers); - response.pipe(original); - }; +/** + * Proxy a HTTP response + * It uses the Proxy object to intercept the response and buffer it until the + * response is finished. This allows us to modify the response before sending + * it back to the client. + */ +export function proxyResponse( + req: IncomingMessage, + res: ServerResponse, + next: () => void +): ServerResponse { + const proxiedRes = new ServerResponse(req); + // Object to store the original response methods + const buffer: [ + string, + Parameters + ][] = []; + + // Proxy the response methods + // The apply handler is called when the method e.g. write, setHeader, etc. is called + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply + // The target is the original method + // The thisArg is the proxied response + // The args are the arguments passed to the method + proxiedRes.write = new Proxy(proxiedRes.write.bind(proxiedRes), { + apply: ( + target: ServerResponse["write"], + thisArg: ServerResponse, + args: Parameters + ) => { + // call the original write method on the proxied response + target.call(thisArg, ...args); + // store the method call in the buffer + buffer.push(["write", args]); + }, + }); + + proxiedRes.setHeader = new Proxy(proxiedRes.setHeader.bind(proxiedRes), { + apply: ( + target: ServerResponse["setHeader"], + thisArg: ServerResponse, + args: Parameters + ) => { + target.call(thisArg, ...args); + buffer.push(["setHeader", args]); + }, + }); + proxiedRes.removeHeader = new Proxy(proxiedRes.removeHeader.bind(proxiedRes), { + apply: ( + target: ServerResponse["removeHeader"], + thisArg: ServerResponse, + args: Parameters + ) => { + target.call(thisArg, ...args); + buffer.push(["removeHeader", args]); + }, + }); + proxiedRes.writeHead = new Proxy(proxiedRes.writeHead.bind(proxiedRes), { + apply: ( + target: ServerResponse["writeHead"], + thisArg: ServerResponse, + args: Parameters + ) => { + target.call(thisArg, ...args); + buffer.push(["writeHead", args]); + }, + }); + proxiedRes.end = new Proxy(proxiedRes.end.bind(proxiedRes), { + apply: ( + target: ServerResponse["end"], + thisArg: ServerResponse, + args: Parameters + ) => { + // call the original end method on the proxied response + target.call(thisArg, ...args); + // if the proxied response is a 404, call next to continue down the middleware chain + // otherwise, send the buffered response i.e. call the original response methods: write, setHeader, etc. + // and then end the response and clear the buffer + if (proxiedRes.statusCode === 404) { + next(); + } else { + for (const [fn, args] of buffer) { + (res as any)[fn](...args); + } + res.end(...args); + buffer.length = 0; + } + }, + }); + + return proxiedRes; } export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { @@ -127,9 +204,8 @@ export function simpleProxy(hostOrRequestHandler: string | RequestHandler) { originalRes.end(); }); } else { - await Promise.resolve(hostOrRequestHandler(originalReq, originalRes, next)); - const proxiedRes = new ServerResponse(originalReq); - proxyResponse(originalRes, next)(proxiedRes); + const proxiedRes = proxyResponse(originalReq, originalRes, next); + await hostOrRequestHandler(originalReq, proxiedRes, next); } }; } From 85303344a394ce4910d9d67a11b39e0d5fb76e7e Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 22 Jun 2023 11:38:36 -0700 Subject: [PATCH 1035/1699] Revert incorrectly included code (#6022) --- src/deploy/hosting/prepare.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/deploy/hosting/prepare.ts b/src/deploy/hosting/prepare.ts index dabb448243e..df55b527330 100644 --- a/src/deploy/hosting/prepare.ts +++ b/src/deploy/hosting/prepare.ts @@ -12,7 +12,6 @@ import * as utils from "../../utils"; import { HostingSource, RunRewrite } from "../../firebaseConfig"; import * as backend from "../functions/backend"; import { ensureTargeted } from "../../functions/ensureTargeted"; -import { normalizeAndValidate } from "../../functions/projectConfig"; function handlePublicDirectoryFlag(options: HostingOptions & Options): void { // Allow the public directory to be overridden by the --public flag @@ -76,20 +75,8 @@ export async function addPinnedFunctionsToOnlyString( if (endpoint) { options.only = ensureTargeted(options.only, endpoint.codebase || "default", endpoint.id); } else { - const functionsConfig = normalizeAndValidate(options.config.src.functions); - const codebasesFromConfig = [ - ...new Set(Object.values(functionsConfig).map((c) => c.codebase)), - ]; - if (codebasesFromConfig.length > 0) { - options.only = ensureTargeted( - options.only, - codebasesFromConfig[0], - r.function.functionId - ); - } else { - // This endpoint is just being added in this push. We don't know what codebase it is. - options.only = ensureTargeted(options.only, r.function.functionId); - } + // This endpoint is just being added in this push. We don't know what codebase it is. + options.only = ensureTargeted(options.only, r.function.functionId); } addedFunctionsPerSite.push(r.function.functionId); } From 87968f0d3b5a71ee7e6b89d4efa2dd633a2510f4 Mon Sep 17 00:00:00 2001 From: Sairam Sakhamuri Date: Thu, 22 Jun 2023 12:28:46 -0700 Subject: [PATCH 1036/1699] Init flow frameworks cli. (#6010) * Added init flow commands * change region name * Enable frameworkstacks api * Added code review changes * Revert unwanted changes * Revert unwanted changes * Revert unwanted changes * change according to project id * Removed unwanted statements related to projectId --- src/commands/init.ts | 10 ++++ src/experiments.ts | 6 +++ src/init/features/frameworks/constants.ts | 4 ++ src/init/features/frameworks/index.ts | 57 +++++++++++++++++++++++ src/init/features/index.ts | 1 + src/init/index.ts | 5 ++ 6 files changed, 83 insertions(+) create mode 100644 src/init/features/frameworks/constants.ts create mode 100644 src/init/features/frameworks/index.ts diff --git a/src/commands/init.ts b/src/commands/init.ts index 9a306947dc4..4987bc82e65 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -13,6 +13,7 @@ import { requireAuth } from "../requireAuth"; import * as fsutils from "../fsutils"; import * as utils from "../utils"; import { Options } from "../options"; +import { isEnabled } from "../experiments"; const homeDir = os.homedir(); @@ -71,6 +72,15 @@ const choices = [ checked: false, }, ]; + +if (isEnabled("frameworks")) { + choices.push({ + value: "frameworks", + name: "Frameworks: Get started with Frameworks projects.", + checked: false, + }); +} + const featureNames = choices.map((choice) => choice.value); const DESCRIPTION = `Interactively configure the current directory as a Firebase project or initialize new features in an already configured Firebase project directory. diff --git a/src/experiments.ts b/src/experiments.ts index 8ad9ff4e2b9..0d61ba57082 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -97,6 +97,12 @@ export const ALL_EXPERIMENTS = experiments({ "These commands are not meant for public consumption and may break or disappear " + "without a notice.", }, + + frameworks: { + shortDescription: "Allow CLI option for Frameworks", + default: true, + public: false, + }, }); export type ExperimentName = keyof typeof ALL_EXPERIMENTS; diff --git a/src/init/features/frameworks/constants.ts b/src/init/features/frameworks/constants.ts new file mode 100644 index 00000000000..90731f7858e --- /dev/null +++ b/src/init/features/frameworks/constants.ts @@ -0,0 +1,4 @@ +export const DEFAULT_REGION = "us-central1"; +export const ALLOWED_REGIONS = [{ name: "us-central1 (Iowa)", value: "us-central1" }]; +export const DEFAULT_DEPLOY_METHOD = "github"; +export const ALLOWED_DEPLOY_METHODS = [{ name: "Deploy using github", value: "github" }]; diff --git a/src/init/features/frameworks/index.ts b/src/init/features/frameworks/index.ts new file mode 100644 index 00000000000..8b75e5023c9 --- /dev/null +++ b/src/init/features/frameworks/index.ts @@ -0,0 +1,57 @@ +import * as clc from "colorette"; +import * as utils from "../../../utils"; +import { logger } from "../../../logger"; +import { promptOnce } from "../../../prompt"; +import { + DEFAULT_REGION, + ALLOWED_REGIONS, + DEFAULT_DEPLOY_METHOD, + ALLOWED_DEPLOY_METHODS, +} from "./constants"; + +/** + * Setup new frameworks project. + */ +export async function doSetup(setup: any): Promise { + setup.frameworks = {}; + + utils.logBullet("First we need a few details to create your service."); + + await promptOnce( + { + name: "serviceName", + type: "input", + default: "acme-inc-web", + message: "Create a name for your service [6-32 characters]", + }, + setup.frameworks + ); + + await promptOnce( + { + name: "region", + type: "list", + default: DEFAULT_REGION, + message: + "Please select a region " + + `(${clc.yellow("info")}: Your region determines where your backend is located):\n`, + choices: ALLOWED_REGIONS, + }, + setup.frameworks + ); + + utils.logSuccess(`Region set to ${setup.frameworks.region}.`); + + logger.info(clc.bold(`\n${clc.white("===")} Deploy Setup`)); + + await promptOnce( + { + name: "deployMethod", + type: "list", + default: DEFAULT_DEPLOY_METHOD, + message: "How do you want to deploy", + choices: ALLOWED_DEPLOY_METHODS, + }, + setup.frameworks + ); +} diff --git a/src/init/features/index.ts b/src/init/features/index.ts index a52bfd6f400..afbfa51f652 100644 --- a/src/init/features/index.ts +++ b/src/init/features/index.ts @@ -10,3 +10,4 @@ export { doSetup as extensions } from "./extensions"; export { doSetup as project } from "./project"; export { doSetup as remoteconfig } from "./remoteconfig"; export { initGitHub as hostingGithub } from "./hosting/github"; +export { doSetup as frameworks } from "./frameworks"; diff --git a/src/init/index.ts b/src/init/index.ts index 19565c19769..e3b7557b4df 100644 --- a/src/init/index.ts +++ b/src/init/index.ts @@ -4,6 +4,7 @@ import * as clc from "colorette"; import { FirebaseError } from "../error"; import { logger } from "../logger"; import * as features from "./features"; +import { isEnabled } from "../experiments"; export interface Setup { config: Record; @@ -31,6 +32,10 @@ const featureFns = new Map P ["hosting:github", features.hostingGithub], ]); +if (isEnabled("frameworks")) { + featureFns.set("frameworks", features.frameworks); +} + export async function init(setup: Setup, config: any, options: any): Promise { const nextFeature = setup.features?.shift(); if (nextFeature) { From 9330b35726a55eb997115ace86ff8a9192727422 Mon Sep 17 00:00:00 2001 From: Sairam Sakhamuri Date: Thu, 22 Jun 2023 12:52:43 -0700 Subject: [PATCH 1037/1699] Frameworks (#6012) * Initial commit to stacks api * Added more properties to the Stack and Build object * Added api calls for framework stacks * Changed naming * Changed naming * Review changes * Removed and used minimal fields * Moved frameworks api file --- src/api.ts | 4 ++ src/api/frameworks.ts | 117 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/api/frameworks.ts diff --git a/src/api.ts b/src/api.ts index c20ead3091b..0a362e63758 100644 --- a/src/api.ts +++ b/src/api.ts @@ -173,6 +173,10 @@ export const serviceUsageOrigin = utils.envOverride( "FIREBASE_SERVICE_USAGE_URL", "https://serviceusage.googleapis.com" ); +export const frameworksOrigin = utils.envOverride( + "FRAMEWORKS_URL", + "https://placeholder.googleapis.com" +); export const githubOrigin = utils.envOverride("GITHUB_URL", "https://github.com"); export const githubApiOrigin = utils.envOverride("GITHUB_API_URL", "https://api.github.com"); export const secretManagerOrigin = utils.envOverride( diff --git a/src/api/frameworks.ts b/src/api/frameworks.ts new file mode 100644 index 00000000000..30fcc41bd66 --- /dev/null +++ b/src/api/frameworks.ts @@ -0,0 +1,117 @@ +import { Client } from "../apiv2"; +import { frameworksOrigin } from "../api"; + +export const API_VERSION = "v1"; + +const client = new Client({ + urlPrefix: frameworksOrigin, + auth: true, + apiVersion: API_VERSION, +}); + +export type State = "BUILDING" | "BUILD" | "DEPLOYING" | "READY" | "FAILED"; + +interface Codebase { + repository?: string; + rootDirectory: string; +} + +/** A Stack, the primary resource of Frameworks. */ +interface Stack { + name: string; + mode?: string; + codebase: Codebase; + labels: Record; + createTime: string; + updateTime: string; + uri: string; +} + +export type StackOutputOnlyFields = "createTime" | "updateTime" | "uri"; + +interface Build { + name: string; + state: State; + error: Status; + image: string; + source: BuildSource; + buildLogsUri: string; + createTime: Date; + updateTime: Date; + sourceRef: string; +} + +export type BuildOutputOnlyFields = "createTime" | "updateTime" | "sourceRef"; + +interface BuildSource { + codeBaseSource?: CodebaseSource; +} + +interface Status { + code: number; + message: string; + details: any[]; +} + +interface CodebaseSource { + // oneof reference + branch: string; + commit: string; + tag: string; + // end oneof reference +} + +export interface OperationMetadata { + createTime: string; + endTime: string; + target: string; + verb: string; + statusDetail: string; + cancelRequested: boolean; + apiVersion: string; +} + +export interface Operation { + name: string; + metadata?: OperationMetadata; + done: boolean; + // oneof result + error?: Status; + response?: any; + // end oneof result +} + +/** + * Creates a new Stack in a given project and location. + */ +export async function createStack( + projectId: string, + location: string, + stackId: string, + stack: Stack +): Promise { + const res = await client.post, Operation>( + `projects/${projectId}/locations/${location}/stacks`, + stack, + { queryParams: { stackId } } + ); + return res.body; +} + +/** + * Creates a new Build in a given project and location. + */ +export async function createBuild( + projectId: string, + location: string, + stackId: string, + buildId: string, + build: Build +): Promise { + const res = await client.post, Operation>( + `projects/${projectId}/locations/${location}/stacks/${stackId}/builds`, + build, + { queryParams: { buildId } } + ); + return res.body; +} From 728d07da7304c382b4e34be2c634b269b2fd1558 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 22 Jun 2023 16:13:21 -0700 Subject: [PATCH 1038/1699] Switched most uses of track to GA4 (#6016) * Switched most uses of track to GA4 * Move duration out of params, and improve debug logging slightly --- src/auth.ts | 8 +- src/command.ts | 25 ++- src/commands/ext-install.ts | 13 +- src/deploy/functions/checkIam.ts | 7 +- src/deploy/hosting/prepare.ts | 6 +- src/deploy/index.ts | 21 ++- src/emulator/controller.ts | 4 +- src/emulator/functionsEmulator.ts | 4 +- src/ensureApiEnabled.ts | 6 +- src/extensions/paramHelper.ts | 5 - src/test/deploy/hosting/prepare.spec.ts | 8 +- src/track.ts | 206 ++++++++++++++++-------- 12 files changed, 207 insertions(+), 106 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 56e5f31cdd9..4ab586b3c0e 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -19,7 +19,7 @@ import * as scopes from "./scopes"; import { clearCredentials } from "./defaultCredentials"; import { v4 as uuidv4 } from "uuid"; import { randomBytes, createHash } from "crypto"; -import { track } from "./track"; +import { trackGA4 } from "./track"; import { authOrigin, authProxyOrigin, @@ -444,7 +444,7 @@ async function loginRemotely(): Promise { codeVerifier ); - void track("login", "google_remote"); + void trackGA4("login", { method: "google_remote" }); return { user: jwt.decode(tokens.id_token!) as User, @@ -468,7 +468,7 @@ async function loginWithLocalhostGoogle(port: number, userHint?: string): Promis getTokensFromAuthorizationCode ); - void track("login", "google_localhost"); + void trackGA4("login", { method: "google_localhost" }); // getTokensFromAuthoirzationCode doesn't handle the --token case, so we know we'll // always have an id_token. return { @@ -489,7 +489,7 @@ async function loginWithLocalhostGitHub(port: number): Promise { successTemplate, getGithubTokensFromAuthorizationCode ); - void track("login", "google_localhost"); + void trackGA4("login", { method: "github_localhost" }); return tokens; } diff --git a/src/command.ts b/src/command.ts index 86edffbc791..1d14255071e 100644 --- a/src/command.ts +++ b/src/command.ts @@ -8,7 +8,7 @@ import { loadRC } from "./rc"; import { Config } from "./config"; import { configstore } from "./configstore"; import { detectProjectRoot } from "./detectProjectRoot"; -import { track, trackEmulator } from "./track"; +import { trackEmulator, trackGA4 } from "./track"; import { selectAccount, setActiveAccount } from "./auth"; import { getFirebaseProject } from "./management/projects"; import { requireAuth } from "./requireAuth"; @@ -202,7 +202,12 @@ export class Command { ); } const duration = Math.floor((process.uptime() - start) * 1000); - const trackSuccess = track(this.name, "success", duration); + const trackSuccess = trackGA4("command_execution", { + command_name: this.name, + result: "success", + duration, + interactive: getInheritedOption(options, "nonInteractive") ? "false" : "true", + }); if (!isEmulator) { await withTimeout(5000, trackSuccess); } else { @@ -236,8 +241,15 @@ export class Command { await withTimeout( 5000, Promise.all([ - track(this.name, "error", duration), - track(err.exit === 1 ? "Error (User)" : "Error (Unexpected)", "", duration), + trackGA4( + "command_execution", + { + command_name: this.name, + result: "error", + interactive: getInheritedOption(options, "nonInteractive") ? "false" : "true", + }, + duration + ), isEmulator ? trackEmulator("command_error", { command_name: this.name, @@ -400,7 +412,10 @@ export function validateProjectId(project: string): void { if (PROJECT_ID_REGEX.test(project)) { return; } - track("Project ID Check", "invalid"); + trackGA4("error", { + error_type: "Error (User)", + details: "Invalid project ID", + }); const invalidMessage = "Invalid project id: " + clc.bold(project) + "."; if (project.toLowerCase() !== project) { // Attempt to be more helpful in case uppercase letters are used. diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index d4fe999a397..35e55d2d09c 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -27,7 +27,7 @@ import { import { getRandomString } from "../extensions/utils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; -import { track } from "../track"; +import { trackGA4 } from "../track"; import { confirm } from "../prompt"; import { Options } from "../options"; import * as manifest from "../extensions/manifest"; @@ -96,11 +96,18 @@ export const command = new Command("ext:install [extensionName]") // Should parse spec locally so we don't need project ID. source = await createSourceFromLocation(needProjectId({ projectId }), extensionName); await displayExtInfo(extensionName, "", source.spec); - void track("Extension Install", "Install by Source", options.interactive ? 1 : 0); + void trackGA4("extension_added_to_manifest", { + published: "local", + interactive: options.nonInteractive ? "false" : "true", + }); } else { - void track("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0); extensionName = await canonicalizeRefInput(extensionName); extensionVersion = await extensionsApi.getExtensionVersion(extensionName); + + void trackGA4("extension_added_to_manifest", { + published: extensionVersion.listing?.state === "APPROVED" ? "published" : "uploaded", + interactive: options.nonInteractive ? "false" : "true", + }); await infoExtensionVersion({ extensionName, extensionVersion, diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index 8b4a0d1d692..20f61599ad2 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -8,7 +8,7 @@ import { flattenArray } from "../../functional"; import * as iam from "../../gcp/iam"; import * as args from "./args"; import * as backend from "./backend"; -import { track } from "../../track"; +import { trackGA4 } from "../../track"; import * as utils from "../../utils"; import { getIamPolicy, setIamPolicy } from "../../gcp/resourceManager"; @@ -101,7 +101,10 @@ export async function checkHttpIam( } if (!passed) { - void track("Error (User)", "deploy:functions:http_create_missing_iam"); + void trackGA4("error", { + error_type: "Error (User)", + details: "deploy:functions:http_create_missing_iam", + }); throw new FirebaseError( `Missing required permission on project ${bold( context.projectId diff --git a/src/deploy/hosting/prepare.ts b/src/deploy/hosting/prepare.ts index df55b527330..0a6223c39bf 100644 --- a/src/deploy/hosting/prepare.ts +++ b/src/deploy/hosting/prepare.ts @@ -7,7 +7,7 @@ import { Context } from "./context"; import { Options } from "../../options"; import { HostingOptions } from "../../hosting/options"; import { assertExhaustive, zipIn } from "../../functional"; -import { track } from "../../track"; +import { trackGA4 } from "../../track"; import * as utils from "../../utils"; import { HostingSource, RunRewrite } from "../../firebaseConfig"; import * as backend from "../functions/backend"; @@ -137,7 +137,9 @@ export async function prepare(context: Context, options: HostingOptions & Option labels, }; const [, versionName] = await Promise.all([ - track("hosting_deploy", config.webFramework || "classic"), + trackGA4("hosting_version", { + framework: config.webFramework || "classic", + }), api.createVersion(config.site, version), ]); return versionName; diff --git a/src/deploy/index.ts b/src/deploy/index.ts index 54a64b571cb..99d2bb5eccb 100644 --- a/src/deploy/index.ts +++ b/src/deploy/index.ts @@ -2,11 +2,11 @@ import * as clc from "colorette"; import { logger } from "../logger"; import { hostingOrigin } from "../api"; import { bold, underline, white } from "colorette"; -import { has, includes, each } from "lodash"; +import { includes, each } from "lodash"; import { needProjectId } from "../projectUtils"; import { logBullet, logSuccess, consoleUrl, addSubdomain } from "../utils"; import { FirebaseError } from "../error"; -import { track } from "../track"; +import { AnalyticsParams, trackGA4 } from "../track"; import { lifecycleHooks } from "./lifecycleHooks"; import * as experiments from "../experiments"; import * as HostingTarget from "./hosting"; @@ -119,12 +119,19 @@ export const deploy = async function ( await chain(releases, context, options, payload); await chain(postdeploys, context, options, payload); - if (has(options, "config.notes.databaseRules")) { - await track("Rules Deploy", options.config.notes.databaseRules); - } - const duration = Date.now() - startTime; - await track("Product Deploy", [...targetNames].sort().join(","), duration); + const analyticsParams: AnalyticsParams = { + interactive: options.nonInteractive ? "false" : "true", + }; + + Object.keys(TARGETS).reduce((accum, t) => { + accum[t] = "false"; + return accum; + }, analyticsParams); + for (const t of targetNames) { + analyticsParams[t] = "true"; + } + await trackGA4("product_deploy", analyticsParams, duration); logger.info(); logSuccess(bold(underline("Deploy complete!"))); diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index a837d5d2d7d..7505bf9ea81 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -4,7 +4,7 @@ import * as path from "path"; import * as fsConfig from "../firestore/fsConfig"; import { logger } from "../logger"; -import { track, trackEmulator } from "../track"; +import { trackEmulator } from "../track"; import * as utils from "../utils"; import { EmulatorRegistry } from "./registry"; import { @@ -401,7 +401,6 @@ export async function startAll( const name = instance.getName(); // Log the command for analytics - void track("Emulator Run", name); void trackEmulator("emulator_run", { emulator_name: name, is_demo_project: String(isDemoProject), @@ -421,7 +420,6 @@ export async function startAll( // since we originally mistakenly reported emulators:start events // for each emulator, by reporting the "hub" we ensure that our // historical data can still be viewed. - void track("emulators:start", "hub"); await startEmulator(hub); } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index f350d2d6d33..a3f93206585 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -11,7 +11,7 @@ import { EventEmitter } from "events"; import { Account } from "../types/auth"; import { logger } from "../logger"; -import { track, trackEmulator } from "../track"; +import { trackEmulator } from "../track"; import { Constants } from "./constants"; import { EmulatorInfo, EmulatorInstance, Emulators, FunctionsExecutionMode } from "./types"; import * as chokidar from "chokidar"; @@ -63,7 +63,6 @@ import { resolveBackend } from "../deploy/functions/build"; import { setEnvVarsForEmulators } from "./env"; import { runWithVirtualEnv } from "../functions/python"; -const EVENT_INVOKE = "functions:invoke"; // event name for UA const EVENT_INVOKE_GA4 = "functions_invoke"; // event name GA4 (alphanumertic) /* @@ -1551,7 +1550,6 @@ export class FunctionsEmulator implements EmulatorInstance { } } // For analytics, track the invoked service - void track(EVENT_INVOKE, getFunctionService(trigger)); void trackEmulator(EVENT_INVOKE_GA4, { function_service: getFunctionService(trigger), }); diff --git a/src/ensureApiEnabled.ts b/src/ensureApiEnabled.ts index 48d0044f046..d0da2ba8b21 100644 --- a/src/ensureApiEnabled.ts +++ b/src/ensureApiEnabled.ts @@ -1,6 +1,6 @@ import { bold } from "colorette"; -import { track } from "./track"; +import { trackGA4 } from "./track"; import { serviceUsageOrigin } from "./api"; import { Client } from "./apiv2"; import * as utils from "./utils"; @@ -91,7 +91,9 @@ async function pollCheckEnabled( }); const isEnabled = await check(projectId, apiName, prefix, silent); if (isEnabled) { - void track("api_enabled", apiName); + void trackGA4("api_enabled", { + api_name: apiName, + }); return; } if (!silent) { diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index 8231d69ebaf..aca46a91d4d 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -7,7 +7,6 @@ import { logger } from "../logger"; import { ExtensionInstance, ExtensionSpec, Param } from "./types"; import { getFirebaseProjectParams, substituteParams } from "./extensionsHelper"; import * as askUserForParam from "./askUserForParam"; -import { track } from "../track"; import * as env from "../functions/env"; import { cloneDeep } from "../utils"; @@ -111,8 +110,6 @@ export async function getParams(args: { reconfiguring: !!args.reconfiguring, }); } - const paramNames = Object.keys(params); - void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length); return params; } @@ -137,8 +134,6 @@ export async function getParamsForUpdate(args: { instanceId: args.instanceId, }); } - const paramNames = Object.keys(params); - void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length); return params; } diff --git a/src/test/deploy/hosting/prepare.spec.ts b/src/test/deploy/hosting/prepare.spec.ts index c8041ad9aba..4ddb7b661a0 100644 --- a/src/test/deploy/hosting/prepare.spec.ts +++ b/src/test/deploy/hosting/prepare.spec.ts @@ -104,7 +104,9 @@ describe("hosting prepare", () => { }; await prepare(context, options); - expect(trackingStub.track).to.have.been.calledOnceWith("hosting_deploy", "fake-framework"); + expect(trackingStub.trackGA4).to.have.been.calledOnceWith("hosting_version", { + framework: "fake-framework", + }); expect(hostingStub.createVersion).to.have.been.calledOnce; expect(context.hosting).to.deep.equal({ deploys: [ @@ -138,7 +140,9 @@ describe("hosting prepare", () => { }; await prepare(context, options); - expect(trackingStub.track).to.have.been.calledOnceWith("hosting_deploy", "classic"); + expect(trackingStub.trackGA4).to.have.been.calledOnceWith("hosting_version", { + framework: "classic", + }); expect(hostingStub.createVersion).to.have.been.calledOnce; expect(context.hosting).to.deep.equal({ deploys: [ diff --git a/src/track.ts b/src/track.ts index 65d3bcdd817..c1aa68eaf33 100644 --- a/src/track.ts +++ b/src/track.ts @@ -7,11 +7,36 @@ import { configstore } from "./configstore"; import { logger } from "./logger"; const pkg = require("../package.json"); -// The ID identifying the GA4 property for the Emulator Suite only. Should only -// be used in Emulator UI and emulator-related commands (e.g. emulators:start). -export const EMULATOR_GA4_MEASUREMENT_ID = - process.env.FIREBASE_EMULATOR_GA4_MEASUREMENT_ID || "G-KYP2JMPFC0"; - +type cliEventNames = + | "command_execution" + | "product_deploy" + | "error" + | "login" + | "api_enabled" + | "hosting_version" + | "extension_added_to_manifest"; +type GA4Property = "cli" | "emulator"; +interface GA4Info { + measurementId: string; + apiSecret: string; + clientIdKey: string; + currentSession?: AnalyticsSession; +} +export const GA4_PROPERTIES: Record = { + // Info for the GA4 property for the rest of the CLI. + cli: { + measurementId: process.env.FIREBASE_CLI_GA4_MEASUREMENT_ID || "G-PDN0QWHQJR", + apiSecret: process.env.FIREBASE_CLI_GA4_API_SECRET || "LSw5lNxhSFSWeB6aIzJS2w", + clientIdKey: "analytics-uuid", + }, + // Info for the GA4 property for the Emulator Suite only. Should only + // be used in Emulator UI and emulator-related commands (e.g. emulators:start). + emulator: { + measurementId: process.env.FIREBASE_EMULATOR_GA4_MEASUREMENT_ID || "G-KYP2JMPFC0", + apiSecret: process.env.FIREBASE_EMULATOR_GA4_API_SECRET || "2V_zBYc4TdeoppzDaIu0zw", + clientIdKey: "emulator-analytics-clientId", + }, +}; /** * UA is enabled only if: * 1) Entrypoint to the code is Firebase CLI (not require("firebase-tools")). @@ -21,55 +46,9 @@ export function usageEnabled(): boolean { return !!process.env.IS_FIREBASE_CLI && !!configstore.get("usage"); } -// The Tracking ID for the Universal Analytics property for all of the CLI -// including emulator-related commands (double-tracked for historical reasons) -// but excluding Emulator UI. -// TODO: Upgrade to GA4 before July 1, 2023. See: -// https://support.google.com/analytics/answer/11583528 -const FIREBASE_ANALYTICS_UA = process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3"; - -let visitor: ua.Visitor; - -function ensureUAVisitor(): void { - if (!visitor) { - // Identifier for the client (UUID) in the CLI UA. - let anonId = configstore.get("analytics-uuid") as string; - if (!anonId) { - anonId = uuidV4(); - configstore.set("analytics-uuid", anonId); - } - - visitor = ua(FIREBASE_ANALYTICS_UA, anonId, { - strictCidFormat: false, - https: true, - }); - - visitor.set("cd1", process.platform); // Platform - visitor.set("cd2", process.version); // NodeVersion - visitor.set("cd3", process.env.FIREPIT_VERSION || "none"); // FirepitVersion - } -} - -export function track(action: string, label: string, duration = 0): Promise { - ensureUAVisitor(); - return new Promise((resolve) => { - if (usageEnabled() && configstore.get("tokens")) { - visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(() => { - // we could handle errors here, but we won't - resolve(); - }); - } else { - resolve(); - } - }); -} - -const EMULATOR_GA4_API_SECRET = - process.env.FIREBASE_EMULATOR_GA4_API_SECRET || "2V_zBYc4TdeoppzDaIu0zw"; - // Prop name length must <= 24 and cannot begin with google_/ga_/firebase_. // https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=firebase#reserved_parameter_names -const EMULATOR_GA4_USER_PROPS = { +const GA4_USER_PROPS = { node_platform: { value: process.platform, }, @@ -97,6 +76,11 @@ export interface AnalyticsParams { /** The elapsed time in milliseconds (e.g. for command runs) (param for custom metrics) */ duration?: number; + /** The result (success or error) of a command */ + result?: string; + + /** Whether the command was run in interactive or noninteractive mode */ + interactive?: string; /** * One-off params (that may be used for custom params / metrics later). * @@ -110,6 +94,24 @@ export interface AnalyticsParams { [key: string]: string | number | undefined; } +export async function trackGA4( + eventName: cliEventNames, + params: AnalyticsParams, + duration: number = 1 // Default to 1ms duration so that events show up in realtime view. +): Promise { + const session = cliSession(); + if (!session) { + return; + } + return _ga4Track({ + session, + apiSecret: GA4_PROPERTIES.cli.apiSecret, + eventName, + params, + duration, + }); +} + /** * Record an emulator-related event for Analytics. * @@ -134,11 +136,29 @@ export async function trackEmulator(eventName: string, params?: AnalyticsParams) // staring at the terminal and waiting for the command to finish also counts.) const oldTotalEngagementSeconds = session.totalEngagementSeconds; session.totalEngagementSeconds = process.uptime(); + const duration = session.totalEngagementSeconds - oldTotalEngagementSeconds; + return _ga4Track({ + session, + apiSecret: GA4_PROPERTIES.emulator.apiSecret, + eventName, + params, + duration, + }); +} + +async function _ga4Track(args: { + session: AnalyticsSession; + apiSecret: string; + eventName: string; + params?: AnalyticsParams; + duration?: number; +}): Promise { + const { session, apiSecret, eventName, params, duration } = args; // Memorize and set command_name throughout the session. session.commandName = params?.command_name || session.commandName; - const search = `?api_secret=${EMULATOR_GA4_API_SECRET}&measurement_id=${session.measurementId}`; + const search = `?api_secret=${apiSecret}&measurement_id=${session.measurementId}`; const validate = session.validateOnly ? "debug/" : ""; const url = `https://www.google-analytics.com/${validate}mp/collect${search}`; const body = { @@ -147,7 +167,7 @@ export async function trackEmulator(eventName: string, params?: AnalyticsParams) timestamp_micros: `${Date.now()}000`, client_id: session.clientId, user_properties: { - ...EMULATOR_GA4_USER_PROPS, + ...GA4_USER_PROPS, java_major_version: session.javaMajorVersion ? { value: session.javaMajorVersion } : undefined, @@ -165,10 +185,7 @@ export async function trackEmulator(eventName: string, params?: AnalyticsParams) // https://support.google.com/analytics/answer/11109416?hl=en // Additional engagement time since last event, in microseconds. - engagement_time_msec: (session.totalEngagementSeconds - oldTotalEngagementSeconds) - .toFixed(3) - .replace(".", "") - .replace(/^0+/, ""), // trim leading zeros + engagement_time_msec: (duration ?? 0).toFixed(3).replace(".", "").replace(/^0+/, ""), // trim leading zeros // https://support.google.com/analytics/answer/7201382?hl=en // To turn debug mode off, `debug_mode` must be left out not `false`. @@ -180,7 +197,11 @@ export async function trackEmulator(eventName: string, params?: AnalyticsParams) ], }; if (session.validateOnly) { - logger.info(`Sending Analytics for event ${eventName}`, params, body); + logger.info( + `Sending Analytics for event ${eventName} to property ${session.measurementId}`, + params, + body + ); } try { const response = await fetch(url, { @@ -240,6 +261,14 @@ export interface AnalyticsSession { } export function emulatorSession(): AnalyticsSession | undefined { + return session("emulator"); +} + +export function cliSession(): AnalyticsSession | undefined { + return session("cli"); +} + +function session(propertyName: GA4Property): AnalyticsSession | undefined { const validateOnly = !!process.env.FIREBASE_CLI_MP_VALIDATE; if (!usageEnabled()) { if (validateOnly) { @@ -247,15 +276,15 @@ export function emulatorSession(): AnalyticsSession | undefined { } return; } - if (!currentEmulatorSession) { - let clientId: string | undefined = configstore.get("emulator-analytics-clientId"); + const property = GA4_PROPERTIES[propertyName]; + if (!property.currentSession) { + let clientId: string | undefined = configstore.get(property.clientIdKey); if (!clientId) { clientId = uuidV4(); - configstore.set("emulator-analytics-clientId", clientId); + configstore.set(property.clientIdKey, clientId); } - - currentEmulatorSession = { - measurementId: EMULATOR_GA4_MEASUREMENT_ID, + property.currentSession = { + measurementId: property.measurementId, clientId, // This must be an int64 string, but only ~50 bits are generated here @@ -268,11 +297,9 @@ export function emulatorSession(): AnalyticsSession | undefined { validateOnly, }; } - return currentEmulatorSession; + return property.currentSession; } -let currentEmulatorSession: AnalyticsSession | undefined = undefined; - function isDebugMode(): boolean { const account = getGlobalDefaultAccount(); if (account?.user.email.endsWith("@google.com")) { @@ -289,3 +316,46 @@ function isDebugMode(): boolean { } return false; } + +// The Tracking ID for the Universal Analytics property for all of the CLI +// including emulator-related commands (double-tracked for historical reasons) +// but excluding Emulator UI. +// TODO: Upgrade to GA4 before July 1, 2023. See: +// https://support.google.com/analytics/answer/11583528 +const FIREBASE_ANALYTICS_UA = process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3"; + +let visitor: ua.Visitor; + +function ensureUAVisitor(): void { + if (!visitor) { + // Identifier for the client (UUID) in the CLI UA. + let anonId = configstore.get("analytics-uuid") as string; + if (!anonId) { + anonId = uuidV4(); + configstore.set("analytics-uuid", anonId); + } + + visitor = ua(FIREBASE_ANALYTICS_UA, anonId, { + strictCidFormat: false, + https: true, + }); + + visitor.set("cd1", process.platform); // Platform + visitor.set("cd2", process.version); // NodeVersion + visitor.set("cd3", process.env.FIREPIT_VERSION || "none"); // FirepitVersion + } +} + +export function track(action: string, label: string, duration = 0): Promise { + ensureUAVisitor(); + return new Promise((resolve) => { + if (usageEnabled() && configstore.get("tokens")) { + visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(() => { + // we could handle errors here, but we won't + resolve(); + }); + } else { + resolve(); + } + }); +} From a9b6ecfe2744294be34f5113f01cd7edf9fa3918 Mon Sep 17 00:00:00 2001 From: clairekeer1997 Date: Thu, 22 Jun 2023 17:38:52 -0700 Subject: [PATCH 1039/1699] Release Firestore emulator 1.18.1 (#5942) * Release Firestore emulator 1.18.1 * Update CHANGELOG.md Co-authored-by: joehan * Formatting changes in CHANGE.md * Update CHANGELOG.md --------- Co-authored-by: Jia You Co-authored-by: joehan Co-authored-by: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c93ac2e5b19..65ff9f56488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ +- Release Firestore emulator 1.18.1 which addes a emulator configuration to start with experimental mode (#5942). - Run lifecycle hooks for specific codebases. (#6011) - Fixed issue causing `firebase emulators:start` to crash in Next.js apps (#6005) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 072fb699a58..ca3ec53dbe8 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -33,9 +33,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet expectedChecksum: "2fd771101c0e1f7898c04c9204f2ce63", }, firestore: { - version: "1.17.4", - expectedSize: 64969580, - expectedChecksum: "9d580b58e55e57b0cdc3ca8888098d43", + version: "1.18.1", + expectedSize: 64866257, + expectedChecksum: "743211a3e33217fe71dc20aff1fa26a5", }, storage: { version: "1.1.3", From 29747a824caa5589a4e04851a4ca0fa92491f03e Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 23 Jun 2023 10:18:06 -0700 Subject: [PATCH 1040/1699] Fix frameworks deploy to preview channel (#6025) --- src/deploy/hosting/prepare.ts | 7 +++++++ src/frameworks/index.ts | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/deploy/hosting/prepare.ts b/src/deploy/hosting/prepare.ts index 0a6223c39bf..5cff13ae002 100644 --- a/src/deploy/hosting/prepare.ts +++ b/src/deploy/hosting/prepare.ts @@ -12,6 +12,7 @@ import * as utils from "../../utils"; import { HostingSource, RunRewrite } from "../../firebaseConfig"; import * as backend from "../functions/backend"; import { ensureTargeted } from "../../functions/ensureTargeted"; +import { generateSSRCodebaseId } from "../../frameworks"; function handlePublicDirectoryFlag(options: HostingOptions & Options): void { // Allow the public directory to be overridden by the --public flag @@ -74,6 +75,12 @@ export async function addPinnedFunctionsToOnlyString( ]?.[r.function.functionId]; if (endpoint) { options.only = ensureTargeted(options.only, endpoint.codebase || "default", endpoint.id); + } else if (c.webFramework) { + options.only = ensureTargeted( + options.only, + generateSSRCodebaseId(c.site), + r.function.functionId + ); } else { // This endpoint is just being added in this push. We don't know what codebase it is. options.only = ensureTargeted(options.only, r.function.functionId); diff --git a/src/frameworks/index.ts b/src/frameworks/index.ts index 8ac1b3bf9c8..b25bc4f49b3 100644 --- a/src/frameworks/index.ts +++ b/src/frameworks/index.ts @@ -104,6 +104,14 @@ function memoizeBuild( return value; } +/** + * Use a function to ensure the same codebase name is used here and + * during hosting deploy. + */ +export function generateSSRCodebaseId(site: string) { + return `firebase-frameworks-${site}`; +} + /** * */ @@ -330,7 +338,7 @@ export async function prepareFrameworks( ); } - const codebase = `firebase-frameworks-${site}`; + const codebase = generateSSRCodebaseId(site); const existingFunctionsConfig = options.config.get("functions") ? [].concat(options.config.get("functions")) : []; From d971a08f4be53066f5240483c8725cc632d6c4cb Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 23 Jun 2023 11:08:40 -0700 Subject: [PATCH 1041/1699] Inject GOOGLE_CLOUD_QUOTA_PROJECT environment variable during function discovery and emulation (#5985) When deploying functions that makes use of GCP's usage-based (not resource-based) APIs , such as Vertex AI or ML Vision API, users may see an error like this: ``` // main.py import vertexai # Initialize the Vertex AI client vertexai.init() $ firebase deploy Failed to load function definition from source: FirebaseError: Failed to parse build specification Caused by: 403 Vertex AI API has not been used in project 563584335869 before or it is disabled. ``` During function discovery, `vertex.init()` makes a call to the Vertex AI with the client credentials associated with Firebase CLI . In the case of usage-based APIs, the call is being made on the Firebase CLI project, not the users project, resulting in the error message like `been used in project 563584335869 before or it is disabled.` (project `563584335869` is the Firebase CLI GCP project). Similar issue happens when running the function on the emulator. To workaround the issue, we have to properly override the client project associated with the Vertex AI API call. There are few ways for doing this, and here we choose to leverage `GOOGLE_CLOUD_QUOTA_PROJECT` environment variable to correctly override the quota project to be the user project associated with the Firebase CLI session. --- src/deploy/functions/prepare.ts | 8 +++++++- src/emulator/functionsEmulator.ts | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 38f22c88675..d666ccac1db 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -457,7 +457,13 @@ export async function loadCodebases( await runtimeDelegate.build(); const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); - wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs); + wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, { + ...firebaseEnvs, + // Quota project is required when using GCP's Client-based APIs + // Some GCP client SDKs, like Vertex AI, requires appropriate quota project setup + // in order for .init() calls to succeed. + GOOGLE_CLOUD_QUOTA_PROJECT: projectId, + }); } return wantBuilds; } diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index a3f93206585..3d753ec2d68 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -1167,6 +1167,9 @@ export class FunctionsEmulator implements EmulatorInstance { envs.GCLOUD_PROJECT = this.args.projectId; envs.K_REVISION = "1"; envs.PORT = "80"; + // Quota project is required when using GCP's Client-based APIs. + // Some GCP client SDKs, like Vertex AI, requires appropriate quota project setup. + envs.GOOGLE_CLOUD_QUOTA_PROJECT = this.args.projectId; if (trigger) { const target = trigger.entryPoint; From 463e6491faed709bc62b1a1778d477aced9caee0 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Fri, 23 Jun 2023 12:25:26 -0600 Subject: [PATCH 1042/1699] Reimplement Storage emulator /internal/setRules (#6014) * stash * commit * commit * lint * fix tests * const --- .../internal/tests.ts | 168 ++++++++++++++++++ .../rules/manager.test.ts | 60 +++++-- scripts/storage-emulator-integration/run.sh | 2 + src/emulator/storage/index.ts | 9 +- src/emulator/storage/rules/manager.ts | 4 - src/emulator/storage/server.ts | 101 ++++++++++- 6 files changed, 319 insertions(+), 25 deletions(-) create mode 100644 scripts/storage-emulator-integration/internal/tests.ts diff --git a/scripts/storage-emulator-integration/internal/tests.ts b/scripts/storage-emulator-integration/internal/tests.ts new file mode 100644 index 00000000000..fdf93440650 --- /dev/null +++ b/scripts/storage-emulator-integration/internal/tests.ts @@ -0,0 +1,168 @@ +import { expect } from "chai"; +import * as supertest from "supertest"; +import { StorageRulesFiles } from "../../../src/test/emulators/fixtures"; +import { TriggerEndToEndTest } from "../../integration-helpers/framework"; +import { + EMULATORS_SHUTDOWN_DELAY_MS, + getStorageEmulatorHost, + readEmulatorConfig, + TEST_SETUP_TIMEOUT, +} from "../utils"; + +const FIREBASE_PROJECT = process.env.FBTOOLS_TARGET_PROJECT || "fake-project-id"; +const EMULATOR_CONFIG = readEmulatorConfig(); +const STORAGE_EMULATOR_HOST = getStorageEmulatorHost(EMULATOR_CONFIG); + +describe("Storage emulator internal endpoints", () => { + let test: TriggerEndToEndTest; + + before(async function (this) { + this.timeout(TEST_SETUP_TIMEOUT); + process.env.STORAGE_EMULATOR_HOST = STORAGE_EMULATOR_HOST; + test = new TriggerEndToEndTest(FIREBASE_PROJECT, __dirname, EMULATOR_CONFIG); + await test.startEmulators(["--only", "auth,storage"]); + }); + + beforeEach(async () => { + // Reset emulator to default rules. + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [StorageRulesFiles.readWriteIfAuth], + }, + }) + .expect(200); + }); + + after(async function (this) { + this.timeout(EMULATORS_SHUTDOWN_DELAY_MS); + delete process.env.STORAGE_EMULATOR_HOST; + await test.stopEmulators(); + }); + + describe("setRules", () => { + it("should set single ruleset", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [StorageRulesFiles.readWriteIfTrue], + }, + }) + .expect(200); + }); + + it("should set multiple rules/resource objects", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [ + { resource: "bucket_0", ...StorageRulesFiles.readWriteIfTrue }, + { resource: "bucket_1", ...StorageRulesFiles.readWriteIfAuth }, + ], + }, + }) + .expect(200); + }); + + it("should overwrite single ruleset with multiple rules/resource objects", async () => { + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [StorageRulesFiles.readWriteIfTrue], + }, + }) + .expect(200); + + await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [ + { resource: "bucket_0", ...StorageRulesFiles.readWriteIfTrue }, + { resource: "bucket_1", ...StorageRulesFiles.readWriteIfAuth }, + ], + }, + }) + .expect(200); + }); + + it("should return 400 if rules.files array is missing", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ rules: {} }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal("Request body must include 'rules.files' array"); + }); + + it("should return 400 if rules.files array has missing name field", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [{ content: StorageRulesFiles.readWriteIfTrue.content }], + }, + }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal( + "Each member of 'rules.files' array must contain 'name' and 'content'" + ); + }); + + it("should return 400 if rules.files array has missing content field", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [{ name: StorageRulesFiles.readWriteIfTrue.name }], + }, + }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal( + "Each member of 'rules.files' array must contain 'name' and 'content'" + ); + }); + + it("should return 400 if rules.files array has missing resource field", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [ + { resource: "bucket_0", ...StorageRulesFiles.readWriteIfTrue }, + StorageRulesFiles.readWriteIfAuth, + ], + }, + }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal( + "Each member of 'rules.files' array must contain 'name', 'content', and 'resource'" + ); + }); + + it("should return 400 if rules.files array has invalid content", async () => { + const errorMessage = await supertest(STORAGE_EMULATOR_HOST) + .put("/internal/setRules") + .send({ + rules: { + files: [{ name: StorageRulesFiles.readWriteIfTrue.name, content: "foo" }], + }, + }) + .expect(400) + .then((res) => res.body.message); + + expect(errorMessage).to.equal("There was an error updating rules, see logs for more details"); + }); + }); +}); diff --git a/scripts/storage-emulator-integration/rules/manager.test.ts b/scripts/storage-emulator-integration/rules/manager.test.ts index d483cf1ec61..cc0ef2b9e23 100644 --- a/scripts/storage-emulator-integration/rules/manager.test.ts +++ b/scripts/storage-emulator-integration/rules/manager.test.ts @@ -1,10 +1,7 @@ import { expect } from "chai"; import { createTmpDir, StorageRulesFiles } from "../../../src/test/emulators/fixtures"; -import { - createStorageRulesManager, - StorageRulesManager, -} from "../../../src/emulator/storage/rules/manager"; +import { createStorageRulesManager } from "../../../src/emulator/storage/rules/manager"; import { StorageRulesRuntime } from "../../../src/emulator/storage/rules/runtime"; import * as fs from "fs"; import { RulesetOperationMethod, SourceFile } from "../../../src/emulator/storage/rules/types"; @@ -14,22 +11,21 @@ import * as path from "path"; const EMULATOR_LOAD_RULESET_DELAY_MS = 20000; const SETUP_TIMEOUT = 60000; +const PROJECT_ID = "demo-project-id"; describe("Storage Rules Manager", () => { let rulesRuntime: StorageRulesRuntime; const opts = { method: RulesetOperationMethod.GET, file: {}, path: "/b/bucket_0/o/" }; - const projectId = "demo-project-id"; - let rulesManager: StorageRulesManager; - beforeEach(async function (this) { + before(async function (this) { this.timeout(SETUP_TIMEOUT); rulesRuntime = new StorageRulesRuntime(); await rulesRuntime.start(); }); - afterEach(async function (this) { + after(async function (this) { this.timeout(SETUP_TIMEOUT); - await rulesManager.stop(); + await rulesRuntime.stop(); }); it("should load multiple rulesets on start", async function (this) { @@ -38,18 +34,30 @@ describe("Storage Rules Manager", () => { { resource: "bucket_0", rules: StorageRulesFiles.readWriteIfTrue }, { resource: "bucket_1", rules: StorageRulesFiles.readWriteIfAuth }, ]; - rulesManager = createStorageRulesManager(rules, rulesRuntime); + const rulesManager = createStorageRulesManager(rules, rulesRuntime); await rulesManager.start(); const bucket0Ruleset = rulesManager.getRuleset("bucket_0"); expect( - await isPermitted({ ...opts, path: "/b/bucket_0/o/", ruleset: bucket0Ruleset!, projectId }) + await isPermitted({ + ...opts, + path: "/b/bucket_0/o/", + ruleset: bucket0Ruleset!, + projectId: PROJECT_ID, + }) ).to.be.true; const bucket1Ruleset = rulesManager.getRuleset("bucket_1"); expect( - await isPermitted({ ...opts, path: "/b/bucket_1/o/", ruleset: bucket1Ruleset!, projectId }) + await isPermitted({ + ...opts, + path: "/b/bucket_1/o/", + ruleset: bucket1Ruleset!, + projectId: PROJECT_ID, + }) ).to.be.false; + + await rulesManager.stop(); }); it("should load single ruleset on start", async function (this) { @@ -60,11 +68,13 @@ describe("Storage Rules Manager", () => { appendBytes(testDir, fileName, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); const sourceFile = getSourceFile(testDir, fileName); - rulesManager = createStorageRulesManager(sourceFile, rulesRuntime); + const rulesManager = createStorageRulesManager(sourceFile, rulesRuntime); await rulesManager.start(); const ruleset = rulesManager.getRuleset("bucket"); - expect(await isPermitted({ ...opts, ruleset: ruleset!, projectId })).to.be.true; + expect(await isPermitted({ ...opts, ruleset: ruleset!, projectId: PROJECT_ID })).to.be.true; + + await rulesManager.stop(); }); it("should reload ruleset on changes to source file", async function (this) { @@ -75,19 +85,31 @@ describe("Storage Rules Manager", () => { appendBytes(testDir, fileName, Buffer.from(StorageRulesFiles.readWriteIfTrue.content)); const sourceFile = getSourceFile(testDir, fileName); - rulesManager = createStorageRulesManager(sourceFile, rulesRuntime); + const rulesManager = createStorageRulesManager(sourceFile, rulesRuntime); await rulesManager.start(); - expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket")!, projectId })) - .to.be.true; + expect( + await isPermitted({ + ...opts, + ruleset: rulesManager.getRuleset("bucket")!, + projectId: PROJECT_ID, + }) + ).to.be.true; // Write new rules to file deleteFile(testDir, fileName); appendBytes(testDir, fileName, Buffer.from(StorageRulesFiles.readWriteIfAuth.content)); await new Promise((resolve) => setTimeout(resolve, EMULATOR_LOAD_RULESET_DELAY_MS)); - expect(await isPermitted({ ...opts, ruleset: rulesManager.getRuleset("bucket")!, projectId })) - .to.be.false; + expect( + await isPermitted({ + ...opts, + ruleset: rulesManager.getRuleset("bucket")!, + projectId: PROJECT_ID, + }) + ).to.be.false; + + await rulesManager.stop(); }); }); diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index 48f3a21a77c..f2152963321 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -10,6 +10,8 @@ source scripts/set-default-credentials.sh # Prepare the storage emulator rules runtime firebase setup:emulators:storage +mocha scripts/storage-emulator-integration/internal/tests.ts + mocha scripts/storage-emulator-integration/rules/*.test.ts mocha scripts/storage-emulator-integration/import/tests.ts diff --git a/src/emulator/storage/index.ts b/src/emulator/storage/index.ts index cadb34c61a0..d57dc3e8758 100644 --- a/src/emulator/storage/index.ts +++ b/src/emulator/storage/index.ts @@ -6,7 +6,7 @@ import { createApp } from "./server"; import { StorageLayer, StoredFile } from "./files"; import { EmulatorLogger } from "../emulatorLogger"; import { createStorageRulesManager, StorageRulesManager } from "./rules/manager"; -import { StorageRulesRuntime } from "./rules/runtime"; +import { StorageRulesIssues, StorageRulesRuntime } from "./rules/runtime"; import { SourceFile } from "./rules/types"; import * as express from "express"; import { @@ -118,6 +118,7 @@ export class StorageEmulator implements EmulatorInstance { async stop(): Promise { await this._persistence.deleteAll(); + await this._rulesRuntime.stop(); await this._rulesManager.stop(); return this.destroyServer ? this.destroyServer() : Promise.resolve(); } @@ -145,6 +146,12 @@ export class StorageEmulator implements EmulatorInstance { return createStorageRulesManager(rules, this._rulesRuntime); } + async replaceRules(rules: SourceFile | RulesConfig[]): Promise { + await this._rulesManager.stop(); + this._rulesManager = this.createRulesManager(rules); + return this._rulesManager.start(); + } + private getPersistenceTmpDir(): string { return `${tmpdir()}/firebase/storage/blobs`; } diff --git a/src/emulator/storage/rules/manager.ts b/src/emulator/storage/rules/manager.ts index 9c4d8cd8526..53d292196f9 100644 --- a/src/emulator/storage/rules/manager.ts +++ b/src/emulator/storage/rules/manager.ts @@ -60,7 +60,6 @@ class DefaultStorageRulesManager implements StorageRulesManager { } async start(): Promise { - this._runtime.start(); const issues = await this.loadRuleset(); this.updateWatcher(this._rules.name); return issues; @@ -72,9 +71,6 @@ class DefaultStorageRulesManager implements StorageRulesManager { async stop(): Promise { await this._watcher.close(); - if (this._runtime.alive) { - await this._runtime.stop(); - } } private updateWatcher(rulesFile: string): void { diff --git a/src/emulator/storage/server.ts b/src/emulator/storage/server.ts index d5e82e53ac4..1489f5fb09f 100644 --- a/src/emulator/storage/server.ts +++ b/src/emulator/storage/server.ts @@ -4,8 +4,10 @@ import { EmulatorLogger } from "../emulatorLogger"; import { Emulators } from "../types"; import * as bodyParser from "body-parser"; import { createCloudEndpoints } from "./apis/gcloud"; -import { StorageEmulator } from "./index"; +import { RulesConfig, StorageEmulator } from "./index"; import { createFirebaseEndpoints } from "./apis/firebase"; +import { InvalidArgumentError } from "../auth/errors"; +import { SourceFile } from "./rules/types"; /** * @param defaultProjectId @@ -77,6 +79,91 @@ export function createApp( res.sendStatus(200); }); + /** + * Internal endpoint to overwrite current rules. Callers provide either a single set of rules to + * be applied to all resources or an array of rules/resource objects. + * + * Example payload for single set of rules: + * + * ``` + * { + * rules: { + * files: [{ name: , content: }] + * } + * } + * ``` + * + * Example payload for multiple rules/resource objects: + * + * ``` + * { + * rules: { + * files: [ + * { name: , content: , resource: }, + * ... + * ] + * } + * } + * ``` + */ + app.put("/internal/setRules", async (req, res) => { + const rulesRaw = req.body.rules; + if (!(rulesRaw && Array.isArray(rulesRaw.files) && rulesRaw.files.length > 0)) { + res.status(400).json({ + message: "Request body must include 'rules.files' array", + }); + return; + } + + const { files } = rulesRaw; + + function parseRulesFromFiles(files: Array): SourceFile | RulesConfig[] { + if (files.length === 1) { + const file = files[0]; + if (!isRulesFile(file)) { + throw new InvalidArgumentError( + "Each member of 'rules.files' array must contain 'name' and 'content'" + ); + } + return { name: file.name, content: file.content }; + } + + const rules: RulesConfig[] = []; + for (const file of files) { + if (!isRulesFile(file) || !file.resource) { + throw new InvalidArgumentError( + "Each member of 'rules.files' array must contain 'name', 'content', and 'resource'" + ); + } + rules.push({ resource: file.resource, rules: { name: file.name, content: file.content } }); + } + return rules; + } + + let rules: SourceFile | RulesConfig[]; + try { + rules = parseRulesFromFiles(files); + } catch (err) { + if (err instanceof InvalidArgumentError) { + res.status(400).json({ message: err.message }); + return; + } + throw err; + } + + const issues = await emulator.replaceRules(rules); + if (issues.errors.length > 0) { + res.status(400).json({ + message: "There was an error updating rules, see logs for more details", + }); + return; + } + + res.status(200).json({ + message: "Rules updated successfully", + }); + }); + app.post("/internal/reset", (req, res) => { emulator.reset(); res.sendStatus(200); @@ -87,3 +174,15 @@ export function createApp( return Promise.resolve(app); } + +interface RulesFile { + name: string; + content: string; + resource?: string; +} + +function isRulesFile(file: unknown): file is RulesFile { + return ( + typeof (file as RulesFile).name === "string" && typeof (file as RulesFile).content === "string" + ); +} From 8af4c93aa26ed5ea3c59fb572a94316469d3f4aa Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 23 Jun 2023 11:36:18 -0700 Subject: [PATCH 1043/1699] Adding JSON schema for extension yaml (#5984) * Starting on extension.yaml json schema * finsh extension.yaml schema * adding events * add billingrequired * Adding schemas to vscode plugin too * reverting new format for firebase-config.json --- firebase-vscode/package.json | 14 +- schema/extension-yaml.json | 432 +++++++++++++++++++++++++++++++++++ 2 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 schema/extension-yaml.json diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index ad7369c27c9..aa206f8ae34 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -68,7 +68,19 @@ "name": "Firebase" } ] - } + }, + "jsonValidation": [ + { + "fileMatch": "firebase.json", + "url": "https://raw.githubusercontent.com/firebase/firebase-tools/master/schema/firebase-config.json" + } + ], + "yamlValidation": [ + { + "fileMatch": "extension.yaml", + "url": "https://raw.githubusercontent.com/firebase/firebase-tools/master/schema/extension-yaml.json" + } + ] }, "scripts": { "vscode:prepublish": "npm run build", diff --git a/schema/extension-yaml.json b/schema/extension-yaml.json new file mode 100644 index 00000000000..6440c59e937 --- /dev/null +++ b/schema/extension-yaml.json @@ -0,0 +1,432 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "definitions": { + "author": { + "additionalProperties": false, + "type": "object", + "properties": { + "authorName": { + "type": "string", + "description": "The author's name" + }, + "email": { + "type": "string", + "description": "A contact email for the author" + }, + "url": { + "type": "string", + "description": "URL of the author's website" + } + } + }, + "role": { + "additionalProperties": false, + "type": "object", + "description": "An IAM role to grant to this extension.", + "properties": { + "role": { + "type": "string", + "description": "Name of the IAM role to grant. Must be on the list of allowed roles: https://firebase.google.com/docs/extensions/publishers/access#supported-roles", + "pattern": "[a-zA-Z]+\\.[a-zA-Z]+" + }, + "reason": { + "type": "string", + "description": "Why this extension needs this IAM role" + }, + "resource": { + "type": "string", + "description": "What resource to grant this role on. If omitted, defaults to projects/${project_id}" + } + }, + "required": ["role", "reason"] + }, + "api": { + "additionalProperties": false, + "type": "object", + "description": "A Google API used by this extension. Will be enabled on extension deployment.", + "properties": { + "apiName": { + "type": "string", + "description": "Name of the Google API to enable. Should match the service name listed in https://console.cloud.google.com/apis/library", + "pattern": "[^\\.]+\\.googleapis\\.com" + }, + "reason": { + "type": "string", + "description": "Why this extension needs this API enabled" + } + }, + "required": ["apiName", "reason"] + }, + "externalService": { + "additionalProperties": false, + "type": "object", + "description": "A non-Google API used by this extension", + "properties": { + "name": { + "type": "string", + "description": "Name of the external service" + }, + "pricingUri": { + "type": "string", + "description": "URI to pricing information for the service" + } + } + }, + "param": { + "additionalProperties": false, + "type": "object", + "description": "A parameter that users installing this extension can configure", + "properties": { + "param": { + "type": "string", + "description": "The name of the param. This is how you reference the param in your code" + }, + "label": { + "type": "string", + "description": "Short description for the parameter. Displayed to users when they're prompted for the parameter's value." + }, + "description": { + "type": "string", + "description": "Detailed description for the parameter. Displayed to users when they're prompted for the parameter's value." + }, + "example": { + "type": "string", + "description": "Example value for the parameter." + }, + "validationRegex": { + "type": "string", + "description": "Regular expression for validation of the parameter's user-configured value. Uses Google RE2 syntax." + }, + "validationErrorMessage": { + "type": "string", + "description": "Error message to display if regex validation fails." + }, + "default": { + "type": "string", + "description": "Default value for the parameter if the user leaves the parameter's value blank." + }, + "required": { + "type": "boolean", + "description": "Defines whether the user can submit an empty string when they're prompted for the parameter's value. Defaults to true." + }, + "immutable": { + "type": "boolean", + "description": "Defines whether the user can change the parameter's value after installation (such as if they reconfigure the extension). Defaults to false." + }, + "advanced": { + "type": "boolean", + "description": "Whether this a param for advanced users. When true, only users who choose 'advanced configuration' will see this param." + }, + "type": { + "type": "string", + "description": "The parameter type. Special parameter types might have additional requirements or different UI presentation. See https://firebase.google.com/docs/extensions/reference/extension-yaml#params for more details.", + "pattern": "string|STRING|select|SELECT|multiselect|MULTISELECT|secret|SECRET|selectresource|SELECTRESOURCE" + }, + "resourceType": { + "type": "string", + "description": "The type of resource to prompt the user to select. Provides a special UI treatment for the param.", + "pattern": "storage\\.googleapis\\.com\\/Bucket|firestore\\.googleapis\\.com\\/Database|firebasedatabase\\.googleapis\\.com\\/DatabaseInstance" + }, + "options": { + "type": "array", + "description": "Options for a select or multiselect type param.", + "items": { + "$ref": "#/definitions/paramOption" + } + } + }, + "required": ["param"] + }, + "paramOption": { + "additionalProperties": false, + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "One of the values the user can choose. This is the value you get when you read the parameter value in code." + }, + "label": { + "type": "string", + "description": "Short description of the selectable option. If omitted, defaults to value." + } + }, + "required": ["value"] + }, + "resource":{ + "additionalProperties": false, + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of this resource" + }, + "type": { + "type": "string", + "description": "What type of resource this is. See https://firebase.google.com/docs/extensions/reference/extension-yaml#resources for a full list of options." + }, + "description": { + "type": "string", + "description": "A brief description of what this resource does" + }, + "properties": { + "type": "object", + "description": "The properties of this resource", + "additionalProperties": true, + "properties": { + "location": { + "type": "string", + "description": "The location for this resource" + }, + "entryPoint": { + "type": "string", + "description": "The entry point for a function resource" + }, + "sourceDirectory": { + "type": "string", + "description": "Directory that contains your package.json at its root. The file for your functions source code must be in this directory. Defaults to functions" + }, + "timeout": { + "type": "string", + "description": "A function resources's maximum execution time.", + "pattern": "\\d+s" + }, + "availableMemoryMb": { + "type": "string", + "description": "Amount of memory in MB available for the function.", + "pattern": "\\d+" + }, + "runtime": { + "type": "string", + "description": "Runtime environment for the function. Defaults to the most recent LTS version of node." + }, + "httpsTrigger": { + "type": "object", + "description": "A function triggered by HTTPS calls", + "properties": {} + }, + "eventTrigger": { + "type": "object", + "description": "A function triggered by a background event", + "properties": { + "eventType": { + "type": "string", + "description": "The type of background event to trigger on. See https://firebase.google.com/docs/extensions/publishers/functions#supported for a full list." + }, + "resource": { + "type": "string", + "description": "The name or pattern of the resource to trigger on" + }, + "eventFilters": { + "type": "string", + "description": "Filters that further limit the events to listen to." + }, + "channel": { + "type": "string", + "description": "The name of the channel associated with the trigger in projects/{project}/locations/{location}/channels/{channel} format. If you omit this property, the function will listen for events on the project's default channel." + }, + "triggerRegion": { + "type": "string", + "description": "The trigger will only receive events originating in this region. It can be the same region as the function, a different region or multi-region, or the global region. If not provided, defaults to the same region as the function." + } + }, + "required": ["eventType"] + }, + "scheduleTrigger": { + "type": "object", + "description": "A function triggered at a regular interval by a Cloud Scheduler job", + "properties": { + "schedule": { + "type": "string", + "description": "The frequency at which you want the function to run. Accepts unix-cron (https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules) or App Engine (https://cloud.google.com/appengine/docs/standard/nodejs/scheduling-jobs-with-cron-yaml#defining_the_cron_job_schedule) syntax." + }, + "timeZone": { + "type": "string", + "description": "The time zone in which the schedule will run. Defaults to UTC." + } + }, + "required": ["schedule"] + }, + "taskQueueTrigger": { + "type": "object", + "description": "A function triggered by a Cloud Task", + "properties": {} + }, + "buildConfig": { + "type": "object", + "description": "Build configuration for a gen 2 Cloud Function", + "properties": { + "runtime": { + "type": "string", + "description": "Runtime environment for the function. Defaults to the most recent LTS version of node." + }, + "entryPoint": { + "type": "string", + "description": "The entry point for a function resource" + } + } + }, + "serviceConfig": { + "type": "object", + "description": "Service configuration for a gen 2 Cloud Function", + "properties": { + "timeoutSeconds": { + "type": "string", + "description": "The function's maximum execution time. Default: 60, max value: 540." + }, + "availableMemory": { + "type": "string", + "description": "The amount of memory available for a function. Defaults to 256M. Supported units are k, M, G, Mi, Gi. If no unit is supplied, the value is interpreted as bytes." + } + } + } + } + } + }, + "required": ["name", "type", "description", "properties"] + }, + "lifecycleEvent": { + "type": "object", + "additionalProperties": false, + "properties": { + "onInstall": { + "$ref": "#/definitions/lifecycleEventSpec" + }, + "onUpdate": { + "$ref": "#/definitions/lifecycleEventSpec" + }, + "onConfigure": { + "$ref": "#/definitions/lifecycleEventSpec" + } + } + }, + "lifecycleEventSpec": { + "type": "object", + "additionalProperties": false, + "properties": { + "function": { + "type": "string", + "description": "Name of the task queue-triggered function that will handle the event. This function must be a taskQueueTriggered function declared in the resources section." + }, + "processingMessage": { + "type": "string", + "description": "Message to display in the Firebase console while the task is in progress." + } + } + }, + "event": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "description": "The type identifier of the event. Construct the identifier out of 3-4 dot-delimited fields: the publisher ID, extension name, and event name fields are required; the version field is recommended. Choose a unique and descriptive event name for each event type you publish." + }, + "description": { + "type": "string", + "description": "A description of the event" + } + } + } + }, + "properties": { + "name": { + "type": "string", + "description": "ID of this extension (ie your-extension-name)" + }, + "version": { + "type": "string", + "description": "Version of this extension. Follows https://semver.org/." + }, + "specVersion": { + "type":"string", + "description": "Version of the extension.yaml spec that this file follows. Currently always 'v1beta'" + }, + "license": { + "type": "string", + "description": "The software license agreement for this extension. Currently, only 'Apache-2.0' is permitted on extensions.dev" + }, + "displayName": { + "type": "string", + "description": "Human readable name for this extension (ie 'Your Extension Name')" + }, + "description": { + "type": "string", + "description": "A one to two sentence description of what this extension does" + }, + "icon": { + "type": "string", + "description": "The file name of this extension's icon" + }, + "billingRequired": { + "type": "boolean", + "description": "Whether this extension requires a billing to be enabled on the project it is installed on" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of tags to help users find your extension in search" + }, + "sourceUrl": { + "type": "string", + "description": "The URL of the GitHub repo hosting this code" + }, + "releaseNotesUrl": { + "type": "string", + "description": "A URL where users can view the full changelog or release notes for this extension" + }, + "author": { + "$ref": "#/definitions/author" + }, + "contributors": { + "type": "array", + "items": { + "$ref": "#/definitions/author" + } + }, + "apis": { + "type": "array", + "items": { + "$ref": "#/definitions/api" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/role" + } + }, + "externalServices": { + "type": "array", + "items": { + "$ref": "#/definitions/externalService" + } + }, + "params": { + "type": "array", + "items": { + "$ref": "#/definitions/param" + } + }, + "resources": { + "type": "array", + "items": { + "$ref": "#/definitions/resource" + } + }, + "lifecycleEvents": { + "type": "array", + "items": { + "$ref": "#/definitions/lifecycleEvent" + } + }, + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/event" + } + } + } +} From 34f4d953f111db4f8a8479746bee0b44b46d3a35 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Fri, 23 Jun 2023 12:17:31 -0700 Subject: [PATCH 1044/1699] Set up vscode plugin test flow (#6024) --- firebase-vscode/.gitignore | 1 + firebase-vscode/.vscodeignore | 1 + firebase-vscode/package.json | 2 +- firebase-vscode/src/test/tsconfig.test.json | 11 +++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 firebase-vscode/src/test/tsconfig.test.json diff --git a/firebase-vscode/.gitignore b/firebase-vscode/.gitignore index 08ccd9d7281..2eb86a66425 100644 --- a/firebase-vscode/.gitignore +++ b/firebase-vscode/.gitignore @@ -2,3 +2,4 @@ dist/ *.scss.d.ts resources/dist +.vscode-test \ No newline at end of file diff --git a/firebase-vscode/.vscodeignore b/firebase-vscode/.vscodeignore index 4dd9a138ef0..42df22e391a 100644 --- a/firebase-vscode/.vscodeignore +++ b/firebase-vscode/.vscodeignore @@ -17,3 +17,4 @@ webpack.*.js ../ *.zip node_modules/ +dist/test/ \ No newline at end of file diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index aa206f8ae34..415cf731ecb 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -94,7 +94,7 @@ "build": "npm run copyfiles && webpack --config webpack.prod.js --devtool hidden-source-map", "build:extension": "webpack --config webpack.prod.js --config-name extension", "build:sidebar": "npm run copyfiles && webpack --config webpack.prod.js --config-name sidebar", - "pretest": "npm run build && npm run lint", + "pretest": "npm run dev ; npm run lint && tsc -p src/test/tsconfig.test.json", "lint": "eslint src --ext ts", "test": "node ./dist/test/runTest.js" }, diff --git a/firebase-vscode/src/test/tsconfig.test.json b/firebase-vscode/src/test/tsconfig.test.json new file mode 100644 index 00000000000..0b25a4a95f3 --- /dev/null +++ b/firebase-vscode/src/test/tsconfig.test.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "../../dist/test", + "sourceMap": true, + "rootDir": "./", + "strict": true, + } +} \ No newline at end of file From f6ed7b6e3356e41eb40e8587a8226f51f4e38c8c Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 26 Jun 2023 03:02:13 -0700 Subject: [PATCH 1045/1699] Print functions discovery to stdout/error (#5931) * Print functions discovery to stdout/error * More tweaking * lint fixes * Use logger instead of console to work with --json --------- Co-authored-by: Daniel Lee --- src/deploy/functions/prepare.ts | 4 ++ .../functions/runtimes/discovery/index.ts | 19 +++++++- src/deploy/functions/runtimes/node/index.ts | 20 +++++++-- src/deploy/functions/runtimes/python/index.ts | 45 ++++++++----------- 4 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index d666ccac1db..32cad037653 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -457,6 +457,10 @@ export async function loadCodebases( await runtimeDelegate.build(); const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId); + logLabeledBullet( + "functions", + `Loading and anaylzing source code for codebase ${codebase} to determine what to deploy` + ); wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, { ...firebaseEnvs, // Quota project is required when using GCP's Client-based APIs diff --git a/src/deploy/functions/runtimes/discovery/index.ts b/src/deploy/functions/runtimes/discovery/index.ts index c8e53268689..9915e588835 100644 --- a/src/deploy/functions/runtimes/discovery/index.ts +++ b/src/deploy/functions/runtimes/discovery/index.ts @@ -13,6 +13,9 @@ import { FirebaseError } from "../../../../error"; export const readFileAsync = promisify(fs.readFile); +/** + * Converts the YAML retrieved from discovery into a Build object for param interpolation. + */ export function yamlToBuild( yaml: any, project: string, @@ -34,6 +37,9 @@ export function yamlToBuild( } } +/** + * Load a Build from a functions.yaml file. + */ export async function detectFromYaml( directory: string, project: string, @@ -56,6 +62,9 @@ export async function detectFromYaml( return yamlToBuild(parsed, project, api.functionsDefaultRegion, runtime); } +/** + * Load a build from a discovery service. + */ export async function detectFromPort( port: number, project: string, @@ -63,7 +72,7 @@ export async function detectFromPort( timeout = 10_000 /* 10s to boot up */ ): Promise { // The result type of fetch isn't exported - let res: { text(): Promise }; + let res: { text(): Promise; status: number }; const timedOut = new Promise((resolve, reject) => { setTimeout(() => { reject(new FirebaseError("User code failed to load. Cannot determine backend specification")); @@ -83,6 +92,14 @@ export async function detectFromPort( } } + if (res.status !== 200) { + const text = await res.text(); + logger.debug(`Got response code ${res.status}; body ${text}`); + throw new FirebaseError( + "Functions codebase could not be analyzed successfully. " + + "It may have a syntax or runtime error" + ); + } const text = await res.text(); logger.debug("Got response from /__/functions.yaml", text); diff --git a/src/deploy/functions/runtimes/node/index.ts b/src/deploy/functions/runtimes/node/index.ts index 92ac00b6d99..a777f57080f 100644 --- a/src/deploy/functions/runtimes/node/index.ts +++ b/src/deploy/functions/runtimes/node/index.ts @@ -200,13 +200,18 @@ export class Delegate { const binPath = path.join(nodeModulesPath, ".bin", "firebase-functions"); if (fileExistsSync(binPath)) { logger.debug(`Found firebase-functions binary at '${binPath}'`); + // Note: We cannot use inherit because we need the stdout/err to be + // omitted in commands that use --json. const childProcess = spawn(binPath, [this.sourceDir], { env, cwd: this.sourceDir, - stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "inherit"], + stdio: [/* stdin=*/ "ignore", /* stdout=*/ "pipe", /* stderr=*/ "pipe"], }); - childProcess.stdout?.on("data", (chunk) => { - logger.debug(chunk.toString()); + childProcess.stdout?.on("data", (chunk: Buffer) => { + logger.info(chunk.toString("utf8")); + }); + childProcess.stderr?.on("data", (chunk: Buffer) => { + logger.error(chunk.toString("utf8")); }); return Promise.resolve(async () => { const p = new Promise((resolve, reject) => { @@ -214,7 +219,14 @@ export class Delegate { childProcess.once("error", reject); }); - await fetch(`http://localhost:${port}/__/quitquitquit`); + try { + await fetch(`http://localhost:${port}/__/quitquitquit`); + } catch (e) { + logger.debug( + "Failed to call quitquitquit. This often means the server failed to start", + e + ); + } setTimeout(() => { if (!childProcess.killed) { childProcess.kill("SIGKILL"); diff --git a/src/deploy/functions/runtimes/python/index.ts b/src/deploy/functions/runtimes/python/index.ts index e17a9043539..2dfbebbef93 100644 --- a/src/deploy/functions/runtimes/python/index.ts +++ b/src/deploy/functions/runtimes/python/index.ts @@ -12,7 +12,6 @@ import { logger } from "../../../../logger"; import { DEFAULT_VENV_DIR, runWithVirtualEnv, virtualEnvCmd } from "../../../../functions/python"; import { FirebaseError } from "../../../../error"; import { Build } from "../../build"; -import { logLabeledWarning } from "../../../../utils"; export const LATEST_VERSION: runtimes.Runtime = "python311"; @@ -145,8 +144,6 @@ export class Delegate implements runtimes.RuntimeDelegate { ADMIN_PORT: port.toString(), }; const args = [this.bin, `"${path.join(modulesDir, "private", "serving.py")}"`]; - const stdout: string[] = []; - const stderr: string[] = []; logger.debug( `Running admin server with args: ${JSON.stringify(args)} and env: ${JSON.stringify( envWithAdminPort @@ -154,27 +151,27 @@ export class Delegate implements runtimes.RuntimeDelegate { ); const childProcess = runWithVirtualEnv(args, this.sourceDir, envWithAdminPort); childProcess.stdout?.on("data", (chunk: Buffer) => { - const chunkString = chunk.toString(); - stdout.push(chunkString); - logger.debug(`stdout: ${chunkString}`); + logger.info(chunk.toString("utf8")); }); childProcess.stderr?.on("data", (chunk: Buffer) => { - const chunkString = chunk.toString(); - stderr.push(chunkString); - logger.debug(`stderr: ${chunkString}`); + logger.error(chunk.toString("utf8")); }); - return Promise.resolve({ - stderr, - stdout, - killProcess: async () => { + return Promise.resolve(async () => { + try { await fetch(`http://127.0.0.1:${port}/__/quitquitquit`); - const quitTimeout = setTimeout(() => { - if (!childProcess.killed) { - childProcess.kill("SIGKILL"); - } - }, 10_000); - clearTimeout(quitTimeout); - }, + } catch (e) { + logger.debug("Failed to call quitquitquit. This often means the server failed to start", e); + } + const quitTimeout = setTimeout(() => { + if (!childProcess.killed) { + childProcess.kill("SIGKILL"); + } + }, 10_000); + clearTimeout(quitTimeout); + return new Promise((resolve, reject) => { + childProcess.once("exit", resolve); + childProcess.once("error", reject); + }); }); } @@ -187,15 +184,9 @@ export class Delegate implements runtimes.RuntimeDelegate { const adminPort = await portfinder.getPortPromise({ port: 8081, }); - const { killProcess, stderr } = await this.serveAdmin(adminPort, envs); + const killProcess = await this.serveAdmin(adminPort, envs); try { discovered = await discovery.detectFromPort(adminPort, this.projectId, this.runtime); - } catch (e: any) { - logLabeledWarning( - "functions", - `Failed to detect functions from source ${e}.\nstderr:${stderr.join("\n")}` - ); - throw e; } finally { await killProcess(); } From 380390f89f21a0f54f33b4174076a6996c42d4ce Mon Sep 17 00:00:00 2001 From: Alex Astrum Date: Mon, 26 Jun 2023 11:30:50 -0400 Subject: [PATCH 1046/1699] Bump firebase-frameworks version in constants.ts (#6038) Fixes compatibility with latest Next.js --- src/frameworks/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frameworks/constants.ts b/src/frameworks/constants.ts index 63cc6cd18b0..5d4c8c24296 100644 --- a/src/frameworks/constants.ts +++ b/src/frameworks/constants.ts @@ -25,7 +25,7 @@ export const FEATURE_REQUEST_URL = "https://github.com/firebase/firebase-tools/issues/new?template=feature_request.md"; export const MAILING_LIST_URL = "https://goo.gle/41enW5X"; -export const FIREBASE_FRAMEWORKS_VERSION = "^0.10.1"; +export const FIREBASE_FRAMEWORKS_VERSION = "^0.10.4"; export const FIREBASE_FUNCTIONS_VERSION = "^4.3.0"; export const FIREBASE_ADMIN_VERSION = "^11.0.1"; export const SHARP_VERSION = "^0.32.1"; From 2b18e0bad4f234511054dbef2d8fa8407fccced4 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 26 Jun 2023 10:32:02 -0700 Subject: [PATCH 1047/1699] Revert emulators changes (#6030) --- firebase-vscode/src/cli.ts | 33 +---------------- firebase-vscode/src/extension.ts | 7 ---- firebase-vscode/src/workflow.ts | 47 ------------------------- firebase-vscode/webviews/SidebarApp.tsx | 2 -- 4 files changed, 1 insertion(+), 88 deletions(-) diff --git a/firebase-vscode/src/cli.ts b/firebase-vscode/src/cli.ts index 74d3a03f64b..beae5b8a777 100644 --- a/firebase-vscode/src/cli.ts +++ b/firebase-vscode/src/cli.ts @@ -14,17 +14,13 @@ import { requireAuth } from "../../src/requireAuth"; import { deploy } from "../../src/deploy"; import { getDefaultHostingSite } from "../../src/getDefaultHostingSite"; import { initAction } from "../../src/commands/init"; -import { startAll as startAllEmulators, cleanShutdown as stopAllEmulators } from "../../src/emulator/controller"; -import { EmulatorRegistry } from "../../src/emulator/registry"; -import { EmulatorInfo, Emulators } from "../../src/emulator/types"; import { Account, User } from "../../src/types/auth"; import { Options } from "../../src/options"; import { currentOptions, getCommandOptions } from "./options"; import { setInquirerOptions } from "./stubs/inquirer-stub"; import { ServiceAccount } from "../common/types"; import { listChannels } from "../../src/hosting/api"; -import * as commandUtils from "../../src/emulator/commandUtils"; -import { EmulatorUiSelections, ChannelWithId } from "../common/messaging/types"; +import { ChannelWithId } from "../common/messaging/types"; import { pluginLogger } from "./logger-wrapper"; import { Config } from "../../src/config"; @@ -170,33 +166,6 @@ export async function initHosting( setInquirerOptions(inquirerOptions); await initAction("hosting", commandOptions); } - -export async function emulatorsStart(emulatorUiSelections: EmulatorUiSelections) { - const commandOptions = await getCommandOptions(undefined, { - ...currentOptions, - project: emulatorUiSelections.projectId, - exportOnExit: emulatorUiSelections.exportStateOnExit, - import: emulatorUiSelections.importStateFolderPath, - only: emulatorUiSelections.mode === "hosting" ? "hosting" : "" - }); - // Adjusts some options, export on exit can be a boolean or a path. - commandUtils.setExportOnExitOptions(commandOptions as commandUtils.ExportOnExitOptions); - return startAllEmulators(commandOptions, /*showUi=*/ true); -} - -export async function stopEmulators() { - await stopAllEmulators(); -} - -export function listRunningEmulators(): EmulatorInfo[] { - return EmulatorRegistry.listRunningWithInfo(); -} - -export function getEmulatorUiUrl(): string | undefined { - const url: URL = EmulatorRegistry.url(Emulators.UI); - return url.hostname === "unknown" ? undefined : url.toString(); -} - export async function deployToHosting( firebaseJSON: Config, deployTarget: string diff --git a/firebase-vscode/src/extension.ts b/firebase-vscode/src/extension.ts index 368fecf5afb..7cc25c41d76 100644 --- a/firebase-vscode/src/extension.ts +++ b/firebase-vscode/src/extension.ts @@ -11,7 +11,6 @@ import { import { setupSidebar } from "./sidebar"; import { setupWorkflow } from "./workflow"; import { pluginLogger } from "./logger-wrapper"; -import { onShutdown } from "./workflow"; const broker = createBroker< ExtensionToWebviewParamsMap, @@ -26,9 +25,3 @@ export function activate(context: vscode.ExtensionContext) { setupWorkflow(context, broker); setupSidebar(context, broker); } - -// This method is called when the extension is deactivated -export async function deactivate() { - // This await is optimistic but it might wait for a moment longer while we run cleanup activities - await onShutdown(); -} diff --git a/firebase-vscode/src/workflow.ts b/firebase-vscode/src/workflow.ts index ce23f0b23d1..7ec6186cc6f 100644 --- a/firebase-vscode/src/workflow.ts +++ b/firebase-vscode/src/workflow.ts @@ -9,16 +9,12 @@ import { FirebaseProjectMetadata } from "../../src/types/project"; import { ExtensionBrokerImpl } from "./extension-broker"; import { deployToHosting, - emulatorsStart, getAccounts, getChannels, - getEmulatorUiUrl, initHosting, listProjects, - listRunningEmulators, login, logoutUser, - stopEmulators, } from "./cli"; import { User } from "../../src/types/auth"; import { currentOptions } from "./options"; @@ -364,47 +360,4 @@ export async function setupWorkflow( { projectId, folderPath: currentOptions.cwd }); await fetchChannels(true); } - broker.on( - "launchEmulators", - async ({ emulatorUiSelections }) => { - await emulatorsStart(emulatorUiSelections); - broker.send("notifyRunningEmulatorInfo", { uiUrl: getEmulatorUiUrl(), displayInfo: listRunningEmulators() }); - } - ); - - broker.on( - "stopEmulators", - async () => { - await stopEmulators(); - // Update the UI - broker.send("notifyEmulatorsStopped"); - } - ); - - broker.on( - "selectEmulatorImportFolder", - async () => { - const options: vscode.OpenDialogOptions = { - canSelectMany: false, - openLabel: `Pick an import folder`, - title: `Pick an import folder`, - canSelectFiles: false, - canSelectFolders: true, - }; - const fileUri = await vscode.window.showOpenDialog(options); - // Update the UI of the selection - if (!fileUri || fileUri.length < 1) { - vscode.window.showErrorMessage("Invalid import folder selected."); - return; - } - broker.send("notifyEmulatorImportFolder", { folder: fileUri[0].fsPath }); - } - ); -} - -/** - * Cleans up any open resources before shutting down. - */ -export async function onShutdown() { - await stopEmulators(); } diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx index c5f9e9bd77d..5a5b4c61175 100644 --- a/firebase-vscode/webviews/SidebarApp.tsx +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -9,7 +9,6 @@ import { ServiceAccountUser } from "../common/types"; import { DeployPanel } from "./components/DeployPanel"; import { HostingState } from "./webview-types"; import { ChannelWithId } from "./messaging/types"; -import { EmulatorPanel } from "./components/EmulatorPanel"; import { webLogger } from "./globals/web-logger"; import { InitFirebasePanel } from "./components/InitPanel"; @@ -146,7 +145,6 @@ export function SidebarApp() { }} /> )} - {(!!userEmail && !!firebaseJson) && } ); } From af7c645d63e17a2c4296020332f62003a3a1d086 Mon Sep 17 00:00:00 2001 From: Alex Astrum Date: Mon, 26 Jun 2023 15:24:26 -0400 Subject: [PATCH 1048/1699] Fix table in CONTRIBUTING.md (#6029) Multi-line table syntax is not supported in GitHub Markdown. --- CONTRIBUTING.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c8d2b16559..2617886a763 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -148,15 +148,10 @@ are unavailable to Pull Requests coming from forks of the repository. | path | description | | --------------- | --------------------------------------------------------- | | `src` | Contains shared/support code for the commands | -| `src/bin` | Contains the runnable script. You shouldn't need to touch | -: : this content. : -| `src/commands` | Contains code for the commands, organized by | -: : one-file-per-command with dashes. : -| `src/templates` | Contains static files needed for various reasons | -: : (inittemplates, login success HTML, etc.) : -| `src/test` | Contains tests. Mirrors the top-level directory structure | -: : (i.e., `src/test/commands` contains command tests and : -: : `src/test/gcp` contains `gcp` tests) : +| `src/bin` | Contains the runnable script. You shouldn't need to touch this content. | +| `src/commands` | Contains code for the commands, organized by one-file-per-command with dashes. | +| `src/templates` | Contains static files needed for various reasons (inittemplates, login success HTML, etc.) | +| `src/test` | Contains tests. Mirrors the top-level directory structure (i.e., `src/test/commands` contains command tests and `src/test/gcp` contains `gcp` tests) | ## Building CLI commands From 31c2d22df3cb89f8c08665371f628089e53bf9c8 Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Tue, 27 Jun 2023 11:35:55 -0400 Subject: [PATCH 1049/1699] Implement module to link stack to GitHub repository (#5793) * add internal testing cmd for connecting gh to cloud build repos * use regex to extract repo slug * add comments * add unit tests * fix tests * refactoring & allow changing gh app access settings * refactor & update unit tests * add comments & change internaltesting name * rename to composer --- src/api.ts | 6 + src/commands/index.ts | 2 + .../internaltesting-frameworks-init.ts | 14 ++ src/gcp/cloudbuild.ts | 179 +++++++++++++++++ src/init/features/composer/repo.ts | 184 ++++++++++++++++++ src/test/init/features/composer.spec.ts | 134 +++++++++++++ src/utils.ts | 9 + 7 files changed, 528 insertions(+) create mode 100644 src/commands/internaltesting-frameworks-init.ts create mode 100644 src/gcp/cloudbuild.ts create mode 100644 src/init/features/composer/repo.ts create mode 100644 src/test/init/features/composer.spec.ts diff --git a/src/api.ts b/src/api.ts index 0a362e63758..86f827a2e35 100644 --- a/src/api.ts +++ b/src/api.ts @@ -94,6 +94,12 @@ export const functionsDefaultRegion = utils.envOverride( "FIREBASE_FUNCTIONS_DEFAULT_REGION", "us-central1" ); + +export const cloudbuildOrigin = utils.envOverride( + "FIREBASE_CLOUDBUILD_URL", + "https://cloudbuild.googleapis.com" +); + export const cloudschedulerOrigin = utils.envOverride( "FIREBASE_CLOUDSCHEDULER_URL", "https://cloudscheduler.googleapis.com" diff --git a/src/commands/index.ts b/src/commands/index.ts index 1b3c5d3bc30..b3fa211894e 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -150,6 +150,8 @@ export function load(client: any): any { client.internaltesting.frameworks.compose = loadCommand("internaltesting-frameworks-compose"); client.internaltesting.functions = {}; client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover"); + client.internaltesting.frameworks = {}; + client.internaltesting.frameworks.init = loadCommand("internaltesting-frameworks-init"); } client.login = loadCommand("login"); client.login.add = loadCommand("login-add"); diff --git a/src/commands/internaltesting-frameworks-init.ts b/src/commands/internaltesting-frameworks-init.ts new file mode 100644 index 00000000000..729fde0e9d9 --- /dev/null +++ b/src/commands/internaltesting-frameworks-init.ts @@ -0,0 +1,14 @@ +import { Command } from "../command"; +import { linkGitHubRepository } from "../init/features/composer/repo"; +import { Options } from "../options"; +import { needProjectId } from "../projectUtils"; +import requireInteractive from "../requireInteractive"; + +export const command = new Command("internaltesting:frameworks:init") + .description("connect github repo to cloud build") + .before(requireInteractive) + .action(async (options: Options) => { + const projectId = needProjectId(options); + await linkGitHubRepository(projectId, "us-central2", "stack0"); + // TODO: send repo metadata to control plane + }); diff --git a/src/gcp/cloudbuild.ts b/src/gcp/cloudbuild.ts new file mode 100644 index 00000000000..488d806ab94 --- /dev/null +++ b/src/gcp/cloudbuild.ts @@ -0,0 +1,179 @@ +import { Client } from "../apiv2"; +import { cloudbuildOrigin } from "../api"; + +const client = new Client({ + urlPrefix: cloudbuildOrigin, + auth: true, + apiVersion: "v2", +}); + +export interface OperationMetadata { + createTime: string; + endTime: string; + target: string; + verb: string; + requestedCancellation: boolean; + apiVersion: string; +} + +export interface Operation { + name: string; + metadata?: OperationMetadata; + done: boolean; + error?: { code: number; message: string; details: unknown }; + response?: any; +} + +export interface GitHubConfig { + authorizerCredential?: { + oauthTokenSecretVersion: string; + username: string; + }; + appInstallationId?: string; +} + +type InstallationStage = + | "STAGE_UNSPECIFIED" + | "PENDING_CREATE_APP" + | "PENDING_USER_OAUTH" + | "PENDING_INSTALL_APP" + | "COMPLETE"; + +type ConnectionOutputOnlyFields = "createTime" | "updateTime" | "installationState" | "reconciling"; + +export interface Connection { + name?: string; + disabled?: boolean; + annotations?: { + [key: string]: string; + }; + etag?: string; + githubConfig?: GitHubConfig; + createTime: string; + updateTime: string; + installationState: { + stage: InstallationStage; + message: string; + actionUri: string; + }; + reconciling: boolean; +} + +type RepositoryOutputOnlyFields = "createTime" | "updateTime"; + +export interface Repository { + name?: string; + remoteUri: string; + annotations?: { + [key: string]: string; + }; + etag?: string; + createTime: string; + updateTime: string; +} + +interface LinkableRepositories { + repositories: Repository[]; + nextPageToken: string; +} + +/** + * Creates a Cloud Build V2 Connection. + */ +export async function createConnection( + projectId: string, + location: string, + connectionId: string +): Promise { + const res = await client.post, Operation>( + `projects/${projectId}/locations/${location}/connections`, + { githubConfig: {} }, + { queryParams: { connectionId } } + ); + return res.body; +} + +/** + * Gets metadata for a Cloud Build V2 Connection. + */ +export async function getConnection( + projectId: string, + location: string, + connectionId: string +): Promise { + const name = `projects/${projectId}/locations/${location}/connections/${connectionId}`; + const res = await client.get(name); + return res.body; +} + +/** + * Deletes a Cloud Build V2 Connection. + */ +export async function deleteConnection( + projectId: string, + location: string, + connectionId: string +): Promise { + const name = `projects/${projectId}/locations/${location}/connections/${connectionId}`; + const res = await client.delete(name); + return res.body; +} + +/** + * Gets a list of repositories that can be added to the provided Connection. + */ +export async function fetchLinkableRepositories( + projectId: string, + location: string, + connectionId: string +): Promise { + const name = `projects/${projectId}/locations/${location}/connections/${connectionId}:fetchLinkableRepositories`; + const res = await client.get(name); + return res.body; +} + +/** + * Creates a Cloud Build V2 Repository. + */ +export async function createRepository( + projectId: string, + location: string, + connectionId: string, + repositoryId: string, + remoteUri: string +): Promise { + const res = await client.post, Operation>( + `projects/${projectId}/locations/${location}/connections/${connectionId}/repositories`, + { remoteUri }, + { queryParams: { repositoryId } } + ); + return res.body; +} + +/** + * Gets metadata for a Cloud Build V2 Repository. + */ +export async function getRepository( + projectId: string, + location: string, + connectionId: string, + repositoryId: string +): Promise { + const name = `projects/${projectId}/locations/${location}/connections/${connectionId}/repositories/${repositoryId}`; + const res = await client.get(name); + return res.body; +} + +/** + * Deletes a Cloud Build V2 Repository. + */ +export async function deleteRepository( + projectId: string, + location: string, + connectionId: string, + repositoryId: string +) { + const name = `projects/${projectId}/locations/${location}/connections/${connectionId}/repositories/${repositoryId}`; + const res = await client.delete(name); + return res.body; +} diff --git a/src/init/features/composer/repo.ts b/src/init/features/composer/repo.ts new file mode 100644 index 00000000000..c5080ffa73f --- /dev/null +++ b/src/init/features/composer/repo.ts @@ -0,0 +1,184 @@ +import { cloudbuildOrigin } from "../../../api"; +import { FirebaseError } from "../../../error"; +import * as gcb from "../../../gcp/cloudbuild"; +import { logger } from "../../../logger"; +import * as poller from "../../../operation-poller"; +import * as utils from "../../../utils"; +import { promptOnce } from "../../../prompt"; + +const gcbPollerOptions: Omit = { + apiOrigin: cloudbuildOrigin, + apiVersion: "v2", + masterTimeout: 25 * 60 * 1_000, + maxBackoff: 10_000, +}; + +/** + * Example usage: + * extractRepoSlugFromURI("https://github.com/user/repo.git") => "user/repo" + */ +function extractRepoSlugFromURI(remoteUri: string): string | undefined { + const match = /github.com\/(.+).git/.exec(remoteUri); + if (!match) { + return undefined; + } + return match[1]; +} + +function generateConnectionId(stackId: string): string { + return `composer-${stackId}-conn`; +} + +/** + * Generates a repository ID. + * N.B. The deterministic nature of the repository ID implies that each + * Cloud Build Connection will have one Cloud Build Repo child resource. + * The current implementation is subject to change in the event that + * the 1:1 Connection-to-Resource relationship no longer holds. + */ +function generateRepositoryId(): string | undefined { + return `composer-repo`; +} + +/** + * Prompts the user to link their stack to a GitHub repository. + */ +export async function linkGitHubRepository( + projectId: string, + location: string, + stackId: string +): Promise { + const connectionId = generateConnectionId(stackId); + await getOrCreateConnection(projectId, location, connectionId); + + let remoteUri = await promptRepositoryURI(projectId, location, connectionId); + while (remoteUri === "") { + await utils.openInBrowser("https://github.com/apps/google-cloud-build/installations/new"); + await promptOnce({ + type: "input", + message: + "Press any key once you have finished configuring your installation's access settings.", + }); + remoteUri = await promptRepositoryURI(projectId, location, connectionId); + } + + const repo = await getOrCreateRepository(projectId, location, connectionId, remoteUri); + logger.info(`Successfully linked GitHub repository at remote URI ${remoteUri}.`); + return repo; +} + +async function promptRepositoryURI( + projectId: string, + location: string, + connectionId: string +): Promise { + const resp = await gcb.fetchLinkableRepositories(projectId, location, connectionId); + if (!resp.repositories || resp.repositories.length === 0) { + throw new FirebaseError( + "The GitHub App does not have access to any repositories. Please configure " + + "your app installation permissions at https://github.com/settings/installations." + ); + } + const choices = resp.repositories.map((repo: gcb.Repository) => ({ + name: extractRepoSlugFromURI(repo.remoteUri) || repo.remoteUri, + value: repo.remoteUri, + })); + choices.push({ + name: "Missing a repo? Select this option to configure your installation's access settings", + value: "", + }); + + return await promptOnce({ + type: "list", + message: "Which of the following repositories would you like to link?", + choices, + }); +} + +async function promptConnectionAuth( + conn: gcb.Connection, + projectId: string, + location: string, + connectionId: string +): Promise { + logger.info(conn.installationState.message); + logger.info(conn.installationState.actionUri); + await utils.openInBrowser(conn.installationState.actionUri); + await promptOnce({ + type: "input", + message: + "Press any key once you have authorized the app (Cloud Build) to access your GitHub repo.", + }); + return await gcb.getConnection(projectId, location, connectionId); +} + +/** + * Exported for unit testing. + */ +export async function getOrCreateConnection( + projectId: string, + location: string, + connectionId: string +): Promise { + let conn: gcb.Connection; + try { + conn = await gcb.getConnection(projectId, location, connectionId); + } catch (err: unknown) { + if ((err as FirebaseError).status === 404) { + const op = await gcb.createConnection(projectId, location, connectionId); + conn = await poller.pollOperation({ + ...gcbPollerOptions, + pollerName: `create-${location}-${connectionId}`, + operationResourceName: op.name, + }); + } else { + throw err; + } + } + + while (conn.installationState.stage !== "COMPLETE") { + conn = await promptConnectionAuth(conn, projectId, location, connectionId); + } + return conn; +} + +/** + * Exported for unit testing. + */ +export async function getOrCreateRepository( + projectId: string, + location: string, + connectionId: string, + remoteUri: string +): Promise { + const repositoryId = generateRepositoryId(); + if (!repositoryId) { + throw new FirebaseError(`Failed to generate repositoryId for URI "${remoteUri}".`); + } + let repo: gcb.Repository; + try { + repo = await gcb.getRepository(projectId, location, connectionId, repositoryId); + const repoSlug = extractRepoSlugFromURI(repo.remoteUri); + if (repoSlug) { + throw new FirebaseError(`${repoSlug} has already been linked.`); + } + } catch (err: unknown) { + if ((err as FirebaseError).status === 404) { + const op = await gcb.createRepository( + projectId, + location, + connectionId, + repositoryId, + remoteUri + ); + repo = await poller.pollOperation({ + ...gcbPollerOptions, + pollerName: `create-${location}-${connectionId}-${repositoryId}`, + operationResourceName: op.name, + }); + } else { + throw err; + } + } + return repo; +} diff --git a/src/test/init/features/composer.spec.ts b/src/test/init/features/composer.spec.ts new file mode 100644 index 00000000000..661bdd831aa --- /dev/null +++ b/src/test/init/features/composer.spec.ts @@ -0,0 +1,134 @@ +import * as sinon from "sinon"; +import { expect } from "chai"; + +import * as gcb from "../../../gcp/cloudbuild"; +import * as prompt from "../../../prompt"; +import * as poller from "../../../operation-poller"; +import { FirebaseError } from "../../../error"; +import * as repo from "../../../init/features/composer/repo"; +import * as utils from "../../../utils"; + +describe("composer", () => { + const sandbox: sinon.SinonSandbox = sinon.createSandbox(); + + let promptOnceStub: sinon.SinonStub; + let pollOperationStub: sinon.SinonStub; + let getConnectionStub: sinon.SinonStub; + let getRepositoryStub: sinon.SinonStub; + let createConnectionStub: sinon.SinonStub; + let createRepositoryStub: sinon.SinonStub; + let fetchLinkableRepositoriesStub: sinon.SinonStub; + + beforeEach(() => { + promptOnceStub = sandbox.stub(prompt, "promptOnce").throws("Unexpected promptOnce call"); + pollOperationStub = sandbox + .stub(poller, "pollOperation") + .throws("Unexpected pollOperation call"); + getConnectionStub = sandbox.stub(gcb, "getConnection").throws("Unexpected getConnection call"); + getRepositoryStub = sandbox.stub(gcb, "getRepository").throws("Unexpected getRepository call"); + createConnectionStub = sandbox + .stub(gcb, "createConnection") + .throws("Unexpected createConnection call"); + createRepositoryStub = sandbox + .stub(gcb, "createRepository") + .throws("Unexpected createRepository call"); + fetchLinkableRepositoriesStub = sandbox + .stub(gcb, "fetchLinkableRepositories") + .throws("Unexpected fetchLinkableRepositories call"); + + sandbox.stub(utils, "openInBrowser").resolves(); + }); + + afterEach(() => { + sandbox.verifyAndRestore(); + }); + + describe("connect GitHub repo", () => { + const projectId = "projectId"; + const location = "us-central1"; + const stackId = "stack0"; + const connectionId = `composer-${stackId}-conn`; + + const op = { + name: `projects/${projectId}/locations/${location}/connections/${connectionId}`, + done: true, + }; + const pendingConn = { + name: `projects/${projectId}/locations/${location}/connections/${connectionId}`, + disabled: false, + createTime: "0", + updateTime: "1", + installationState: { + stage: "PENDING_USER_OAUTH", + message: "pending", + actionUri: "https://google.com", + }, + reconciling: false, + }; + const completeConn = { + name: `projects/${projectId}/locations/${location}/connections/${connectionId}`, + disabled: false, + createTime: "0", + updateTime: "1", + installationState: { + stage: "COMPLETE", + message: "complete", + actionUri: "https://google.com", + }, + reconciling: false, + }; + const repos = { + repositories: [ + { + name: "repo0", + remoteUri: "https://github.com/test/repo0.git", + }, + { + name: "repo1", + remoteUri: "https://github.com/test/repo1.git", + }, + ], + }; + + it("creates a connection if it doesn't exist", async () => { + getConnectionStub.onFirstCall().rejects(new FirebaseError("error", { status: 404 })); + getConnectionStub.onSecondCall().resolves(completeConn); + createConnectionStub.resolves(op); + pollOperationStub.resolves(pendingConn); + promptOnceStub.onFirstCall().resolves("any key"); + + await repo.getOrCreateConnection(projectId, location, connectionId); + expect(createConnectionStub).to.be.calledWith(projectId, location, connectionId); + }); + + it("creates repository if it doesn't exist", async () => { + getConnectionStub.resolves(completeConn); + fetchLinkableRepositoriesStub.resolves(repos); + promptOnceStub.onFirstCall().resolves(repos.repositories[0].remoteUri); + getRepositoryStub.rejects(new FirebaseError("error", { status: 404 })); + createRepositoryStub.resolves({ name: "op" }); + pollOperationStub.resolves(repos.repositories[0]); + + await repo.getOrCreateRepository( + projectId, + location, + connectionId, + repos.repositories[0].remoteUri + ); + expect(createRepositoryStub).to.be.calledWith( + projectId, + location, + connectionId, + "composer-repo", + repos.repositories[0].remoteUri + ); + }); + + it("throws error if no linkable repositories are available", async () => { + getConnectionStub.resolves(pendingConn); + fetchLinkableRepositoriesStub.resolves({ repositories: [] }); + + await expect(repo.linkGitHubRepository(projectId, location, stackId)).to.be.rejected; + }); + }); +}); diff --git a/src/utils.ts b/src/utils.ts index 39c517587b2..318c58d530c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,7 @@ import * as _ from "lodash"; import * as url from "url"; import * as http from "http"; import * as clc from "colorette"; +import * as open from "open"; import * as ora from "ora"; import * as process from "process"; import { Readable } from "stream"; @@ -764,3 +765,11 @@ export function connectableHostname(hostname: string): string { } return hostname; } + +/** + * We wrap and export the open() function from the "open" package + * to stub it out in unit tests. + */ +export async function openInBrowser(url: string): Promise { + await open(url); +} From e96b069feb2b0f66a6007c22efe7056b4967b1cb Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 27 Jun 2023 12:42:02 -0700 Subject: [PATCH 1050/1699] Imporved extensions metrics (#6037) * Switched most uses of track to GA4 * Move duration out of params, and improve debug logging slightly * Improved metrics for extensions * formats --- src/deploy/extensions/args.ts | 1 + src/deploy/extensions/deploy.ts | 1 - src/deploy/extensions/prepare.ts | 1 + src/deploy/extensions/release.ts | 15 +++++++++++++++ src/deploy/hosting/deploy.ts | 6 ------ src/emulator/controller.ts | 6 +++++- src/track.ts | 4 +++- 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/deploy/extensions/args.ts b/src/deploy/extensions/args.ts index 2ff5221e1cf..d7398c4bf56 100644 --- a/src/deploy/extensions/args.ts +++ b/src/deploy/extensions/args.ts @@ -10,4 +10,5 @@ export interface Payload { export interface Context { have?: planner.DeploymentInstanceSpec[]; want?: planner.DeploymentInstanceSpec[]; + extensionsStartTime?: number; } diff --git a/src/deploy/extensions/deploy.ts b/src/deploy/extensions/deploy.ts index 47e1edcbf53..e49d7462f17 100644 --- a/src/deploy/extensions/deploy.ts +++ b/src/deploy/extensions/deploy.ts @@ -59,7 +59,6 @@ export async function deploy(context: Context, options: Options, payload: Payloa validationQueue.process(); validationQueue.close(); - await validationPromise; if (errorHandler.hasErrors()) { diff --git a/src/deploy/extensions/prepare.ts b/src/deploy/extensions/prepare.ts index 403af32073d..47a3b3820ad 100644 --- a/src/deploy/extensions/prepare.ts +++ b/src/deploy/extensions/prepare.ts @@ -17,6 +17,7 @@ import { checkSpecForV2Functions, ensureNecessaryV2ApisAndRoles } from "./v2Func import { acceptLatestAppDeveloperTOS } from "../../extensions/tos"; export async function prepare(context: Context, options: Options, payload: Payload) { + context.extensionsStartTime = Date.now(); const projectId = needProjectId(options); const projectNumber = await needProjectNumber(options); const aliases = getAliases(options, projectId); diff --git a/src/deploy/extensions/release.ts b/src/deploy/extensions/release.ts index a8501a02242..67a5063b0f2 100644 --- a/src/deploy/extensions/release.ts +++ b/src/deploy/extensions/release.ts @@ -7,6 +7,7 @@ import { ErrorHandler } from "./errors"; import { Options } from "../../options"; import { needProjectId } from "../../projectUtils"; import { saveEtags } from "../../extensions/etags"; +import { trackGA4 } from "../../track"; export async function release(context: Context, options: Options, payload: Payload) { const projectId = needProjectId(options); @@ -45,6 +46,20 @@ export async function release(context: Context, options: Options, payload: Paylo deploymentQueue.close(); await deploymentPromise; + // extensionsStartTime should always be populated, but if not, fall back to something that won't break us. + const duration = context.extensionsStartTime ? Date.now() - context.extensionsStartTime : 1; + await trackGA4( + "extensions_deploy", + { + extension_instance_created: payload.instancesToCreate?.length ?? 0, + extension_instance_updated: payload.instancesToUpdate?.length ?? 0, + extension_instance_configured: payload.instancesToConfigure?.length ?? 0, + extension_instance_deleted: payload.instancesToDelete?.length ?? 0, + errors: errorHandler.errors.length ?? 0, + interactive: options.nonInteractive ? "false" : "true", + }, + duration + ); // After deployment, write the latest etags to RC so we can detect out of band changes in the next deploy. const newHave = await planner.have(projectId); diff --git a/src/deploy/hosting/deploy.ts b/src/deploy/hosting/deploy.ts index 2f9fc4e528a..279b250cef7 100644 --- a/src/deploy/hosting/deploy.ts +++ b/src/deploy/hosting/deploy.ts @@ -2,7 +2,6 @@ import { Uploader } from "./uploader"; import { detectProjectRoot } from "../../detectProjectRoot"; import { listFiles } from "../../listFiles"; import { logger } from "../../logger"; -import { track } from "../../track"; import { envOverride, logLabeledBullet, logLabeledSuccess } from "../../utils"; import { bold, cyan } from "colorette"; import * as ora from "ora"; @@ -88,9 +87,6 @@ export async function deploy(context: Context, options: Options): Promise try { await uploader.start(); - } catch (err: any) { - void track("Hosting Deploy", "failure"); - throw err; } finally { clearInterval(progressInterval); updateSpinner(uploader.statusMessage(), debugging); @@ -103,8 +99,6 @@ export async function deploy(context: Context, options: Options): Promise logLabeledSuccess(`hosting[${deploy.config.site}]`, "file upload complete"); const dt = Date.now() - t0; logger.debug(`[hosting] deploy completed after ${dt}ms`); - - void track("Hosting Deploy", "success", dt); return runDeploys(deploys, debugging); } diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 7505bf9ea81..1950cb0b346 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -4,7 +4,7 @@ import * as path from "path"; import * as fsConfig from "../firestore/fsConfig"; import { logger } from "../logger"; -import { trackEmulator } from "../track"; +import { trackEmulator, trackGA4 } from "../track"; import * as utils from "../utils"; import { EmulatorRegistry } from "./registry"; import { @@ -351,6 +351,10 @@ export async function startAll( extensionsBackends ); emulatableBackends.push(...filteredExtensionsBackends); + trackGA4("extensions_emulated", { + number_of_extensions_emulated: filteredExtensionsBackends.length, + number_of_extensions_ignored: extensionsBackends.length - filteredExtensionsBackends.length, + }); } const listenConfig = {} as Record; diff --git a/src/track.ts b/src/track.ts index c1aa68eaf33..69a0c92f4bd 100644 --- a/src/track.ts +++ b/src/track.ts @@ -14,7 +14,9 @@ type cliEventNames = | "login" | "api_enabled" | "hosting_version" - | "extension_added_to_manifest"; + | "extension_added_to_manifest" + | "extensions_deploy" + | "extensions_emulated"; type GA4Property = "cli" | "emulator"; interface GA4Info { measurementId: string; From 3ca530ca85fa2e4deb56d617b034b9423b6900a6 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 27 Jun 2023 21:32:18 +0000 Subject: [PATCH 1051/1699] 12.4.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 018218f8e08..6a9fdbca629 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "12.4.0", + "version": "12.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "12.4.0", + "version": "12.4.1", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index a93b3c2d71d..c9334a89044 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "12.4.0", + "version": "12.4.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From a21ac90519b960b3c4f6465546dd02a3b7a3a1e6 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 27 Jun 2023 21:32:33 +0000 Subject: [PATCH 1052/1699] [firebase-release] Removed change log and reset repo after 12.4.1 release --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ff9f56488..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +0,0 @@ -- Release Firestore emulator 1.18.1 which addes a emulator configuration to start with experimental mode (#5942). -- Run lifecycle hooks for specific codebases. (#6011) -- Fixed issue causing `firebase emulators:start` to crash in Next.js apps (#6005) From 2e8e909ca438f230854e8cbb8091fb40026218cf Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Tue, 27 Jun 2023 15:08:05 -0700 Subject: [PATCH 1053/1699] Refactored ext:install to use the latest extension metadata. (#5997) * Added cascading of latest approved version to latest version when installing. * Changed output of extension version info. * Formatting, added more metadata, and cleaned up TODOs. * Formatting and extra notices. * Added even more metadata. * Formatting. * Fixing tests. * Added display of extension resources. * Added link to Extensions Hub. * Added displaying of events. * Formatting. * Formatting. * Version bug. * Added displaying of secrets and task queues. * Added displaying of external services. * Fixed resolveVersion() + tests. * Added tests for displayExtensionInfo(). * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: joehan * Better messaging and parameterizing. * Update displayExtensionInfo.ts * Update displayExtensionInfo.spec.ts * Update CHANGELOG.md --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + src/commands/ext-configure.ts | 2 - src/commands/ext-install.ts | 143 ++++++------- src/commands/ext-update.ts | 2 - src/deploy/extensions/planner.ts | 25 +-- src/extensions/displayExtensionInfo.ts | 197 ++++++++++++------ src/extensions/extensionsHelper.ts | 23 -- src/extensions/paramHelper.ts | 2 - src/extensions/types.ts | 6 + src/extensions/updateHelper.ts | 6 +- src/test/deploy/extensions/planner.spec.ts | 31 +-- .../extensions/displayExtensionInfo.spec.ts | 184 ++++++++-------- src/test/extensions/extensionsHelper.spec.ts | 35 ---- 13 files changed, 327 insertions(+), 330 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..2668eb75f0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Refactored `ext:install` to use the latest extension metadata. (#5997) diff --git a/src/commands/ext-configure.ts b/src/commands/ext-configure.ts index 994523de058..fd6ba7e6577 100644 --- a/src/commands/ext-configure.ts +++ b/src/commands/ext-configure.ts @@ -88,8 +88,6 @@ export const command = new Command("ext:configure ") projectId, paramSpecs: tbdParams, nonInteractive: false, - // TODO(b/230598656): Clean up paramsEnvPath after v11 launch. - paramsEnvPath: "", instanceId, reconfiguring: true, }); diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 35e55d2d09c..4e722975a17 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -1,12 +1,14 @@ import * as clc from "colorette"; import { marked } from "marked"; +import * as semver from "semver"; import * as TerminalRenderer from "marked-terminal"; -import { displayExtInfo } from "../extensions/displayExtensionInfo"; +import { displayExtensionVersionInfo } from "../extensions/displayExtensionInfo"; import * as askUserForEventsConfig from "../extensions/askUserForEventsConfig"; import { checkMinRequiredVersion } from "../checkMinRequiredVersion"; import { Command } from "../command"; import { FirebaseError } from "../error"; +import { logger } from "../logger"; import { getProjectId, needProjectId } from "../projectUtils"; import * as extensionsApi from "../extensions/extensionsApi"; import { ExtensionVersion, ExtensionSource } from "../extensions/types"; @@ -17,13 +19,11 @@ import { createSourceFromLocation, ensureExtensionsApiEnabled, logPrefix, - promptForOfficialExtension, promptForValidInstanceId, diagnoseAndFixProject, - isUrlPath, isLocalPath, - canonicalizeRefInput, } from "../extensions/extensionsHelper"; +import { resolveVersion } from "../deploy/extensions/planner"; import { getRandomString } from "../extensions/utils"; import { requirePermissions } from "../requirePermissions"; import * as utils from "../utils"; @@ -40,7 +40,7 @@ marked.setOptions({ /** * Command for installing an extension */ -export const command = new Command("ext:install [extensionName]") +export const command = new Command("ext:install [extensionRef]") .description( "add an uploaded extension to firebase.json if [publisherId/extensionId] is provided;" + "or, add a local extension if [localPath] is provided" @@ -51,67 +51,80 @@ export const command = new Command("ext:install [extensionName]") .before(ensureExtensionsApiEnabled) .before(checkMinRequiredVersion, "extMinVersion") .before(diagnoseAndFixProject) - .action(async (extensionName: string, options: Options) => { - const projectId = getProjectId(options); - // TODO(b/230598656): Clean up paramsEnvPath after v11 launch. - const paramsEnvPath = ""; - let learnMore = false; - if (!extensionName) { - if (options.interactive) { - learnMore = true; - extensionName = await promptForOfficialExtension( - "Which official extension do you wish to install?\n" + - " Select an extension, then press Enter to learn more." - ); - } else { - throw new FirebaseError( - `Unable to find published extension '${clc.bold(extensionName)}'. ` + - `Run ${clc.bold( - "firebase ext:install -i" - )} to select from the list of all available published extensions.` - ); - } - } - let source; - let extensionVersion; - - // TODO(b/220900194): Remove when deprecating old install flow. - // --local doesn't support urlPath so this will become dead codepath. - if (isUrlPath(extensionName)) { - throw new FirebaseError( - `Installing with a source url is no longer supported in the CLI. Please use Firebase Console instead.` - ); - } + .action(async (extensionRef: string, options: Options) => { if (options.local) { utils.logLabeledWarning( logPrefix, "As of firebase-tools@11.0.0, the `--local` flag is no longer required, as it is the default behavior." ); } - + if (!extensionRef) { + throw new FirebaseError( + "Extension ref is required to install. To see a full list of available extensions, go to Extensions Hub (https://extensions.dev/extensions)." + ); + } + let source: ExtensionSource | undefined; + let extensionVersion: ExtensionVersion | undefined; + const projectId = getProjectId(options); // If the user types in a local path (prefixed with ~/, ../, or ./), install from local source. // Otherwise, treat the input as an extension reference and proceed with reference-based installation. - if (isLocalPath(extensionName)) { + if (isLocalPath(extensionRef)) { // TODO(b/228444119): Create source should happen at deploy time. // Should parse spec locally so we don't need project ID. - source = await createSourceFromLocation(needProjectId({ projectId }), extensionName); - await displayExtInfo(extensionName, "", source.spec); + source = await createSourceFromLocation(needProjectId({ projectId }), extensionRef); + await displayExtensionVersionInfo({ spec: source.spec }); void trackGA4("extension_added_to_manifest", { published: "local", interactive: options.nonInteractive ? "false" : "true", }); } else { - extensionName = await canonicalizeRefInput(extensionName); - extensionVersion = await extensionsApi.getExtensionVersion(extensionName); - + const extension = await extensionsApi.getExtension(extensionRef); + const ref = refs.parse(extensionRef); + ref.version = await resolveVersion(ref, extension); + const extensionVersionRef = refs.toExtensionVersionRef(ref); + extensionVersion = await extensionsApi.getExtensionVersion(extensionVersionRef); void trackGA4("extension_added_to_manifest", { published: extensionVersion.listing?.state === "APPROVED" ? "published" : "uploaded", interactive: options.nonInteractive ? "false" : "true", }); - await infoExtensionVersion({ - extensionName, + await displayExtensionVersionInfo({ + spec: extensionVersion.spec, extensionVersion, + latestApprovedVersion: extension.latestApprovedVersion, + latestVersion: extension.latestVersion, }); + if (extensionVersion.state === "DEPRECATED") { + throw new FirebaseError( + `Extension version ${clc.bold( + extensionVersionRef + )} is deprecated and cannot be installed. To install the latest non-deprecated version, omit the version in the extension ref.` + ); + } + logger.info(); + // Check if selected version is older than the latest approved version, or the latest version only if there is no approved version. + if ( + (extension.latestApprovedVersion && + semver.gt(extension.latestApprovedVersion, extensionVersion.spec.version)) || + (!extension.latestApprovedVersion && + extension.latestVersion && + semver.gt(extension.latestVersion, extensionVersion.spec.version)) + ) { + const version = extension.latestApprovedVersion || extension.latestVersion; + logger.info( + `You are about to install extension version ${clc.bold( + extensionVersion.spec.version + )} which is older than the latest ${ + extension.latestApprovedVersion ? "accepted version" : "version" + } ${clc.bold(version!)}.` + ); + } + } + if (!source && !extensionVersion) { + throw new FirebaseError( + `Failed to parse ${clc.bold( + extensionRef + )} as an extension version or a path to a local extension. Please specify a valid reference.` + ); } if ( !(await confirm({ @@ -122,33 +135,18 @@ export const command = new Command("ext:install [extensionName]") ) { return; } - if (!source && !extensionVersion) { - throw new FirebaseError( - "Could not find a source. Please specify a valid source to continue." - ); - } const spec = source?.spec ?? extensionVersion?.spec; if (!spec) { throw new FirebaseError( `Could not find the extension.yaml for extension '${clc.bold( - extensionName + extensionRef )}'. Please make sure this is a valid extension and try again.` ); } - if (learnMore) { - utils.logLabeledBullet( - logPrefix, - `You selected: ${clc.bold(spec.displayName || "")}.\n` + - `${spec.description}\n` + - `View details: https://firebase.google.com/products/extensions/${spec.name}\n` - ); - } - try { return installToManifest({ - paramsEnvPath, projectId, - extensionName, + extensionRef, source, extVersion: extensionVersion, nonInteractive: options.nonInteractive, @@ -164,18 +162,9 @@ export const command = new Command("ext:install [extensionName]") } }); -async function infoExtensionVersion(args: { - extensionName: string; - extensionVersion: ExtensionVersion; -}): Promise { - const ref = refs.parse(args.extensionName); - await displayExtInfo(args.extensionName, ref.publisherId, args.extensionVersion.spec, true); -} - interface InstallExtensionOptions { - paramsEnvPath?: string; projectId?: string; - extensionName: string; + extensionRef: string; source?: ExtensionSource; extVersion?: ExtensionVersion; nonInteractive: boolean; @@ -189,14 +178,13 @@ interface InstallExtensionOptions { * @param options */ async function installToManifest(options: InstallExtensionOptions): Promise { - const { projectId, extensionName, extVersion, source, paramsEnvPath, nonInteractive, force } = - options; - const isLocalSource = isLocalPath(extensionName); + const { projectId, extensionRef, extVersion, source, nonInteractive, force } = options; + const isLocalSource = isLocalPath(extensionRef); const spec = extVersion?.spec ?? source?.spec; if (!spec) { throw new FirebaseError( - `Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.` + `Could not find the extension.yaml for ${extensionRef}. Please make sure this is a valid extension and try again.` ); } @@ -215,7 +203,6 @@ async function installToManifest(options: InstallExtensionOptions): Promise [updateSour newSpec: newExtensionVersion.spec, currentParams: oldParamValues, projectId, - // TODO(b/230598656): Clean up paramsEnvPath after v11 launch. - paramsEnvPath: "", nonInteractive: options.nonInteractive, instanceId, }); diff --git a/src/deploy/extensions/planner.ts b/src/deploy/extensions/planner.ts index 8a389e3e3dc..75c1778f2e9 100644 --- a/src/deploy/extensions/planner.ts +++ b/src/deploy/extensions/planner.ts @@ -207,32 +207,33 @@ export async function want(args: { } /** - * resolveVersion resolves a semver string to the max matching version. - * Exported for testing. - * @param publisherId - * @param extensionId - * @param version a semver or semver range + * Resolves a semver string to the max matching version. If no version is specified, + * it will default to the extension's latest approved version if set, otherwise to the latest version. + * + * @param ref the extension version ref + * @param extension the extension (optional) */ -export async function resolveVersion(ref: refs.Ref): Promise { +export async function resolveVersion(ref: refs.Ref, extension?: Extension): Promise { const extensionRef = refs.toExtensionRef(ref); - const extension = await extensionsApi.getExtension(extensionRef); - if (!ref.version || ref.version === "latest-approved") { - if (!extension.latestApprovedVersion) { + if (!ref.version && extension?.latestApprovedVersion) { + return extension.latestApprovedVersion; + } + if (ref.version === "latest-approved") { + if (!extension?.latestApprovedVersion) { throw new FirebaseError( `${extensionRef} has not been published to Extensions Hub (https://extensions.dev). To install it, you must specify the version you want to install.` ); } return extension.latestApprovedVersion; } - if (ref.version === "latest") { - if (!extension.latestVersion) { + if (!ref.version || ref.version === "latest") { + if (!extension?.latestVersion) { throw new FirebaseError( `${extensionRef} has no stable non-deprecated versions. If you wish to install a prerelease version, you must specify the version you want to install.` ); } return extension.latestVersion; } - const versions = await extensionsApi.listExtensionVersions(extensionRef, undefined, true); if (versions.length === 0) { throw new FirebaseError(`No versions found for ${extensionRef}`); diff --git a/src/extensions/displayExtensionInfo.ts b/src/extensions/displayExtensionInfo.ts index aef30fe192a..fc23c344f7b 100644 --- a/src/extensions/displayExtensionInfo.ts +++ b/src/extensions/displayExtensionInfo.ts @@ -1,12 +1,23 @@ import * as clc from "colorette"; import { marked } from "marked"; +import * as semver from "semver"; import * as TerminalRenderer from "marked-terminal"; +import * as path from "path"; -import * as utils from "../utils"; -import { logPrefix } from "./extensionsHelper"; +import * as refs from "../extensions/refs"; import { logger } from "../logger"; -import { FirebaseError } from "../error"; -import { Api, ExtensionSpec, Role, Resource, FUNCTIONS_RESOURCE_TYPE } from "./types"; +import { + Api, + ExtensionSpec, + ExtensionVersion, + LifecycleEvent, + ExternalService, + Role, + Param, + Resource, + FUNCTIONS_RESOURCE_TYPE, + EventDescriptor, +} from "./types"; import * as iam from "../gcp/iam"; import { SECRET_ROLE, usesSecrets } from "./secretsUtils"; @@ -18,33 +29,77 @@ const TASKS_ROLE = "cloudtasks.enqueuer"; const TASKS_API = "cloudtasks.googleapis.com"; /** - * displayExtInfo prints the extension info displayed when running ext:install. + * Displays info about an extension version, whether it is uploaded to the registry or a local spec. * - * @param extensionName name of the extension to display information about - * @param spec extension spec - * @param published whether or not the extension is a published extension - */ -export async function displayExtInfo( - extensionName: string, - publisher: string, - spec: ExtensionSpec, - published = false -): Promise { - const lines = []; - lines.push(`**Name**: ${spec.displayName}`); - if (publisher) { - lines.push(`**Publisher**: ${publisher}`); - } + * @param spec the extension spec + * @param extensionVersion the extension version + * */ +export async function displayExtensionVersionInfo(args: { + spec: ExtensionSpec; + extensionVersion?: ExtensionVersion; + latestApprovedVersion?: string; + latestVersion?: string; +}): Promise { + const { spec, extensionVersion, latestApprovedVersion, latestVersion } = args; + const lines: string[] = []; + const extensionRef = extensionVersion + ? refs.toExtensionRef(refs.parse(extensionVersion?.ref)) + : ""; + lines.push( + `${clc.bold("Extension:")} ${spec.displayName ?? "Unnamed extension"} ${ + extensionRef ? `(${extensionRef})` : "" + }` + ); if (spec.description) { - lines.push(`**Description**: ${spec.description}`); + lines.push(`${clc.bold("Description:")} ${spec.description}`); + } + let versionNote = ""; + const latestRelevantVersion = latestApprovedVersion || latestVersion; + if (latestRelevantVersion && semver.eq(spec.version, latestRelevantVersion)) { + versionNote = `- ${clc.green("Latest")}`; + } + if (extensionVersion?.state === "DEPRECATED") { + versionNote = `- ${clc.red("Deprecated")}`; } - if (published) { - if (spec.license) { - lines.push(`**License**: ${spec.license}`); + lines.push(`${clc.bold("Version:")} ${spec.version} ${versionNote}`); + if (extensionVersion) { + let reviewStatus: string; + switch (extensionVersion.listing?.state) { + case "APPROVED": + reviewStatus = clc.bold(clc.green("Accepted")); + break; + case "REJECTED": + reviewStatus = clc.bold(clc.red("Rejected")); + break; + default: + reviewStatus = clc.bold(clc.yellow("Unreviewed")); } - if (spec.sourceUrl) { - lines.push(`**Source code**: ${spec.sourceUrl}`); + lines.push(`${clc.bold("Review status:")} ${reviewStatus}`); + if (latestApprovedVersion) { + lines.push( + `${clc.bold("View in Extensions Hub:")} https://extensions.dev/extensions/${extensionRef}` + ); } + if (extensionVersion.buildSourceUri) { + const buildSourceUri = new URL(extensionVersion.buildSourceUri!); + buildSourceUri.pathname = path.join( + buildSourceUri.pathname, + extensionVersion.extensionRoot ?? "" + ); + lines.push(`${clc.bold("Source in GitHub:")} ${buildSourceUri}`); + } else { + lines.push( + `${clc.bold("Source download URI:")} ${extensionVersion.sourceDownloadUri ?? "-"}` + ); + } + } + lines.push(`${clc.bold("License:")} ${spec.license ?? "-"}`); + lines.push(displayResources(spec)); + if (spec.events?.length) { + lines.push(displayEvents(spec)); + } + if (spec.externalServices?.length) { + lines.push(displayExternalServices(spec)); } const apis = impliedApis(spec); if (apis.length) { @@ -54,33 +109,59 @@ export async function displayExtInfo( if (roles.length) { lines.push(await displayRoles(roles)); } - if (lines.length > 0) { - utils.logLabeledBullet(logPrefix, `information about '${clc.bold(extensionName)}':`); - const infoStr = lines.join("\n"); - // Convert to markdown and convert any trailing newlines to a single newline. - const formatted = marked(infoStr).replace(/\n+$/, "\n"); - logger.info(formatted); - // Return for testing purposes. - return lines; - } else { - throw new FirebaseError( - "Error occurred during installation: cannot parse info from source spec", - { - context: { - spec: spec, - extensionName: extensionName, - }, - } - ); - } + logger.info(`\n${lines.join("\n")}`); + return lines; } -/** - * Prints a clickable link where users can download the source code for an Extension Version. - */ -export function printSourceDownloadLink(sourceDownloadUri: string): void { - const sourceDownloadMsg = `Want to review the source code that will be installed? Download it here: ${sourceDownloadUri}`; - utils.logBullet(marked(sourceDownloadMsg)); +export function displayExternalServices(spec: ExtensionSpec) { + const lines = + spec.externalServices?.map((service: ExternalService) => { + return ` - ${clc.cyan(`${service.name} (${service.pricingUri})`)}`; + }) ?? []; + return clc.bold("External services used:\n") + lines.join("\n"); +} + +export function displayEvents(spec: ExtensionSpec) { + const lines = + spec.events?.map((event: EventDescriptor) => { + return ` - ${clc.magenta(event.type)}${event.description ? `: ${event.description}` : ""}`; + }) ?? []; + return clc.bold("Events emitted:\n") + lines.join("\n"); +} + +export function displayResources(spec: ExtensionSpec) { + const lines = spec.resources.map((resource: Resource) => { + let type: string = resource.type; + switch (resource.type) { + case "firebaseextensions.v1beta.function": + type = "Cloud Function (1st gen)"; + break; + case "firebaseextensions.v1beta.v2function": + type = "Cloud Function (2nd gen)"; + break; + default: + } + return ` - ${clc.blue(`${resource.name} (${type})`)}${ + resource.description ? `: ${resource.description}` : "" + }`; + }); + lines.push( + ...new Set( + spec.lifecycleEvents?.map((event: LifecycleEvent) => { + return ` - ${clc.blue(`${event.taskQueueTriggerFunction} (Cloud Task queue)`)}`; + }) + ) + ); + lines.push( + ...spec.params + .filter((param: Param) => { + return param.type === "SECRET"; + }) + .map((param: Param) => { + return ` - ${clc.blue(`${param.param} (Cloud Secret Manager secret)`)}`; + }) + ); + return clc.bold("Resources created:\n") + (lines.length ? lines.join("\n") : " - None"); } /** @@ -92,7 +173,7 @@ export function printSourceDownloadLink(sourceDownloadUri: string): void { */ export async function retrieveRoleInfo(role: string) { const res = await iam.getRole(role); - return ` ${res.title} (${res.description})`; + return ` - ${clc.yellow(res.title!)}${res.description ? `: ${res.description}` : ""}`; } async function displayRoles(roles: Role[]): Promise { @@ -101,14 +182,14 @@ async function displayRoles(roles: Role[]): Promise { return retrieveRoleInfo(role.role); }) ); - return clc.bold("**Roles granted to this Extension**:\n") + lines.join("\n"); + return clc.bold("Roles granted:\n") + lines.join("\n"); } function displayApis(apis: Api[]): string { const lines: string[] = apis.map((api: Api) => { - return ` ${api.apiName} (${api.reason})`; + return ` - ${clc.cyan(api.apiName!)}: ${api.reason}`; }); - return "**APIs used by this Extension**:\n" + lines.join("\n"); + return clc.bold("APIs used:\n") + lines.join("\n"); } function usesTasks(spec: ExtensionSpec): boolean { @@ -123,13 +204,13 @@ function impliedRoles(spec: ExtensionSpec): Role[] { if (usesSecrets(spec) && !spec.roles?.some((r: Role) => r.role === SECRET_ROLE)) { roles.push({ role: SECRET_ROLE, - reason: "Allows the extension to read secret values from Cloud Secret Manager", + reason: "Allows the extension to read secret values from Cloud Secret Manager.", }); } if (usesTasks(spec) && !spec.roles?.some((r: Role) => r.role === TASKS_ROLE)) { roles.push({ role: TASKS_ROLE, - reason: "Allows the extension to enqueue Cloud Tasks", + reason: "Allows the extension to enqueue Cloud Tasks.", }); } return roles.concat(spec.roles ?? []); @@ -140,7 +221,7 @@ function impliedApis(spec: ExtensionSpec): Api[] { if (usesTasks(spec) && !spec.apis?.some((a: Api) => a.apiName === TASKS_API)) { apis.push({ apiName: TASKS_API, - reason: "Allows the extension to enqueue Cloud Tasks", + reason: "Allows the extension to enqueue Cloud Tasks.", }); } diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index 063d526af9e..b61073423f5 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -42,7 +42,6 @@ import { envOverride } from "../utils"; import { getLocalChangelog } from "./change-log"; import { getProjectNumber } from "../getProjectNumber"; import { Constants } from "../emulator/constants"; -import { resolveVersion } from "../deploy/extensions/planner"; /** * SpecParamType represents the exact strings that the extensions @@ -1245,25 +1244,3 @@ export async function diagnoseAndFixProject(options: any): Promise { throw new FirebaseError("Unable to proceed until all issues are resolved."); } } - -/** - * Canonicalize a user-inputted ref string. - * 1. Infer firebase publisher if not provided - * 2. Infer "latest-approved" as the version if not provided - */ -export async function canonicalizeRefInput(refInput: string): Promise { - let inferredRef = refInput; - // TODO: Stop defaulting to 'firebase' publisher ID if none provided. - // Infer 'firebase' if publisher ID not provided. - if (refInput.split("/").length < 2) { - inferredRef = `firebase/${inferredRef}`; - } - // Infer 'latest-approved' if no version provided. - if (refInput.split("@").length < 2) { - inferredRef = `${inferredRef}@latest-approved`; - } - // Get the correct version for a given extension reference from the Registry API. - const ref = refs.parse(inferredRef); - ref.version = await resolveVersion(ref); - return refs.toExtensionVersionRef(ref); -} diff --git a/src/extensions/paramHelper.ts b/src/extensions/paramHelper.ts index aca46a91d4d..695410085b2 100644 --- a/src/extensions/paramHelper.ts +++ b/src/extensions/paramHelper.ts @@ -94,7 +94,6 @@ export async function getParams(args: { instanceId: string; paramSpecs: Param[]; nonInteractive?: boolean; - paramsEnvPath?: string; reconfiguring?: boolean; }): Promise> { let params: Record; @@ -118,7 +117,6 @@ export async function getParamsForUpdate(args: { newSpec: ExtensionSpec; currentParams: { [option: string]: string }; projectId?: string; - paramsEnvPath?: string; nonInteractive?: boolean; instanceId: string; }): Promise> { diff --git a/src/extensions/types.ts b/src/extensions/types.ts index a9c06cfe2f4..a2c29ce3137 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -122,6 +122,12 @@ export interface ExtensionSpec { readmeContent?: string; externalServices?: ExternalService[]; events?: EventDescriptor[]; + lifecycleEvents?: LifecycleEvent[]; +} + +export interface LifecycleEvent { + stage: "STAGE_UNSPECIFIED" | "ON_INSTALL" | "ON_UPDATE" | "ON_CONFIGURE"; + taskQueueTriggerFunction: string; } export interface EventDescriptor { diff --git a/src/extensions/updateHelper.ts b/src/extensions/updateHelper.ts index 521d91cb013..699107bdf3d 100644 --- a/src/extensions/updateHelper.ts +++ b/src/extensions/updateHelper.ts @@ -13,7 +13,7 @@ import { isLocalOrURLPath, } from "./extensionsHelper"; import * as utils from "../utils"; -import { displayExtInfo } from "./displayExtensionInfo"; +import { displayExtensionVersionInfo } from "./displayExtensionInfo"; function invalidSourceErrMsgTemplate(instanceId: string, source: string): string { return `Unable to update from the source \`${clc.bold( @@ -157,7 +157,7 @@ export async function updateFromLocalSource( localSource: string, existingSpec: ExtensionSpec ): Promise { - await displayExtInfo(instanceId, "", existingSpec, false); + await displayExtensionVersionInfo({ spec: existingSpec }); let source; try { source = await createSourceFromLocation(projectId, localSource); @@ -187,7 +187,7 @@ export async function updateFromUrlSource( urlSource: string, existingSpec: ExtensionSpec ): Promise { - await displayExtInfo(instanceId, "", existingSpec, false); + await displayExtensionVersionInfo({ spec: existingSpec }); let source; try { source = await createSourceFromLocation(projectId, urlSource); diff --git a/src/test/deploy/extensions/planner.spec.ts b/src/test/deploy/extensions/planner.spec.ts index b4a6ee757a6..6c082b5bbbb 100644 --- a/src/test/deploy/extensions/planner.spec.ts +++ b/src/test/deploy/extensions/planner.spec.ts @@ -35,7 +35,6 @@ function extensionVersion(version?: string): any { describe("Extensions Deployment Planner", () => { describe("resolveSemver", () => { let listExtensionVersionsStub: sinon.SinonStub; - let getExtensionStub: sinon.SinonStub; before(() => { listExtensionVersionsStub = sinon.stub(extensionsApi, "listExtensionVersions").resolves([ extensionVersion("0.1.0"), @@ -43,14 +42,10 @@ describe("Extensions Deployment Planner", () => { extensionVersion("0.2.0"), extensionVersion(), // Explicitly test that this doesn't break on bad data ]); - getExtensionStub = sinon - .stub(extensionsApi, "getExtension") - .resolves(extension("0.2.0", "0.1.1")); }); after(() => { listExtensionVersionsStub.restore(); - getExtensionStub.restore(); }); const cases = [ @@ -94,19 +89,25 @@ describe("Extensions Deployment Planner", () => { it(c.description, () => { if (!c.err) { expect( - planner.resolveVersion({ - publisherId: "test", - extensionId: "test", - version: c.in, - }) + planner.resolveVersion( + { + publisherId: "test", + extensionId: "test", + version: c.in, + }, + extension("0.2.0", "0.1.1") + ) ).to.eventually.equal(c.out); } else { expect( - planner.resolveVersion({ - publisherId: "test", - extensionId: "test", - version: c.in, - }) + planner.resolveVersion( + { + publisherId: "test", + extensionId: "test", + version: c.in, + }, + extension("0.2.0", "0.1.1") + ) ).to.eventually.be.rejected; } }); diff --git a/src/test/extensions/displayExtensionInfo.spec.ts b/src/test/extensions/displayExtensionInfo.spec.ts index 8057b149dba..7aa8dac1b10 100644 --- a/src/test/extensions/displayExtensionInfo.spec.ts +++ b/src/test/extensions/displayExtensionInfo.spec.ts @@ -3,18 +3,18 @@ import { expect } from "chai"; import * as iam from "../../gcp/iam"; import * as displayExtensionInfo from "../../extensions/displayExtensionInfo"; -import { ExtensionSpec, Param, Resource } from "../../extensions/types"; +import { ExtensionSpec, ExtensionVersion, Resource } from "../../extensions/types"; import { ParamType } from "../../extensions/types"; const SPEC: ExtensionSpec = { name: "test", - displayName: "Old", - description: "descriptive", - version: "0.1.0", + displayName: "My Extension", + description: "My extension's description", + version: "1.0.0", license: "MIT", apis: [ - { apiName: "api1", reason: "" }, - { apiName: "api2", reason: "" }, + { apiName: "api1.googleapis.com", reason: "" }, + { apiName: "api2.googleapis.com", reason: "" }, ], roles: [ { role: "role1", reason: "" }, @@ -23,29 +23,53 @@ const SPEC: ExtensionSpec = { resources: [ { name: "resource1", type: "firebaseextensions.v1beta.function", description: "desc" }, { name: "resource2", type: "other", description: "" } as unknown as Resource, + { + name: "taskResource", + type: "firebaseextensions.v1beta.function", + properties: { + taskQueueTrigger: {}, + }, + }, ], author: { authorName: "Tester", url: "firebase.google.com" }, contributors: [{ authorName: "Tester 2" }], billingRequired: true, sourceUrl: "test.com", - params: [], + params: [ + { + param: "secret", + label: "Secret", + type: ParamType.SECRET, + }, + ], systemParams: [], + events: [ + { + type: "abc.def.my-event", + description: "desc", + }, + ], + lifecycleEvents: [ + { + stage: "ON_INSTALL", + taskQueueTriggerFunction: "taskResource", + }, + ], }; -const TASK_FUNCTION_RESOURCE: Resource = { - name: "taskResource", - type: "firebaseextensions.v1beta.function", - properties: { - taskQueueTrigger: {}, +const EXT_VERSION: ExtensionVersion = { + name: "publishers/pub/extensions/my-ext/versions/1.0.0", + ref: "pub/my-ext@1.0.0", + state: "PUBLISHED", + spec: SPEC, + hash: "abc123", + sourceDownloadUri: "https://google.com", + buildSourceUri: "https://github.com/pub/extensions/my-ext", + listing: { + state: "APPROVED", }, }; -const SECRET_PARAM: Param = { - param: "secret", - label: "Secret", - type: ParamType.SECRET, -}; - describe("displayExtensionInfo", () => { describe("displayExtInfo", () => { let getRoleStub: sinon.SinonStub; @@ -74,91 +98,51 @@ describe("displayExtensionInfo", () => { }); it("should display info during install", async () => { - const loggedLines = await displayExtensionInfo.displayExtInfo(SPEC.name, "", SPEC); - const expected: string[] = [ - "**Name**: Old", - "**Description**: descriptive", - "**APIs used by this Extension**:\n api1 ()\n api2 ()", - "\u001b[1m**Roles granted to this Extension**:\n\u001b[22m Role 1 (a role)\n Role 2 (a role)", - ]; - expect(loggedLines.length).to.eql(expected.length); - expect(loggedLines[0]).to.include("Old"); - expect(loggedLines[1]).to.include("descriptive"); - expect(loggedLines[2]).to.include("api1"); - expect(loggedLines[2]).to.include("api2"); - expect(loggedLines[3]).to.include("Role 1"); - expect(loggedLines[3]).to.include("Role 2"); + const loggedLines = await displayExtensionInfo.displayExtensionVersionInfo({ spec: SPEC }); + expect(loggedLines[0]).to.include(SPEC.displayName); + expect(loggedLines[1]).to.include(SPEC.description); + expect(loggedLines[2]).to.include(SPEC.version); + expect(loggedLines[3]).to.include(SPEC.license); + expect(loggedLines[4]).to.include("resource1 (Cloud Function (1st gen))"); + expect(loggedLines[4]).to.include("resource2 (other)"); + expect(loggedLines[4]).to.include("taskResource (Cloud Function (1st gen))"); + expect(loggedLines[4]).to.include("taskResource (Cloud Task queue)"); + expect(loggedLines[4]).to.include("secret (Cloud Secret Manager secret)"); + expect(loggedLines[5]).to.include("abc.def.my-event"); + expect(loggedLines[6]).to.include("api1.googleapis.com"); + expect(loggedLines[6]).to.include("api1.googleapis.com"); + expect(loggedLines[6]).to.include("cloudtasks.googleapis.com"); + expect(loggedLines[7]).to.include("Role 1"); + expect(loggedLines[7]).to.include("Role 2"); + expect(loggedLines[7]).to.include("Cloud Task Enqueuer"); }); it("should display additional information for a published extension", async () => { - const loggedLines = await displayExtensionInfo.displayExtInfo( - SPEC.name, - "testpublisher", - SPEC, - true - ); - const expected: string[] = [ - "**Name**: Old", - "**Publisher**: testpublisher", - "**Description**: descriptive", - "**License**: MIT", - "**Source code**: test.com", - "**APIs used by this Extension**:\n api1 ()\n api2 ()", - "\u001b[1m**Roles granted to this Extension**:\n\u001b[22m Role 1 (a role)\n Role 2 (a role)", - ]; - expect(loggedLines.length).to.eql(expected.length); - expect(loggedLines[0]).to.include("Old"); - expect(loggedLines[1]).to.include("testpublisher"); - expect(loggedLines[2]).to.include("descriptive"); - expect(loggedLines[3]).to.include("MIT"); - expect(loggedLines[4]).to.include("test.com"); - expect(loggedLines[5]).to.include("api1"); - expect(loggedLines[5]).to.include("api2"); - expect(loggedLines[6]).to.include("Role 1"); - expect(loggedLines[6]).to.include("Role 2"); - }); - - it("should display role and api for Cloud Tasks during install", async () => { - const specWithTasks = JSON.parse(JSON.stringify(SPEC)) as ExtensionSpec; - specWithTasks.resources.push(TASK_FUNCTION_RESOURCE); - - const loggedLines = await displayExtensionInfo.displayExtInfo(SPEC.name, "", specWithTasks); - const expected: string[] = [ - "**Name**: Old", - "**Description**: descriptive", - "**APIs used by this Extension**:\n api1 ()\n api2 ()", - "\u001b[1m**Roles granted to this Extension**:\n\u001b[22m Role 1 (a role)\n Role 2 (a role)\n Cloud Task Enqueuer (Enqueue tasks)", - ]; - expect(loggedLines.length).to.eql(expected.length); - expect(loggedLines[0]).to.include("Old"); - expect(loggedLines[1]).to.include("descriptive"); - expect(loggedLines[2]).to.include("api1"); - expect(loggedLines[2]).to.include("api2"); - expect(loggedLines[2]).to.include("Cloud Tasks"); - expect(loggedLines[3]).to.include("Role 1"); - expect(loggedLines[3]).to.include("Role 2"); - expect(loggedLines[3]).to.include("Cloud Task Enqueuer"); - }); - - it("should display role for Cloud Secret Manager during install", async () => { - const specWithSecret = JSON.parse(JSON.stringify(SPEC)) as ExtensionSpec; - specWithSecret.params.push(SECRET_PARAM); - - const loggedLines = await displayExtensionInfo.displayExtInfo(SPEC.name, "", specWithSecret); - const expected: string[] = [ - "**Name**: Old", - "**Description**: descriptive", - "**APIs used by this Extension**:\n api1 ()\n api2 ()", - "\u001b[1m**Roles granted to this Extension**:\n\u001b[22m Role 1 (a role)\n Role 2 (a role)\n Secret Accessor (Access secrets)", - ]; - expect(loggedLines.length).to.eql(expected.length); - expect(loggedLines[0]).to.include("Old"); - expect(loggedLines[1]).to.include("descriptive"); - expect(loggedLines[2]).to.include("api1"); - expect(loggedLines[2]).to.include("api2"); - expect(loggedLines[3]).to.include("Role 1"); - expect(loggedLines[3]).to.include("Role 2"); - expect(loggedLines[3]).to.include("Secret Accessor"); + const loggedLines = await displayExtensionInfo.displayExtensionVersionInfo({ + spec: SPEC, + extensionVersion: EXT_VERSION, + latestApprovedVersion: "1.0.0", + latestVersion: "1.0.0", + }); + expect(loggedLines[0]).to.include(SPEC.displayName); + expect(loggedLines[1]).to.include(SPEC.description); + expect(loggedLines[2]).to.include(SPEC.version); + expect(loggedLines[3]).to.include("Accepted"); + expect(loggedLines[4]).to.include("View in Extensions Hub"); + expect(loggedLines[5]).to.include(EXT_VERSION.buildSourceUri); + expect(loggedLines[6]).to.include(SPEC.license); + expect(loggedLines[7]).to.include("resource1 (Cloud Function (1st gen))"); + expect(loggedLines[7]).to.include("resource2 (other)"); + expect(loggedLines[7]).to.include("taskResource (Cloud Function (1st gen))"); + expect(loggedLines[7]).to.include("taskResource (Cloud Task queue)"); + expect(loggedLines[7]).to.include("secret (Cloud Secret Manager secret)"); + expect(loggedLines[8]).to.include("abc.def.my-event"); + expect(loggedLines[9]).to.include("api1.googleapis.com"); + expect(loggedLines[9]).to.include("api1.googleapis.com"); + expect(loggedLines[9]).to.include("cloudtasks.googleapis.com"); + expect(loggedLines[10]).to.include("Role 1"); + expect(loggedLines[10]).to.include("Role 2"); + expect(loggedLines[10]).to.include("Cloud Task Enqueuer"); }); }); }); diff --git a/src/test/extensions/extensionsHelper.spec.ts b/src/test/extensions/extensionsHelper.spec.ts index 446890600c6..51c8fab7b99 100644 --- a/src/test/extensions/extensionsHelper.spec.ts +++ b/src/test/extensions/extensionsHelper.spec.ts @@ -22,8 +22,6 @@ import { } from "../../extensions/types"; import { Readable } from "stream"; import { ArchiveResult } from "../../archiveDirectory"; -import { canonicalizeRefInput } from "../../extensions/extensionsHelper"; -import * as planner from "../../deploy/extensions/planner"; describe("extensionsHelper", () => { describe("substituteParams", () => { @@ -1004,37 +1002,4 @@ describe("extensionsHelper", () => { ).to.eql("Prerelease"); }); }); - - describe(`${canonicalizeRefInput.name}`, () => { - let resolveVersionStub: sinon.SinonStub; - beforeEach(() => { - resolveVersionStub = sinon.stub(planner, "resolveVersion").resolves("10.1.1"); - }); - afterEach(() => { - resolveVersionStub.restore(); - }); - it("should do nothing to a valid ref", async () => { - expect(await canonicalizeRefInput("firebase/bigquery-export@10.1.1")).to.equal( - "firebase/bigquery-export@10.1.1" - ); - }); - - it("should infer latest version", async () => { - expect(await canonicalizeRefInput("firebase/bigquery-export")).to.equal( - "firebase/bigquery-export@10.1.1" - ); - }); - - it("should infer publisher name as firebase", async () => { - expect(await canonicalizeRefInput("firebase/bigquery-export")).to.equal( - "firebase/bigquery-export@10.1.1" - ); - }); - - it("should infer publisher name as firebase and also infer latest as version", async () => { - expect(await canonicalizeRefInput("bigquery-export")).to.equal( - "firebase/bigquery-export@10.1.1" - ); - }); - }); }); From 417c4b483ebbbdaf36a21be2da72e0ad098f0b3c Mon Sep 17 00:00:00 2001 From: Sairam Sakhamuri Date: Tue, 27 Jun 2023 16:26:11 -0700 Subject: [PATCH 1054/1699] Discovery: Added node runtime. (#5993) * Added runtime command discovery * Resolved comments * Added error case to analyse codebase method * Updated install command * Reorganized tests and removed unwated promise.resolve stmt * Added review changes on install command and node version string array * Changes to node.ts to include additional condions on run script * Added code comments to types * Added undefied to return if no cmd * Added undefied to return if no cmd --- .../compose/discover/runtime/node.ts | 212 +++++++++++++++ src/frameworks/compose/discover/types.ts | 2 +- .../compose/discover/runtime/node.spec.ts | 241 ++++++++++++++++++ 3 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 src/frameworks/compose/discover/runtime/node.ts create mode 100644 src/test/frameworks/compose/discover/runtime/node.spec.ts diff --git a/src/frameworks/compose/discover/runtime/node.ts b/src/frameworks/compose/discover/runtime/node.ts new file mode 100644 index 00000000000..976657deac9 --- /dev/null +++ b/src/frameworks/compose/discover/runtime/node.ts @@ -0,0 +1,212 @@ +import { readOrNull } from "../filesystem"; +import { FileSystem, FrameworkSpec, Runtime } from "../types"; +import { RuntimeSpec } from "../types"; +import { frameworkMatcher } from "../frameworkMatcher"; +import { LifecycleCommands } from "../types"; +import { Command } from "../types"; +import { FirebaseError } from "../../../../error"; +import { logger } from "../../../../../src/logger"; +import { conjoinOptions } from "../../../utils"; + +export interface PackageJSON { + dependencies?: Record; + devDependencies?: Record; + scripts?: Record; + engines?: Record; +} +type PackageManager = "npm" | "yarn"; + +const supportedNodeVersions: string[] = ["18"]; +const NODE_RUNTIME_ID = "nodejs"; +const PACKAGE_JSON = "package.json"; +const YARN_LOCK = "yarn.lock"; + +export class NodejsRuntime implements Runtime { + private readonly runtimeRequiredFiles: string[] = [PACKAGE_JSON]; + private readonly contentCache: Record = {}; + + // Checks if the codebase is using Node as runtime. + async match(fs: FileSystem): Promise { + const areAllFilesPresent = await Promise.all( + this.runtimeRequiredFiles.map((file) => fs.exists(file)) + ); + + return areAllFilesPresent.every((present) => present); + } + + getRuntimeName(): string { + return NODE_RUNTIME_ID; + } + + getNodeImage(engine: Record | undefined): string { + // If no version is mentioned explicitly, assuming application is compatible with latest version. + if (!engine || !engine.node) { + return `node:${supportedNodeVersions[supportedNodeVersions.length - 1]}-slim`; + } + const versionNumber = engine.node; + + if (!supportedNodeVersions.includes(versionNumber)) { + throw new FirebaseError( + `This integration expects Node version ${conjoinOptions( + supportedNodeVersions, + "or" + )}. You're running version ${versionNumber}, which is not compatible.` + ); + } + + return `node:${versionNumber}-slim`; + } + + async getPackageManager(fs: FileSystem): Promise { + try { + if (await fs.exists(YARN_LOCK)) { + return "yarn"; + } + + return "npm"; + } catch (error: any) { + logger.error("Failed to check files to identify package manager"); + throw error; + } + } + + getDependencies(packageJSON: PackageJSON): Record { + return { ...packageJSON.dependencies, ...packageJSON.devDependencies }; + } + + packageManagerInstallCommand(packageManager: PackageManager): string | undefined { + const packages: string[] = []; + if (packageManager === "yarn") { + packages.push("yarn"); + } + if (!packages.length) { + return undefined; + } + + return `npm install --global ${packages.join(" ")}`; + } + + installCommand(fs: FileSystem, packageManager: PackageManager): string { + let installCmd = "npm install"; + + if (packageManager === "yarn") { + installCmd = "yarn install"; + } + + return installCmd; + } + + async detectedCommands( + packageManager: PackageManager, + scripts: Record | undefined, + matchedFramework: FrameworkSpec | null, + fs: FileSystem + ): Promise { + return { + build: this.getBuildCommand(packageManager, scripts, matchedFramework), + dev: this.getDevCommand(packageManager, scripts, matchedFramework), + run: await this.getRunCommand(packageManager, scripts, matchedFramework, fs), + }; + } + + executeScript(packageManager: string, scriptName: string): string { + return `${packageManager} run ${scriptName}`; + } + + executeFrameworkCommand(packageManager: PackageManager, command: Command): Command { + if (packageManager === "npm" || packageManager === "yarn") { + command.cmd = "npx " + command.cmd; + } + + return command; + } + + getBuildCommand( + packageManager: PackageManager, + scripts: Record | undefined, + matchedFramework: FrameworkSpec | null + ): Command | undefined { + let buildCommand: Command = { cmd: "" }; + if (scripts?.build) { + buildCommand.cmd = this.executeScript(packageManager, "build"); + } else if (matchedFramework && matchedFramework.commands?.build) { + buildCommand = matchedFramework.commands.build; + buildCommand = this.executeFrameworkCommand(packageManager, buildCommand); + } + + return buildCommand.cmd === "" ? undefined : buildCommand; + } + + getDevCommand( + packageManager: PackageManager, + scripts: Record | undefined, + matchedFramework: FrameworkSpec | null + ): Command | undefined { + let devCommand: Command = { cmd: "", env: { NODE_ENV: "dev" } }; + if (scripts?.dev) { + devCommand.cmd = this.executeScript(packageManager, "dev"); + } else if (matchedFramework && matchedFramework.commands?.dev) { + devCommand = matchedFramework.commands.dev; + devCommand = this.executeFrameworkCommand(packageManager, devCommand); + } + + return devCommand.cmd === "" ? undefined : devCommand; + } + + async getRunCommand( + packageManager: PackageManager, + scripts: Record | undefined, + matchedFramework: FrameworkSpec | null, + fs: FileSystem + ): Promise { + let runCommand: Command = { cmd: "", env: { NODE_ENV: "production" } }; + if (scripts?.start) { + runCommand.cmd = this.executeScript(packageManager, "start"); + } else if (matchedFramework && matchedFramework.commands?.run) { + runCommand = matchedFramework.commands.run; + runCommand = this.executeFrameworkCommand(packageManager, runCommand); + } else if (scripts?.main) { + runCommand.cmd = `node ${scripts.main}`; + } else if (await fs.exists("index.js")) { + runCommand.cmd = `node index.js`; + } + + return runCommand.cmd === "" ? undefined : runCommand; + } + + async analyseCodebase(fs: FileSystem, allFrameworkSpecs: FrameworkSpec[]): Promise { + try { + const packageJSONRaw = await readOrNull(fs, PACKAGE_JSON); + let packageJSON: PackageJSON = {}; + if (packageJSONRaw) { + packageJSON = JSON.parse(packageJSONRaw) as PackageJSON; + } + const packageManager = await this.getPackageManager(fs); + const nodeImage = this.getNodeImage(packageJSON.engines); + const dependencies = this.getDependencies(packageJSON); + const matchedFramework = await frameworkMatcher( + NODE_RUNTIME_ID, + fs, + allFrameworkSpecs, + dependencies + ); + + const runtimeSpec: RuntimeSpec = { + id: NODE_RUNTIME_ID, + baseImage: nodeImage, + packageManagerInstallCommand: this.packageManagerInstallCommand(packageManager), + installCommand: this.installCommand(fs, packageManager), + detectedCommands: await this.detectedCommands( + packageManager, + packageJSON.scripts, + matchedFramework, + fs + ), + }; + + return runtimeSpec; + } catch (error: any) { + throw new FirebaseError(`Failed to parse engine: ${error}`); + } + } +} diff --git a/src/frameworks/compose/discover/types.ts b/src/frameworks/compose/discover/types.ts index 677e054ec27..b919e552c4b 100644 --- a/src/frameworks/compose/discover/types.ts +++ b/src/frameworks/compose/discover/types.ts @@ -6,7 +6,7 @@ export interface FileSystem { export interface Runtime { match(fs: FileSystem): Promise; getRuntimeName(): string; - analyseCodebase(fs: FileSystem, allFrameworkSpecs: FrameworkSpec[]): Promise; + analyseCodebase(fs: FileSystem, allFrameworkSpecs: FrameworkSpec[]): Promise; } export interface Command { diff --git a/src/test/frameworks/compose/discover/runtime/node.spec.ts b/src/test/frameworks/compose/discover/runtime/node.spec.ts new file mode 100644 index 00000000000..b2bbaee767c --- /dev/null +++ b/src/test/frameworks/compose/discover/runtime/node.spec.ts @@ -0,0 +1,241 @@ +import { MockFileSystem } from "../mockFileSystem"; +import { expect } from "chai"; +import { + NodejsRuntime, + PackageJSON, +} from "../../../../../frameworks/compose/discover/runtime/node"; +import { FrameworkSpec } from "../../../../../frameworks/compose/discover/types"; +import { FirebaseError } from "../../../../../error"; + +describe("NodejsRuntime", () => { + let nodeJSRuntime: NodejsRuntime; + let allFrameworks: FrameworkSpec[]; + + before(() => { + nodeJSRuntime = new NodejsRuntime(); + allFrameworks = [ + { + id: "express", + runtime: "nodejs", + requiredDependencies: [{ name: "express" }], + }, + { + id: "next", + runtime: "nodejs", + requiredDependencies: [{ name: "next" }], + requiredFiles: [["next.config.js"], "next.config.ts"], + embedsFrameworks: ["react"], + commands: { + dev: { + cmd: "next dev", + env: { NODE_ENV: "dev" }, + }, + }, + }, + ]; + }); + + describe("getNodeImage", () => { + it("should return a valid node Image", () => { + const version: Record = { + node: "18", + }; + const actualImage = nodeJSRuntime.getNodeImage(version); + const expectedImage = "node:18-slim"; + + expect(actualImage).to.deep.equal(expectedImage); + }); + }); + + describe("getPackageManager", () => { + it("should return yarn package manager", async () => { + const fileSystem = new MockFileSystem({ + "yarn.lock": "It is test file", + }); + const actual = await nodeJSRuntime.getPackageManager(fileSystem); + const expected = "yarn"; + + expect(actual).to.equal(expected); + }); + }); + + describe("getDependencies", () => { + it("should return direct and transitive dependencies", () => { + const packageJSON: PackageJSON = { + dependencies: { + express: "^4.18.2", + }, + devDependencies: { + nodemon: "^2.0.12", + mocha: "^9.1.1", + }, + }; + const actual = nodeJSRuntime.getDependencies(packageJSON); + const expected = { + express: "^4.18.2", + nodemon: "^2.0.12", + mocha: "^9.1.1", + }; + + expect(actual).to.deep.equal(expected); + }); + }); + + describe("detectedCommands", () => { + it("should prepend npx to framework commands", async () => { + const fs = new MockFileSystem({ + "package.json": "Test file", + }); + const matchedFramework: FrameworkSpec = { + id: "next", + runtime: "nodejs", + requiredDependencies: [], + commands: { + dev: { + cmd: "next dev", + env: { NODE_ENV: "dev" }, + }, + }, + }; + const scripts = { + build: "next build", + start: "next start", + }; + + const actual = await nodeJSRuntime.detectedCommands("yarn", scripts, matchedFramework, fs); + const expected = { + build: { + cmd: "yarn run build", + }, + dev: { + cmd: "npx next dev", + env: { NODE_ENV: "dev" }, + }, + run: { + cmd: "yarn run start", + env: { NODE_ENV: "production" }, + }, + }; + + expect(actual).to.deep.equal(expected); + }); + + it("should prefer scripts over framework commands", async () => { + const fs = new MockFileSystem({ + "package.json": "Test file", + }); + const matchedFramework: FrameworkSpec = { + id: "next", + runtime: "nodejs", + requiredDependencies: [], + commands: { + build: { + cmd: "next build testing", + }, + run: { + cmd: "next start testing", + env: { NODE_ENV: "production" }, + }, + dev: { + cmd: "next dev", + env: { NODE_ENV: "dev" }, + }, + }, + }; + const scripts = { + build: "next build", + start: "next start", + }; + + const actual = await nodeJSRuntime.detectedCommands("yarn", scripts, matchedFramework, fs); + const expected = { + build: { + cmd: "yarn run build", + }, + dev: { + cmd: "npx next dev", + env: { NODE_ENV: "dev" }, + }, + run: { + cmd: "yarn run start", + env: { NODE_ENV: "production" }, + }, + }; + + expect(actual).to.deep.equal(expected); + }); + }); + + describe("analyseCodebase", () => { + it("should return runtime specs", async () => { + const fileSystem = new MockFileSystem({ + "next.config.js": "For testing", + "next.config.ts": "For testing", + "package.json": JSON.stringify({ + scripts: { + build: "next build", + start: "next start", + }, + dependencies: { + next: "13.4.5", + react: "18.2.0", + }, + engines: { + node: "18", + }, + }), + }); + + const actual = await nodeJSRuntime.analyseCodebase(fileSystem, allFrameworks); + const expected = { + id: "nodejs", + baseImage: "node:18-slim", + packageManagerInstallCommand: undefined, + installCommand: "npm install", + detectedCommands: { + build: { + cmd: "npm run build", + }, + dev: { + cmd: "npx next dev", + env: { NODE_ENV: "dev" }, + }, + run: { + cmd: "npm run start", + env: { NODE_ENV: "production" }, + }, + }, + }; + + expect(actual).to.deep.equal(expected); + }); + + it("should return error", async () => { + const fileSystem = new MockFileSystem({ + "next.config.js": "For testing purpose.", + "next.config.ts": "For testing purpose.", + "package.json": JSON.stringify({ + scripts: { + build: "next build", + start: "next start", + }, + dependencies: { + // Having both express and next as dependencies. + express: "2.0.8", + next: "13.4.5", + react: "18.2.0", + }, + engines: { + node: "18", + }, + }), + }); + + // Failed with multiple framework matches + await expect(nodeJSRuntime.analyseCodebase(fileSystem, allFrameworks)).to.be.rejectedWith( + FirebaseError, + "Failed to parse engine" + ); + }); + }); +}); From ac28f26b3a8525e239a41f60afef49b0fc35aa38 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Wed, 28 Jun 2023 10:18:30 -0400 Subject: [PATCH 1055/1699] Increased create extensions instance timeout to 1h to match the backend (#5969) --- CHANGELOG.md | 1 + src/extensions/extensionsApi.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2668eb75f0f..0ddb4feca9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ +- Increased extension instance create poll timeout to 1h to match backend (#5969). - Refactored `ext:install` to use the latest extension metadata. (#5997) diff --git a/src/extensions/extensionsApi.ts b/src/extensions/extensionsApi.ts index bcb50764faf..1ec8b140028 100644 --- a/src/extensions/extensionsApi.ts +++ b/src/extensions/extensionsApi.ts @@ -58,7 +58,7 @@ async function createInstanceHelper( apiOrigin: extensionsOrigin, apiVersion: EXTENSIONS_API_VERSION, operationResourceName: createRes.body.name, - masterTimeout: 600000, + masterTimeout: 3600000, }); return pollRes; } From 654e88483bb650b2544024d8232d7091867e5cdb Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Wed, 28 Jun 2023 11:50:54 -0700 Subject: [PATCH 1056/1699] Normalized extension root path before usage in ext:dev:upload. (#6054) * Normalized extension root path before usage. * Update CHANGELOG.md * Update publisherApi.ts * Update extensionsHelper.ts * Update CHANGELOG.md * Replaced join() with normalize() and fixed regex. * Update extensionsHelper.ts * Update extensionsHelper.ts --- CHANGELOG.md | 1 + src/extensions/extensionsHelper.ts | 31 +++++++++++++++--------------- src/extensions/publisherApi.ts | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ddb4feca9c..5484a42cfb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Increased extension instance create poll timeout to 1h to match backend (#5969). - Refactored `ext:install` to use the latest extension metadata. (#5997) +- Normalized extension root path before usage in `ext:dev:upload`. (#6054) diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index b61073423f5..a5025142fe7 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -408,24 +408,17 @@ export async function promptForValidRepoURI(): Promise { } /** - * Prompts for a valid extension root. + * Prompts for an extension root. * * @param defaultRoot the default extension root */ -export async function promptForValidExtensionRoot(defaultRoot: string): Promise { - let rootIsValid = false; - let extensionRoot = ""; - while (!rootIsValid) { - extensionRoot = await promptOnce({ - type: "input", - message: - "Enter this extension's root directory in the repo (defaults to previous root if set):", - default: defaultRoot, - }); - // TODO: Add real directory path validation. - rootIsValid = true; - } - return extensionRoot; +export async function promptForExtensionRoot(defaultRoot: string): Promise { + return await promptOnce({ + type: "input", + message: + "Enter this extension's root directory in the repo (defaults to previous root if set):", + default: defaultRoot, + }); } /** @@ -817,11 +810,17 @@ export async function uploadExtensionVersionFromGitHubSource(args: { if (!extensionRoot) { const defaultRoot = "/"; if (!args.nonInteractive) { - extensionRoot = await promptForValidExtensionRoot(defaultRoot); + extensionRoot = await promptForExtensionRoot(defaultRoot); } else { extensionRoot = defaultRoot; } } + // Normalize root path and strip leading and trailing slashes and all `../`. + const normalizedRoot = path + .normalize(extensionRoot) + .replaceAll(/^\/|\/$/g, "") + .replaceAll(/^(\.\.\/)*/g, ""); + extensionRoot = normalizedRoot || "/"; // Prompt for source ref and default to HEAD. let sourceRef = args.sourceRef; diff --git a/src/extensions/publisherApi.ts b/src/extensions/publisherApi.ts index 877ccac7198..b1caa6f177f 100644 --- a/src/extensions/publisherApi.ts +++ b/src/extensions/publisherApi.ts @@ -205,7 +205,7 @@ export async function createExtensionVersionFromGitHubSource(args: { ExtensionVersion >(`/${refs.toExtensionName(ref)}/versions:createFromSource`, { versionId: ref.version, - extensionRoot: args.extensionRoot ?? "/", + extensionRoot: args.extensionRoot || "/", githubRepositorySource: { uri: args.repoUri, sourceRef: args.sourceRef, From 197506a739b83dc7ca5ceaef6bb94d1be520676a Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:05:00 -0400 Subject: [PATCH 1057/1699] Migrates functions metrics to GA4 (#6053) * track functions metrics with ga4 * remove old track() calls --- src/deploy/functions/args.ts | 14 ++ src/deploy/functions/build.ts | 2 + src/deploy/functions/ensure.ts | 3 - src/deploy/functions/prepare.ts | 35 ++-- src/deploy/functions/release/index.ts | 2 +- src/deploy/functions/release/reporter.ts | 77 +++++---- .../node/parseRuntimeAndValidateSDK.ts | 3 - .../functions/runtimes/node/versioning.ts | 2 - .../deploy/functions/release/reporter.spec.ts | 163 +++++++++--------- src/track.ts | 5 +- 10 files changed, 159 insertions(+), 147 deletions(-) diff --git a/src/deploy/functions/args.ts b/src/deploy/functions/args.ts index 1138b9d9491..bc6e1e21db8 100644 --- a/src/deploy/functions/args.ts +++ b/src/deploy/functions/args.ts @@ -2,6 +2,7 @@ import * as backend from "./backend"; import * as gcfV2 from "../../gcp/cloudfunctionsv2"; import * as projectConfig from "../../functions/projectConfig"; import * as deployHelper from "./functionsDeployHelper"; +import { Runtime } from "./runtimes"; // These types should probably be in a root deploy.ts, but we can only boil the ocean one bit at a time. interface CodebasePayload { @@ -49,6 +50,19 @@ export interface Context { gcfV1: string[]; gcfV2: string[]; }; + + // Tracks metrics about codebase deployments to send to GA4 + codebaseDeployEvents?: Record; +} + +export interface CodebaseDeployEvent { + params?: "env_only" | "with_secrets" | "none"; + runtime?: Runtime; + runtime_notice?: string; + fn_deploy_num_successes: number; + fn_deploy_num_failures: number; + fn_deploy_num_canceled: number; + fn_deploy_num_skipped: number; } export interface FirebaseConfig { diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index 222b0a75d4e..846a49043b3 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -6,12 +6,14 @@ import { FirebaseError } from "../../error"; import { assertExhaustive, mapObject, nullsafeVisitor } from "../../functional"; import { UserEnvsOpts, writeUserEnvs } from "../../functions/env"; import { FirebaseConfig } from "./args"; +import { Runtime } from "./runtimes"; /* The union of a customer-controlled deployment and potentially deploy-time defined parameters */ export interface Build { requiredAPIs: RequiredApi[]; endpoints: Record; params: params.Param[]; + runtime?: Runtime; } /** diff --git a/src/deploy/functions/ensure.ts b/src/deploy/functions/ensure.ts index b48740bd748..7006e872af0 100644 --- a/src/deploy/functions/ensure.ts +++ b/src/deploy/functions/ensure.ts @@ -8,7 +8,6 @@ import { logLabeledBullet, logLabeledSuccess } from "../../utils"; import { ensureServiceAgentRole } from "../../gcp/secretManager"; import { getFirebaseProject } from "../../management/projects"; import { assertExhaustive } from "../../functional"; -import { track } from "../../track"; import * as backend from "./backend"; const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime"; @@ -37,7 +36,6 @@ export async function defaultServiceAccount(e: backend.Endpoint): Promise): boolean { - // "firebase" key is always going to exist in runtime config. - // If any other key exists, we can assume that user is using runtime config. - return Object.keys(config).length > 1; -} /** * Prepare functions codebases for deploy. @@ -88,6 +82,8 @@ export async function prepare( runtimeConfig = { ...runtimeConfig, ...(await getFunctionsConfig(projectId)) }; } + context.codebaseDeployEvents = {}; + // ===Phase 1. Load codebases from source. const wantBuilds = await loadCodebases( context.config, @@ -155,15 +151,23 @@ export async function prepare( codebaseUsesEnvs.push(codebase); } + context.codebaseDeployEvents[codebase] = { + fn_deploy_num_successes: 0, + fn_deploy_num_failures: 0, + fn_deploy_num_canceled: 0, + fn_deploy_num_skipped: 0, + }; + if (wantBuild.params.length > 0) { if (wantBuild.params.every((p) => p.type !== "secret")) { - void track("functions_params_in_build", "env_only"); + context.codebaseDeployEvents[codebase].params = "env_only"; } else { - void track("functions_params_in_build", "with_secrets"); + context.codebaseDeployEvents[codebase].params = "with_secrets"; } } else { - void track("functions_params_in_build", "none"); + context.codebaseDeployEvents[codebase].params = "none"; } + context.codebaseDeployEvents[codebase].runtime = wantBuild.runtime; } // ===Phase 2.5. Before proceeding further, let's make sure that we don't have conflicting function names. @@ -214,18 +218,6 @@ export async function prepare( inferBlockingDetails(wantBackend); } - const tag = hasUserConfig(runtimeConfig) - ? codebaseUsesEnvs.length > 0 - ? "mixed" - : "runtime_config" - : codebaseUsesEnvs.length > 0 - ? "dotenv" - : "none"; - void track("functions_codebase_deploy_env_method", tag); - - const codebaseCnt = Object.keys(payload.functions).length; - void track("functions_codebase_deploy_count", codebaseCnt >= 5 ? "5+" : codebaseCnt.toString()); - // ===Phase 5. Enable APIs required by the deploying backends. const wantBackend = backend.merge(...Object.values(wantBackends)); const haveBackend = backend.merge(...Object.values(haveBackends)); @@ -468,6 +460,7 @@ export async function loadCodebases( // in order for .init() calls to succeed. GOOGLE_CLOUD_QUOTA_PROJECT: projectId, }); + wantBuilds[codebase].runtime = codebaseConfig.runtime; } return wantBuilds; } diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index 08c46191304..e32749feda8 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -76,7 +76,7 @@ export async function release( const summary = await fab.applyPlan(plan); - await reporter.logAndTrackDeployStats(summary); + await reporter.logAndTrackDeployStats(summary, context); reporter.printErrors(summary); // N.B. Fabricator::applyPlan updates the endpoints it deploys to include the diff --git a/src/deploy/functions/release/reporter.ts b/src/deploy/functions/release/reporter.ts index 61611c0a7ba..10a4a64757c 100644 --- a/src/deploy/functions/release/reporter.ts +++ b/src/deploy/functions/release/reporter.ts @@ -1,8 +1,9 @@ import * as backend from "../backend"; import * as clc from "colorette"; +import * as args from "../args"; import { logger } from "../../../logger"; -import { track } from "../../../track"; +import { trackGA4 } from "../../../track"; import * as utils from "../../../utils"; import { getFunctionLabel } from "../functionsDeployHelper"; @@ -56,7 +57,10 @@ export class AbortedDeploymentError extends DeploymentError { } /** Add debugger logs and GA metrics for deploy stats. */ -export async function logAndTrackDeployStats(summary: Summary): Promise { +export async function logAndTrackDeployStats( + summary: Summary, + context?: args.Context +): Promise { let totalTime = 0; let totalErrors = 0; let totalSuccesses = 0; @@ -64,54 +68,65 @@ export async function logAndTrackDeployStats(summary: Summary): Promise { const reports: Array> = []; const regions = new Set(); + const codebases = new Set(); for (const result of summary.results) { - const tag = triggerTag(result.endpoint); + const fnDeployEvent = { + platform: result.endpoint.platform, + trigger_type: backend.endpointTriggerType(result.endpoint), + region: result.endpoint.region, + runtime: result.endpoint.runtime, + status: !result.error + ? "success" + : result.error instanceof AbortedDeploymentError + ? "aborted" + : "failure", + duration: result.durationMs, + }; + reports.push(trackGA4("function_deploy", fnDeployEvent)); + regions.add(result.endpoint.region); + codebases.add(result.endpoint.codebase || "default"); totalTime += result.durationMs; if (!result.error) { totalSuccesses++; - reports.push(track("function_deploy_success", tag, result.durationMs)); + if (context?.codebaseDeployEvents?.[result.endpoint.codebase || "default"] !== undefined) { + context.codebaseDeployEvents[result.endpoint.codebase || "default"] + .fn_deploy_num_successes++; + } } else if (result.error instanceof AbortedDeploymentError) { totalAborts++; - reports.push(track("function_deploy_abort", tag, result.durationMs)); + if (context?.codebaseDeployEvents?.[result.endpoint.codebase || "default"] !== undefined) { + context.codebaseDeployEvents[result.endpoint.codebase || "default"] + .fn_deploy_num_canceled++; + } } else { totalErrors++; - reports.push(track("function_deploy_failure", tag, result.durationMs)); + if (context?.codebaseDeployEvents?.[result.endpoint.codebase || "default"] !== undefined) { + context.codebaseDeployEvents[result.endpoint.codebase || "default"] + .fn_deploy_num_failures++; + } } } - const regionCountTag = regions.size < 5 ? regions.size.toString() : ">=5"; - reports.push(track("functions_region_count", regionCountTag, 1)); - - const gcfv1 = summary.results.find((r) => r.endpoint.platform === "gcfv1"); - const gcfv2 = summary.results.find((r) => r.endpoint.platform === "gcfv2"); - const tag = gcfv1 && gcfv2 ? "v1+v2" : gcfv1 ? "v1" : "v2"; - reports.push(track("functions_codebase_deploy", tag, summary.results.length)); + for (const codebase of codebases) { + if (context?.codebaseDeployEvents) { + reports.push(trackGA4("codebase_deploy", { ...context.codebaseDeployEvents[codebase] })); + } + } + const fnDeployGroupEvent = { + codebase_deploy_count: codebases.size >= 5 ? "5+" : codebases.size.toString(), + fn_deploy_num_successes: totalSuccesses, + fn_deploy_num_canceled: totalAborts, + fn_deploy_num_failures: totalErrors, + }; + reports.push(trackGA4("function_deploy_group", fnDeployGroupEvent)); const avgTime = totalTime / (totalSuccesses + totalErrors); - logger.debug(`Total Function Deployment time: ${summary.totalTime}`); logger.debug(`${totalErrors + totalSuccesses + totalAborts} Functions Deployed`); logger.debug(`${totalErrors} Functions Errored`); logger.debug(`${totalAborts} Function Deployments Aborted`); logger.debug(`Average Function Deployment time: ${avgTime}`); - if (totalErrors + totalSuccesses > 0) { - if (totalErrors === 0) { - reports.push(track("functions_deploy_result", "success", totalSuccesses)); - } else if (totalSuccesses > 0) { - reports.push(track("functions_deploy_result", "partial_success", totalSuccesses)); - reports.push(track("functions_deploy_result", "partial_failure", totalErrors)); - reports.push( - track( - "functions_deploy_result", - "partial_error_ratio", - totalErrors / (totalSuccesses + totalErrors) - ) - ); - } else { - reports.push(track("functions_deploy_result", "failure", totalErrors)); - } - } await utils.allSettled(reports); } diff --git a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts index 4ce21970fa8..58fd94e9a2c 100644 --- a/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts +++ b/src/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.ts @@ -2,7 +2,6 @@ import * as path from "path"; import * as clc from "colorette"; import { FirebaseError } from "../../../../error"; -import { track } from "../../../../track"; import * as runtimes from "../../runtimes"; // have to require this because no @types/cjson available @@ -80,7 +79,6 @@ export function getRuntimeChoice(sourceDir: string, runtimeFromConfig?: string): : UNSUPPORTED_NODE_VERSION_PACKAGE_JSON_MSG) + DEPRECATED_NODE_VERSION_INFO; if (!runtime || !ENGINE_RUNTIMES_NAMES.includes(runtime)) { - void track("functions_runtime_notices", "package_missing_runtime"); throw new FirebaseError(errorMessage, { exit: 1 }); } @@ -88,7 +86,6 @@ export function getRuntimeChoice(sourceDir: string, runtimeFromConfig?: string): // it's in ENGINE_RUNTIME_NAMES and not in DEPRECATED_RUNTIMES. This is still a // good defense in depth and also lets us upcast the response to Runtime safely. if (runtimes.isDeprecatedRuntime(runtime) || !runtimes.isValidRuntime(runtime)) { - void track("functions_runtime_notices", `${runtime}_deploy_prohibited`); throw new FirebaseError(errorMessage, { exit: 1 }); } diff --git a/src/deploy/functions/runtimes/node/versioning.ts b/src/deploy/functions/runtimes/node/versioning.ts index d0c400c9bbb..4945309c11f 100644 --- a/src/deploy/functions/runtimes/node/versioning.ts +++ b/src/deploy/functions/runtimes/node/versioning.ts @@ -6,7 +6,6 @@ import * as spawn from "cross-spawn"; import * as semver from "semver"; import { logger } from "../../../../logger"; -import { track } from "../../../../track"; import * as utils from "../../../../utils"; interface NpmShowResult { @@ -113,7 +112,6 @@ export function getLatestSDKVersion(): string | undefined { export function checkFunctionsSDKVersion(currentVersion: string): void { try { if (semver.lt(currentVersion, MIN_SDK_VERSION)) { - void track("functions_runtime_notices", "functions_sdk_too_old"); utils.logWarning(FUNCTIONS_SDK_VERSION_TOO_OLD_WARNING); } diff --git a/src/test/deploy/functions/release/reporter.spec.ts b/src/test/deploy/functions/release/reporter.spec.ts index ae2de5cd3f2..b18fee9f728 100644 --- a/src/test/deploy/functions/release/reporter.spec.ts +++ b/src/test/deploy/functions/release/reporter.spec.ts @@ -6,6 +6,7 @@ import * as backend from "../../../../deploy/functions/backend"; import * as reporter from "../../../../deploy/functions/release/reporter"; import * as track from "../../../../track"; import * as events from "../../../../functions/events"; +import * as args from "../../../../deploy/functions/args"; const ENDPOINT_BASE: Omit = { platform: "gcfv1", @@ -117,11 +118,11 @@ describe("reporter", () => { }); describe("logAndTrackDeployStats", () => { - let trackStub: sinon.SinonStub; + let trackGA4Stub: sinon.SinonStub; let debugStub: sinon.SinonStub; beforeEach(() => { - trackStub = sinon.stub(track, "track"); + trackGA4Stub = sinon.stub(track, "trackGA4"); debugStub = sinon.stub(logger, "debug"); }); @@ -134,105 +135,97 @@ describe("reporter", () => { totalTime: 2_000, results: [ { - endpoint: ENDPOINT, + endpoint: { ...ENDPOINT, codebase: "codebase0" }, durationMs: 2_000, }, { - endpoint: ENDPOINT, + endpoint: { ...ENDPOINT, codebase: "codebase1" }, durationMs: 1_000, - error: new reporter.DeploymentError(ENDPOINT, "update", undefined), + error: new reporter.DeploymentError( + { ...ENDPOINT, codebase: "codebase1" }, + "update", + undefined + ), }, { - endpoint: ENDPOINT, + endpoint: { ...ENDPOINT, codebase: "codebase1" }, durationMs: 0, - error: new reporter.AbortedDeploymentError(ENDPOINT), + error: new reporter.AbortedDeploymentError({ ...ENDPOINT, codebase: "codebase1" }), }, ], }; - await reporter.logAndTrackDeployStats(summary); - - expect(trackStub).to.have.been.calledWith("functions_region_count", "1", 1); - expect(trackStub).to.have.been.calledWith("function_deploy_success", "v1.https", 2_000); - expect(trackStub).to.have.been.calledWith("function_deploy_failure", "v1.https", 1_000); - // Aborts aren't tracked because they would throw off timing metrics - expect(trackStub).to.not.have.been.calledWith("function_deploy_failure", "v1.https", 0); - - expect(debugStub).to.have.been.calledWith("Total Function Deployment time: 2000"); - expect(debugStub).to.have.been.calledWith("3 Functions Deployed"); - expect(debugStub).to.have.been.calledWith("1 Functions Errored"); - expect(debugStub).to.have.been.calledWith("1 Function Deployments Aborted"); - - // The 0ms for an aborted function isn't counted. - expect(debugStub).to.have.been.calledWith("Average Function Deployment time: 1500"); - }); - - it("tracks v1 vs v2 codebases", async () => { - const v1 = { ...ENDPOINT }; - const v2: backend.Endpoint = { ...ENDPOINT, platform: "gcfv2" }; - - const summary: reporter.Summary = { - totalTime: 1_000, - results: [ - { - endpoint: v1, - durationMs: 1_000, + const context: args.Context = { + projectId: "id", + codebaseDeployEvents: { + codebase0: { + params: "none", + fn_deploy_num_successes: 0, + fn_deploy_num_canceled: 0, + fn_deploy_num_failures: 0, + fn_deploy_num_skipped: 0, }, - { - endpoint: v2, - durationMs: 1_000, + codebase1: { + params: "none", + fn_deploy_num_successes: 0, + fn_deploy_num_canceled: 0, + fn_deploy_num_failures: 0, + fn_deploy_num_skipped: 0, }, - ], - }; - - await reporter.logAndTrackDeployStats(summary); - expect(trackStub).to.have.been.calledWith("functions_codebase_deploy", "v1+v2", 2); - trackStub.resetHistory(); - - summary.results = [{ endpoint: v1, durationMs: 1_000 }]; - await reporter.logAndTrackDeployStats(summary); - expect(trackStub).to.have.been.calledWith("functions_codebase_deploy", "v1", 1); - trackStub.resetHistory(); - - summary.results = [{ endpoint: v2, durationMs: 1_000 }]; - await reporter.logAndTrackDeployStats(summary); - expect(trackStub).to.have.been.calledWith("functions_codebase_deploy", "v2", 1); - }); - - it("tracks overall success/failure", async () => { - const success: reporter.DeployResult = { - endpoint: ENDPOINT, - durationMs: 1_000, - }; - const failure: reporter.DeployResult = { - endpoint: ENDPOINT, - durationMs: 1_000, - error: new reporter.DeploymentError(ENDPOINT, "create", undefined), + }, }; - const summary: reporter.Summary = { - totalTime: 1_000, - results: [success, failure], - }; + await reporter.logAndTrackDeployStats(summary, context); + + expect(trackGA4Stub).to.have.been.calledWith("function_deploy", { + platform: "gcfv1", + trigger_type: "https", + region: "region", + runtime: "nodejs16", + status: "success", + duration: 2_000, + }); + expect(trackGA4Stub).to.have.been.calledWith("function_deploy", { + platform: "gcfv1", + trigger_type: "https", + region: "region", + runtime: "nodejs16", + status: "failure", + duration: 1_000, + }); + expect(trackGA4Stub).to.have.been.calledWith("function_deploy", { + platform: "gcfv1", + trigger_type: "https", + region: "region", + runtime: "nodejs16", + status: "aborted", + duration: 0, + }); + + expect(trackGA4Stub).to.have.been.calledWith("codebase_deploy", { + params: "none", + fn_deploy_num_successes: 1, + fn_deploy_num_canceled: 0, + fn_deploy_num_failures: 0, + fn_deploy_num_skipped: 0, + }); + expect(trackGA4Stub).to.have.been.calledWith("codebase_deploy", { + params: "none", + fn_deploy_num_successes: 0, + fn_deploy_num_canceled: 1, + fn_deploy_num_failures: 1, + fn_deploy_num_skipped: 0, + }); + + expect(trackGA4Stub).to.have.been.calledWith("function_deploy_group", { + codebase_deploy_count: "2", + fn_deploy_num_successes: 1, + fn_deploy_num_canceled: 1, + fn_deploy_num_failures: 1, + }); - await reporter.logAndTrackDeployStats(summary); - expect(trackStub).to.have.been.calledWith("functions_deploy_result", "partial_success", 1); - expect(trackStub).to.have.been.calledWith("functions_deploy_result", "partial_failure", 1); - expect(trackStub).to.have.been.calledWith( - "functions_deploy_result", - "partial_error_ratio", - 0.5 - ); - trackStub.resetHistory(); - - summary.results = [success]; - await reporter.logAndTrackDeployStats(summary); - expect(trackStub).to.have.been.calledWith("functions_deploy_result", "success", 1); - trackStub.resetHistory(); - - summary.results = [failure]; - await reporter.logAndTrackDeployStats(summary); - expect(trackStub).to.have.been.calledWith("functions_deploy_result", "failure", 1); + // The 0ms for an aborted function isn't counted. + expect(debugStub).to.have.been.calledWith("Average Function Deployment time: 1500"); }); }); diff --git a/src/track.ts b/src/track.ts index 69a0c92f4bd..3f523752d5e 100644 --- a/src/track.ts +++ b/src/track.ts @@ -16,7 +16,10 @@ type cliEventNames = | "hosting_version" | "extension_added_to_manifest" | "extensions_deploy" - | "extensions_emulated"; + | "extensions_emulated" + | "function_deploy" + | "codebase_deploy" + | "function_deploy_group"; type GA4Property = "cli" | "emulator"; interface GA4Info { measurementId: string; From 4db12d24cedca401a18beb60113c6168397fccc3 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 28 Jun 2023 12:36:46 -0700 Subject: [PATCH 1058/1699] Fix incorrect warnings when emulating extensions with httpsTriggers (#6055) --- CHANGELOG.md | 1 + src/extensions/emulator/triggerHelper.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5484a42cfb7..9bf9b02b194 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Increased extension instance create poll timeout to 1h to match backend (#5969). - Refactored `ext:install` to use the latest extension metadata. (#5997) +- Fixed issue where missing trigger warnings would be wrongly displayed when emulating extensions with HTTPS triggers. (#6055) - Normalized extension root path before usage in `ext:dev:upload`. (#6054) diff --git a/src/extensions/emulator/triggerHelper.ts b/src/extensions/emulator/triggerHelper.ts index 12d2a150d47..ef2f197d1d8 100644 --- a/src/extensions/emulator/triggerHelper.ts +++ b/src/extensions/emulator/triggerHelper.ts @@ -77,7 +77,8 @@ export function functionResourceToEmulatedTriggerDefintion( proto.convertIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration); proto.convertIfPresent(etd, properties, "regions", "location", (str: string) => [str]); proto.copyIfPresent(etd, properties, "availableMemoryMb"); - if (properties.httpsTrigger) { + if (properties.httpsTrigger !== undefined) { + // Need to explcitly check undefined since {} is falsey etd.httpsTrigger = properties.httpsTrigger; } if (properties.eventTrigger) { From 893971ffc3bf25dcd7dd8dd022b6163012b90229 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 28 Jun 2023 13:22:31 -0700 Subject: [PATCH 1059/1699] Only record metrics if user confirms ext:install (#6047) * Only record metrics if user confirms ext:install * format --- src/commands/ext-install.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/commands/ext-install.ts b/src/commands/ext-install.ts index 4e722975a17..1b032cd28c7 100644 --- a/src/commands/ext-install.ts +++ b/src/commands/ext-install.ts @@ -73,20 +73,12 @@ export const command = new Command("ext:install [extensionRef]") // Should parse spec locally so we don't need project ID. source = await createSourceFromLocation(needProjectId({ projectId }), extensionRef); await displayExtensionVersionInfo({ spec: source.spec }); - void trackGA4("extension_added_to_manifest", { - published: "local", - interactive: options.nonInteractive ? "false" : "true", - }); } else { const extension = await extensionsApi.getExtension(extensionRef); const ref = refs.parse(extensionRef); ref.version = await resolveVersion(ref, extension); const extensionVersionRef = refs.toExtensionVersionRef(ref); extensionVersion = await extensionsApi.getExtensionVersion(extensionVersionRef); - void trackGA4("extension_added_to_manifest", { - published: extensionVersion.listing?.state === "APPROVED" ? "published" : "uploaded", - interactive: options.nonInteractive ? "false" : "true", - }); await displayExtensionVersionInfo({ spec: extensionVersion.spec, extensionVersion, @@ -143,6 +135,19 @@ export const command = new Command("ext:install [extensionRef]") )}'. Please make sure this is a valid extension and try again.` ); } + + if (source) { + void trackGA4("extension_added_to_manifest", { + published: "local", + interactive: options.nonInteractive ? "false" : "true", + }); + } else if (extensionVersion) { + void trackGA4("extension_added_to_manifest", { + published: extensionVersion.listing?.state === "APPROVED" ? "published" : "uploaded", + interactive: options.nonInteractive ? "false" : "true", + }); + } + try { return installToManifest({ projectId, From e1f0d8db6e01520d4c2c3b54b8ebd131ca82ff25 Mon Sep 17 00:00:00 2001 From: Alex Pascal Date: Wed, 28 Jun 2023 13:35:34 -0700 Subject: [PATCH 1060/1699] Added descriptive error when repo is private or not found during ext:dev:upload. (#6052) * Added more descriptive error message when repo is private (or not found). * Formatting. * Formatting. * Update CHANGELOG.md --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + src/extensions/extensionsHelper.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf9b02b194..026e7b69d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ - Increased extension instance create poll timeout to 1h to match backend (#5969). - Refactored `ext:install` to use the latest extension metadata. (#5997) +- Added descriptive error when repo is private or not found during `ext:dev:upload`. (#6052) - Fixed issue where missing trigger warnings would be wrongly displayed when emulating extensions with HTTPS triggers. (#6055) - Normalized extension root path before usage in `ext:dev:upload`. (#6054) diff --git a/src/extensions/extensionsHelper.ts b/src/extensions/extensionsHelper.ts index a5025142fe7..46b363e0a56 100644 --- a/src/extensions/extensionsHelper.ts +++ b/src/extensions/extensionsHelper.ts @@ -729,17 +729,21 @@ async function fetchExtensionSource( logger.info(`Validating source code at ${clc.bold(sourceUri)}...`); const archiveUri = `${repoUri}/archive/${sourceRef}.zip`; const tempDirectory = tmp.dirSync({ unsafeCleanup: true }); + const archiveErrorMessage = `Failed to extract archive from ${clc.bold( + archiveUri + )}. Please check that the repo is public and that the source ref is valid.`; try { const response = await fetch(archiveUri); if (response.ok) { await response.body.pipe(createUnzipTransform(tempDirectory.name)).promise(); } } catch (err: any) { - throw new FirebaseError( - `Failed to fetch extension archive from ${archiveUri}. Please check the repo URI and source ref. ${err}` - ); + throw new FirebaseError(archiveErrorMessage); } const archiveName = fs.readdirSync(tempDirectory.name)[0]; + if (!archiveName) { + throw new FirebaseError(archiveErrorMessage); + } const rootDirectory = path.join(tempDirectory.name, archiveName, extensionRoot); // Pre-validation to show a more useful error message in the context of a temp directory. try { From 0bcee86d985be40abb39e47ee869a1b299c7d499 Mon Sep 17 00:00:00 2001 From: Dominic Bartl Date: Wed, 28 Jun 2023 22:44:18 +0200 Subject: [PATCH 1061/1699] Allow $schema property in firebase.json (#6051) * Allow $schema property in firebase.json * Add $schema property via firebaseConfig.ts --------- Co-authored-by: joehan --- schema/firebase-config.json | 4 ++++ src/firebaseConfig.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/schema/firebase-config.json b/schema/firebase-config.json index ca1f66754b1..b4bb9139591 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -128,6 +128,10 @@ } }, "properties": { + "$schema": { + "format": "uri", + "type": "string" + }, "database": { "anyOf": [ { diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index f1395ba13c7..9d76951f59c 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -234,6 +234,10 @@ export type EmulatorsConfig = { export type ExtensionsConfig = Record; export type FirebaseConfig = { + /** + * @TJS-format uri + */ + $schema?: string; database?: DatabaseConfig; firestore?: FirestoreConfig; functions?: FunctionsConfig; From 55e894967130f26fa0c4291923ae5684481a33a8 Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:57:24 -0400 Subject: [PATCH 1062/1699] Run lifecycle hooks for specific functions (#6023) * run lifecycle hooks for individual functions --- CHANGELOG.md | 1 + src/deploy/lifecycleHooks.ts | 40 ++++++++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 026e7b69d48..17e42f7b39d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Run lifecycle hooks for specific functions. (#6023) - Increased extension instance create poll timeout to 1h to match backend (#5969). - Refactored `ext:install` to use the latest extension metadata. (#5997) - Added descriptive error when repo is private or not found during `ext:dev:upload`. (#6052) diff --git a/src/deploy/lifecycleHooks.ts b/src/deploy/lifecycleHooks.ts index 08851844a25..008fa584657 100644 --- a/src/deploy/lifecycleHooks.ts +++ b/src/deploy/lifecycleHooks.ts @@ -132,18 +132,46 @@ function getReleventConfigs(target: string, options: Options) { onlyTargets = onlyTargets .filter((individualOnly) => { - return individualOnly.indexOf(`${target}:`) === 0; + return individualOnly.startsWith(`${target}:`); }) .map((individualOnly) => { return individualOnly.replace(`${target}:`, ""); }); - return targetConfigs.filter((config: any) => { - if (target === "functions") { - return onlyTargets.includes(config.codebase); + if (target === "functions") { + let onlyConfigs = []; + const matched = onlyTargets.reduce( + (matched: object, target: string) => ({ ...matched, [target]: false }), + {} + ); + for (const config of targetConfigs) { + if (!config.codebase) { + onlyConfigs.push(config); + } else { + const found = onlyTargets.find( + (individualOnly) => config.codebase === individualOnly.split(":")[0] + ); + if (found) { + onlyConfigs.push(config); + matched[found] = true; + } + } } - return !config.target || onlyTargets.includes(config.target); - }); + // if there are --only targets that failed to match, we assume that the target is a + // individually specified function and so we run lifecycle hooks for all codebases. + // However, this also means that codebases or functions that don't exist will also run + // the all codebase lifecycle hooks. Until we can significantly refactor the way we + // identify which functions are in which codebase in the predeploy phase, we have to live + // with this default behavior. + if (!Object.values(matched).every((matched) => matched)) { + onlyConfigs = targetConfigs; + } + return onlyConfigs; + } else { + return targetConfigs.filter((config: any) => { + return !config.target || onlyTargets.includes(config.target); + }); + } } export function lifecycleHooks( From 998a5330c9605e3248696b8398a651c44cc5fca1 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 28 Jun 2023 21:22:27 +0000 Subject: [PATCH 1063/1699] 12.4.2 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 6a9fdbca629..1e9c7e9c395 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "12.4.1", + "version": "12.4.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "12.4.1", + "version": "12.4.2", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index c9334a89044..39244402cda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "12.4.1", + "version": "12.4.2", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From c131e4fd6ba44a4b638295c3720b2bcd055d6948 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 28 Jun 2023 21:22:40 +0000 Subject: [PATCH 1064/1699] [firebase-release] Removed change log and reset repo after 12.4.2 release --- CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e42f7b39d..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +0,0 @@ -- Run lifecycle hooks for specific functions. (#6023) -- Increased extension instance create poll timeout to 1h to match backend (#5969). -- Refactored `ext:install` to use the latest extension metadata. (#5997) -- Added descriptive error when repo is private or not found during `ext:dev:upload`. (#6052) -- Fixed issue where missing trigger warnings would be wrongly displayed when emulating extensions with HTTPS triggers. (#6055) -- Normalized extension root path before usage in `ext:dev:upload`. (#6054) From c0a59a59ca9230a98713553a1c0335d6957be154 Mon Sep 17 00:00:00 2001 From: Sairam Sakhamuri Date: Wed, 28 Jun 2023 15:52:50 -0700 Subject: [PATCH 1065/1699] Integrate discovery with composer (#6042) * Added runtime command discovery * Resolved comments * Added error case to analyse codebase method * Updated install command * Reorganized tests and removed unwated promise.resolve stmt * Added review changes on install command and node version string array * Changes to node.ts to include additional condions on run script * Added code comments to types * Integrate discovery with composer * Minor modification * Minor changes * Minor changes * Resolved code commits * Added undefied to return if no cmd * Added undefied to return if no cmd * Added frameworkhook interface * code comments * format error msg * format error msg * format error msg * format error msg * Code comments * Fixed imports and compose command * Fix bugs. * Update base image for node runtime. * bug fix * bug fix * Remove hooks --------- Co-authored-by: Daniel Young Lee --- .../internaltesting-frameworks-compose.ts | 6 +- src/frameworks/compose/discover/filesystem.ts | 2 +- src/frameworks/compose/discover/index.ts | 59 ++++++++++++------- .../compose/discover/runtime/node.ts | 7 +-- src/frameworks/compose/discover/types.ts | 17 ++++++ src/frameworks/compose/driver/docker.ts | 33 ++++++----- src/frameworks/compose/driver/index.ts | 5 +- src/frameworks/compose/driver/local.ts | 17 ++++-- src/frameworks/compose/index.ts | 21 ++++--- src/frameworks/compose/interfaces.ts | 16 +---- .../compose/discover/runtime/node.spec.ts | 4 +- 11 files changed, 113 insertions(+), 74 deletions(-) diff --git a/src/commands/internaltesting-frameworks-compose.ts b/src/commands/internaltesting-frameworks-compose.ts index ee85ec27d8a..a2731ef2a7e 100644 --- a/src/commands/internaltesting-frameworks-compose.ts +++ b/src/commands/internaltesting-frameworks-compose.ts @@ -4,18 +4,20 @@ import { logger } from "../logger"; import { Mode, SUPPORTED_MODES } from "../frameworks/compose/driver"; import { compose } from "../frameworks/compose"; import { FirebaseError } from "../error"; +import { LocalFileSystem } from "../frameworks/compose/discover/filesystem"; +import { frameworkSpecs } from "../frameworks/compose/discover/frameworkSpec"; export const command = new Command("internaltesting:frameworks:compose") .option("-m, --mode ", "Composer mode (local or docker)", "local") .description("compose framework in current directory") - .action((options: Options) => { + .action(async (options: Options) => { const mode = options.mode as string; if (!(SUPPORTED_MODES as unknown as string[]).includes(mode)) { throw new FirebaseError( `Unsupported mode ${mode}. Supported modes are [${SUPPORTED_MODES.join(", ")}]` ); } - const bundle = compose(mode as Mode); + const bundle = await compose(mode as Mode, new LocalFileSystem("."), frameworkSpecs); logger.info(JSON.stringify(bundle, null, 2)); return {}; }); diff --git a/src/frameworks/compose/discover/filesystem.ts b/src/frameworks/compose/discover/filesystem.ts index b2c7b7f9b47..f1e7aa513d0 100644 --- a/src/frameworks/compose/discover/filesystem.ts +++ b/src/frameworks/compose/discover/filesystem.ts @@ -2,7 +2,7 @@ import { FileSystem } from "./types"; import { pathExists, readFile } from "fs-extra"; import * as path from "path"; import { FirebaseError } from "../../../error"; -import { logger } from "../../../../src/logger"; +import { logger } from "../../../logger"; /** * Find files or read file contents present in the directory. diff --git a/src/frameworks/compose/discover/index.ts b/src/frameworks/compose/discover/index.ts index 8d3c8542ab3..43a7ba72e80 100644 --- a/src/frameworks/compose/discover/index.ts +++ b/src/frameworks/compose/discover/index.ts @@ -1,28 +1,43 @@ -import { AppSpec } from "../interfaces"; +import { Runtime, FileSystem, FrameworkSpec, RuntimeSpec } from "./types"; +import { NodejsRuntime } from "./runtime/node"; +import { FirebaseError } from "../../../error"; + +const supportedRuntimes: Runtime[] = [new NodejsRuntime()]; /** - * Discover framework in the given project directory + * Discover the best matching runtime specs for the application. */ -export function discover(): AppSpec { - return { - baseImage: "us-docker.pkg.dev/firestack-build/test/run:latest", - environmentVariables: { - NODE_ENV: "PRODUCTION", - }, - installCommand: "npm install", - buildCommand: "npm run build", - startCommand: "npm run start", +export async function discover( + fs: FileSystem, + allFrameworkSpecs: FrameworkSpec[] +): Promise { + try { + let discoveredRuntime = undefined; + for (const runtime of supportedRuntimes) { + if (await runtime.match(fs)) { + if (!discoveredRuntime) { + discoveredRuntime = runtime; + } else { + throw new FirebaseError( + `Conflit occurred as multiple runtimes ${discoveredRuntime.getRuntimeName()}, ${runtime.getRuntimeName()} are discovered in the application.` + ); + } + } + } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - afterInstall: (b) => { - console.log("HOOK: AFTER INSTALL"); - return { ...b, version: "v1alpha", notes: "afterInstall" }; - }, + if (!discoveredRuntime) { + throw new FirebaseError( + `Unable to determine the specific runtime for the application. The supported runtime options include ${supportedRuntimes + .map((x) => x.getRuntimeName()) + .join(" , ")}.` + ); + } + const runtimeSpec = await discoveredRuntime.analyseCodebase(fs, allFrameworkSpecs); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - afterBuild(b) { - console.log("HOOK: AFTER BUILD"); - return { ...b, version: "v1alpha", notes: "afterBuild" }; - }, - }; + return runtimeSpec; + } catch (error: any) { + throw new FirebaseError( + `Failed to identify required specifications to execute the application: ${error}` + ); + } } diff --git a/src/frameworks/compose/discover/runtime/node.ts b/src/frameworks/compose/discover/runtime/node.ts index 976657deac9..58a6e94fdc5 100644 --- a/src/frameworks/compose/discover/runtime/node.ts +++ b/src/frameworks/compose/discover/runtime/node.ts @@ -5,7 +5,7 @@ import { frameworkMatcher } from "../frameworkMatcher"; import { LifecycleCommands } from "../types"; import { Command } from "../types"; import { FirebaseError } from "../../../../error"; -import { logger } from "../../../../../src/logger"; +import { logger } from "../../../../logger"; import { conjoinOptions } from "../../../utils"; export interface PackageJSON { @@ -23,7 +23,6 @@ const YARN_LOCK = "yarn.lock"; export class NodejsRuntime implements Runtime { private readonly runtimeRequiredFiles: string[] = [PACKAGE_JSON]; - private readonly contentCache: Record = {}; // Checks if the codebase is using Node as runtime. async match(fs: FileSystem): Promise { @@ -41,7 +40,7 @@ export class NodejsRuntime implements Runtime { getNodeImage(engine: Record | undefined): string { // If no version is mentioned explicitly, assuming application is compatible with latest version. if (!engine || !engine.node) { - return `node:${supportedNodeVersions[supportedNodeVersions.length - 1]}-slim`; + return "us-docker.pkg.dev/firestack-build/test/run"; } const versionNumber = engine.node; @@ -54,7 +53,7 @@ export class NodejsRuntime implements Runtime { ); } - return `node:${versionNumber}-slim`; + return "us-docker.pkg.dev/firestack-build/test/run"; } async getPackageManager(fs: FileSystem): Promise { diff --git a/src/frameworks/compose/discover/types.ts b/src/frameworks/compose/discover/types.ts index b919e552c4b..a89fb00c312 100644 --- a/src/frameworks/compose/discover/types.ts +++ b/src/frameworks/compose/discover/types.ts @@ -1,3 +1,5 @@ +import { AppBundle } from "../interfaces"; + export interface FileSystem { exists(file: string): Promise; read(file: string): Promise; @@ -77,4 +79,19 @@ export interface RuntimeSpec { // The runtime has detected a command that should always be run irrespective of // the framework (e.g. the "build" script always wins in Node) detectedCommands?: LifecycleCommands; + + environmentVariables?: Record; + + // Framework authors can execute framework-specific code using hooks at different stages of Frameworks API build process. + frameworkHooks?: FrameworkHooks; +} + +export interface FrameworkHooks { + // Programmatic hook with access to filesystem and nodejs API to inspect the workspace. + // Primarily intended to gather hints relevant to the build. + afterInstall?: (b: AppBundle) => AppBundle; + + // Programmatic hook with access to filesystem and nodejs API to inspect the build artifacts. + // Primarily intended to informs what assets should be deployed. + afterBuild?: (b: AppBundle) => AppBundle; } diff --git a/src/frameworks/compose/driver/docker.ts b/src/frameworks/compose/driver/docker.ts index 70d9d4344b9..8033d7c16cd 100644 --- a/src/frameworks/compose/driver/docker.ts +++ b/src/frameworks/compose/driver/docker.ts @@ -2,8 +2,9 @@ import * as fs from "node:fs"; import * as path from "node:path"; import * as spawn from "cross-spawn"; -import { AppBundle, AppSpec, Driver, Hook } from "../interfaces"; +import { AppBundle, Driver, Hook } from "../interfaces"; import { BUNDLE_PATH, genHookScript } from "./hooks"; +import { RuntimeSpec } from "../discover/types"; const ADAPTER_SCRIPTS_PATH = "./.firebase/adapters" as const; @@ -98,7 +99,7 @@ export class DockerfileBuilder { export class DockerDriver implements Driver { private dockerfileBuilder; - constructor(readonly spec: AppSpec) { + constructor(readonly spec: RuntimeSpec) { this.dockerfileBuilder = new DockerfileBuilder(); this.dockerfileBuilder.from(spec.baseImage, "base").user("firebase"); } @@ -148,21 +149,25 @@ export class DockerDriver implements Driver { } install(): void { - this.dockerfileBuilder - .fromLastStage(DOCKER_STAGE_INSTALL) - .workdir("/home/firebase/app") - .envs(this.spec.environmentVariables || {}) - .copyForFirebase("package.json", ".") - .run(this.spec.installCommand); - this.buildStage(DOCKER_STAGE_INSTALL, "."); + if (this.spec.installCommand) { + this.dockerfileBuilder + .fromLastStage(DOCKER_STAGE_INSTALL) + .workdir("/home/firebase/app") + .envs(this.spec.environmentVariables || {}) + .copyForFirebase("package.json", ".") + .run(this.spec.installCommand); + this.buildStage(DOCKER_STAGE_INSTALL, "."); + } } build(): void { - this.dockerfileBuilder - .fromLastStage(DOCKER_STAGE_BUILD) - .copyForFirebase(".", ".") - .run(this.spec.buildCommand); - this.buildStage(DOCKER_STAGE_BUILD, "."); + if (this.spec.detectedCommands?.build) { + this.dockerfileBuilder + .fromLastStage(DOCKER_STAGE_BUILD) + .copyForFirebase(".", ".") + .run(this.spec.detectedCommands.build.cmd); + this.buildStage(DOCKER_STAGE_BUILD, "."); + } } export(bundle: AppBundle): void { diff --git a/src/frameworks/compose/driver/index.ts b/src/frameworks/compose/driver/index.ts index 8adc6480708..1779fdb5c3e 100644 --- a/src/frameworks/compose/driver/index.ts +++ b/src/frameworks/compose/driver/index.ts @@ -1,6 +1,7 @@ -import { AppSpec, Driver } from "../interfaces"; +import { Driver } from "../interfaces"; import { LocalDriver } from "./local"; import { DockerDriver } from "./docker"; +import { RuntimeSpec } from "../discover/types"; export const SUPPORTED_MODES = ["local", "docker"] as const; export type Mode = (typeof SUPPORTED_MODES)[number]; @@ -8,7 +9,7 @@ export type Mode = (typeof SUPPORTED_MODES)[number]; /** * Returns the driver that provides the execution context for the composer. */ -export function getDriver(mode: Mode, app: AppSpec): Driver { +export function getDriver(mode: Mode, app: RuntimeSpec): Driver { if (mode === "local") { return new LocalDriver(app); } else if (mode === "docker") { diff --git a/src/frameworks/compose/driver/local.ts b/src/frameworks/compose/driver/local.ts index da23187bf58..5bd219c113c 100644 --- a/src/frameworks/compose/driver/local.ts +++ b/src/frameworks/compose/driver/local.ts @@ -1,11 +1,12 @@ import * as fs from "node:fs"; import * as spawn from "cross-spawn"; -import { AppBundle, AppSpec, Hook, Driver } from "../interfaces"; +import { AppBundle, Hook, Driver } from "../interfaces"; import { BUNDLE_PATH, genHookScript } from "./hooks"; +import { RuntimeSpec } from "../discover/types"; export class LocalDriver implements Driver { - constructor(readonly spec: AppSpec) {} + constructor(readonly spec: RuntimeSpec) {} private execCmd(cmd: string, args: string[]) { const ret = spawn.sync(cmd, args, { @@ -18,13 +19,17 @@ export class LocalDriver implements Driver { } install(): void { - const [cmd, ...args] = this.spec.installCommand.split(" "); - this.execCmd(cmd, args); + if (this.spec.installCommand) { + const [cmd, ...args] = this.spec.installCommand.split(" "); + this.execCmd(cmd, args); + } } build(): void { - const [cmd, ...args] = this.spec.buildCommand.split(" "); - this.execCmd(cmd, args); + if (this.spec.detectedCommands?.build) { + const [cmd, ...args] = this.spec.detectedCommands.build.cmd.split(" "); + this.execCmd(cmd, args); + } } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/frameworks/compose/index.ts b/src/frameworks/compose/index.ts index 24e45490be1..4a5d066ca8c 100644 --- a/src/frameworks/compose/index.ts +++ b/src/frameworks/compose/index.ts @@ -1,31 +1,36 @@ import { AppBundle } from "./interfaces"; import { getDriver, Mode } from "./driver"; import { discover } from "./discover"; +import { FrameworkSpec, FileSystem } from "./discover/types"; /** * Run composer in the specified execution context. */ -export function compose(mode: Mode): AppBundle { +export async function compose( + mode: Mode, + fs: FileSystem, + allFrameworkSpecs: FrameworkSpec[] +): Promise { let bundle: AppBundle = { version: "v1alpha" }; - const spec = discover(); + const spec = await discover(fs, allFrameworkSpecs); const driver = getDriver(mode, spec); - if (spec.startCommand) { + if (spec.detectedCommands?.run) { bundle.server = { start: { - cmd: spec.startCommand.split(" "), + cmd: spec.detectedCommands.run.cmd.split(" "), }, }; } driver.install(); - if (spec.afterInstall) { - bundle = driver.execHook(bundle, spec.afterInstall); + if (spec.frameworkHooks?.afterInstall) { + bundle = driver.execHook(bundle, spec.frameworkHooks.afterInstall); } driver.build(); - if (spec.afterBuild) { - bundle = driver.execHook(bundle, spec.afterBuild); + if (spec.frameworkHooks?.afterBuild) { + bundle = driver.execHook(bundle, spec.frameworkHooks?.afterBuild); } if (bundle.server) { diff --git a/src/frameworks/compose/interfaces.ts b/src/frameworks/compose/interfaces.ts index db3caee4898..068b28a178b 100644 --- a/src/frameworks/compose/interfaces.ts +++ b/src/frameworks/compose/interfaces.ts @@ -1,3 +1,5 @@ +import { RuntimeSpec } from "./discover/types"; + export interface AppBundle { version: "v1alpha"; server?: ServerConfig; @@ -22,22 +24,10 @@ interface StartConfig { runtime?: "nodejs18" | string; } -export interface AppSpec { - baseImage: string; - packageManagerInstallCommand?: string; - environmentVariables?: Record; - installCommand: string; - buildCommand: string; - startCommand: string; - - afterInstall?: (b: AppBundle) => AppBundle; - afterBuild?: (b: AppBundle) => AppBundle; -} - export type Hook = (b: AppBundle) => AppBundle; export class Driver { - constructor(readonly spec: AppSpec) {} + constructor(readonly spec: RuntimeSpec) {} install(): void { throw new Error("install() not implemented"); diff --git a/src/test/frameworks/compose/discover/runtime/node.spec.ts b/src/test/frameworks/compose/discover/runtime/node.spec.ts index b2bbaee767c..4dedc70db6a 100644 --- a/src/test/frameworks/compose/discover/runtime/node.spec.ts +++ b/src/test/frameworks/compose/discover/runtime/node.spec.ts @@ -41,7 +41,7 @@ describe("NodejsRuntime", () => { node: "18", }; const actualImage = nodeJSRuntime.getNodeImage(version); - const expectedImage = "node:18-slim"; + const expectedImage = "us-docker.pkg.dev/firestack-build/test/run"; expect(actualImage).to.deep.equal(expectedImage); }); @@ -189,7 +189,7 @@ describe("NodejsRuntime", () => { const actual = await nodeJSRuntime.analyseCodebase(fileSystem, allFrameworks); const expected = { id: "nodejs", - baseImage: "node:18-slim", + baseImage: "us-docker.pkg.dev/firestack-build/test/run", packageManagerInstallCommand: undefined, installCommand: "npm install", detectedCommands: { From 9f14838ee831db8f8642e56b5fc1194bbbc1c73d Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 29 Jun 2023 11:52:05 -0700 Subject: [PATCH 1066/1699] VSCode plugin: Workarounds for functions code + Monospace settings (#6056) --- firebase-vscode/package-lock.json | 4 +-- firebase-vscode/package.json | 15 ++++++---- firebase-vscode/scripts/swap-pkg.js | 22 ++++++++++++-- firebase-vscode/webpack.common.js | 46 +++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index 56f8dbfade9..ef2240a2da3 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-vscode", - "version": "0.0.22-alpha.0", + "version": "0.0.23-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-vscode", - "version": "0.0.22-alpha.0", + "version": "0.0.23-alpha.1", "dependencies": { "@vscode/codicons": "0.0.30", "@vscode/webview-ui-toolkit": "^1.2.1", diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index 415cf731ecb..1eae05edc62 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -3,7 +3,7 @@ "displayName": "firebase-vscode", "publisher": "firebase", "description": "VSCode Extension for Firebase", - "version": "0.0.22-alpha.0", + "version": "0.0.23-alpha.1", "engines": { "vscode": "^1.69.0" }, @@ -22,12 +22,12 @@ "properties": { "firebase.debug": { "type": "boolean", - "default": false, + "default": true, "description": "Enable writing debug-level messages to the file provided in firebase.debugLogPath (requires restart)" }, "firebase.debugLogPath": { "type": "string", - "default": "", + "default": "/tmp/firebase-plugin.log", "description": "If firebase.debug is true, appends debug-level messages to the provided file (requires restart)" }, "firebase.npmPath": { @@ -37,8 +37,8 @@ }, "firebase.useFrameworks": { "type": "boolean", - "default": false, - "description": "Enable web frameworks in a local VSCode environment" + "default": true, + "description": "Enable web frameworks" } } }, @@ -133,5 +133,8 @@ "webpack": "^5.75.0", "webpack-cli": "^5.0.1", "webpack-merge": "^5.8.0" - } + }, + "extensionDependencies": [ + "google.monospace" + ] } \ No newline at end of file diff --git a/firebase-vscode/scripts/swap-pkg.js b/firebase-vscode/scripts/swap-pkg.js index f8655afa9b3..92f68fce843 100644 --- a/firebase-vscode/scripts/swap-pkg.js +++ b/firebase-vscode/scripts/swap-pkg.js @@ -2,6 +2,13 @@ const { writeFileSync } = require("fs"); const path = require("path"); const pkg = require(path.join(__dirname, "../package.json")); +// Swaps package.json config as appropriate for packaging for +// Monospace or VSCE marketplace. + +// TODO(chholland): Don't overwrite the real package.json file and +// create a generated one in dist/ - redo .vscodeignore to package +// dist/ + let target = "vsce"; process.argv.forEach((arg) => { @@ -13,12 +20,23 @@ process.argv.forEach((arg) => { if (target === "vsce") { delete pkg.extensionDependencies; console.log( - "Removing google.monospace extensionDependency for VSCE" + " packaging." + "Removing google.monospace extensionDependency for VSCE packaging." + ); + pkg.contributes.configuration.properties['firebase.debug'].default = false; + pkg.contributes.configuration.properties['firebase.debugLogPath'].default = ""; + console.log( + "Setting default debug log settings to off for VSCE packaging." ); } else if (target === "monospace") { pkg.extensionDependencies = ["google.monospace"]; console.log( - "Adding google.monospace extensionDependency for Monospace" + " packaging." + "Adding google.monospace extensionDependency for Monospace packaging." + ); + pkg.contributes.configuration.properties['firebase.debug'].default = true; + pkg.contributes.configuration.properties['firebase.debugLogPath'].default = + "/tmp/firebase-plugin.log"; + console.log( + "Setting default debug log settings to on for Monospace packaging." ); } diff --git a/firebase-vscode/webpack.common.js b/firebase-vscode/webpack.common.js index ca1e75761e2..d23b42fa85d 100644 --- a/firebase-vscode/webpack.common.js +++ b/firebase-vscode/webpack.common.js @@ -65,22 +65,61 @@ const extensionConfig = { loader: "string-replace-loader", options: { multiple: [ + // CLI code has absolute path to templates/. We copy templates/ + // into dist, and this is the correct path now. { search: /(\.|\.\.)[\.\/]+templates/g, replace: "./templates", }, + // CLI code has absolute path to schema/. We copy schema/ + // into dist, and this is the correct path now. { search: /(\.|\.\.)[\.\/]+schema/g, replace: "./schema", }, + // Without doing this, it dynamically grabs pkg.name from + // package.json, which is the firebase-vscode package name. + // We want to use the same configstore name as firebase-tools + // so the CLI and extension can share login state. { search: /Configstore\(pkg\.name\)/g, replace: "Configstore('firebase-tools')", }, // TODO(hsubox76): replace with something more robust + // This is an issue with a local call to cross-env-shell.js + // and a dependency on calling the Node executable using an env + // variable which returns a string with a lot of unusual characters + // if called inside VSCode. + // We may be able to copy cross-env-shell.js into dist? + // For the node executable we can probably just call Node, still + // need to search/replace though { search: "childProcess.spawn(translatedCommand", replace: "childProcess.spawn(escapedCommand" + }, + // Some CLI code uses module.exports for test stubbing. + // We are using ES2020 and it doesn't recognize functions called + // as exports.functionName() or module.exports.functionName(). + // Maybe separate those CLI src files at a future time so they can + // still be stubbed for tests without doing this, but this is + // a temporary fix. + { + search: /module\.exports\.([a-zA-Z0-9]+)\(/g, + replace: (match) => match.replace('module.exports.', '') + }, + // cloudtasks.ts type casts so there's an " as [type]" before the + // starting paren to call the function + { + search: /module\.exports\.([a-zA-Z0-9]+) as/g, + replace: (match) => match.replace('module.exports.', '') + }, + // Disallow starting . to ensure it doesn't conflict with + // module.exports + // Must end with a paren to avoid overwriting exports assignments + // such as "exports.something = value" + { + search: /[^\.]exports\.([a-zA-Z0-9]+)\(/g, + replace: (match) => match.replace('exports.', '') } ], }, @@ -97,6 +136,13 @@ const extensionConfig = { { from: "../schema", to: "./schema", + }, + // Copy uncompiled JS files called at runtime by + // firebase-tools/src/parseTriggers.ts + { + from: "*.js", + to: './', + context: "../src/deploy/functions/runtimes/node", } ], }) From 57dd7fef80d9b9294da41bb93cf25a1108674459 Mon Sep 17 00:00:00 2001 From: Sairam Sakhamuri Date: Thu, 29 Jun 2023 15:03:30 -0700 Subject: [PATCH 1067/1699] Git to cloud build (#6044) * Initial commit integrate init with api * Revert "Initial commit integrate init with api" This reverts commit 14751512d769d675e9d9fc624c34dfe41f461f0e. * removed internaltesting:frameworks:int command * integrate git repo with cloudBuildRepo * Added changes to create stack * Added changes to create stack * Added code to omit output fields in Stack * Added poller operation * Added poller operation * Added code to integrate init flow to link Git repo and to call frameworks api * Minor changes * minor changes * deleted output file * Rearranged files * Fix imports * Reorganized files * minor changes --- src/commands/index.ts | 2 - .../internaltesting-frameworks-init.ts | 14 ----- src/{api => gcp}/frameworks.ts | 24 ++++---- src/init/features/frameworks/index.ts | 53 ++++++++++++++++ .../features/{composer => frameworks}/repo.ts | 6 +- src/test/init/frameworks/index.spec.ts | 61 +++++++++++++++++++ .../repo.spec.ts} | 2 +- 7 files changed, 129 insertions(+), 33 deletions(-) delete mode 100644 src/commands/internaltesting-frameworks-init.ts rename src/{api => gcp}/frameworks.ts (84%) rename src/init/features/{composer => frameworks}/repo.ts (97%) create mode 100644 src/test/init/frameworks/index.spec.ts rename src/test/init/{features/composer.spec.ts => frameworks/repo.spec.ts} (98%) diff --git a/src/commands/index.ts b/src/commands/index.ts index b3fa211894e..1b3c5d3bc30 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -150,8 +150,6 @@ export function load(client: any): any { client.internaltesting.frameworks.compose = loadCommand("internaltesting-frameworks-compose"); client.internaltesting.functions = {}; client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover"); - client.internaltesting.frameworks = {}; - client.internaltesting.frameworks.init = loadCommand("internaltesting-frameworks-init"); } client.login = loadCommand("login"); client.login.add = loadCommand("login-add"); diff --git a/src/commands/internaltesting-frameworks-init.ts b/src/commands/internaltesting-frameworks-init.ts deleted file mode 100644 index 729fde0e9d9..00000000000 --- a/src/commands/internaltesting-frameworks-init.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Command } from "../command"; -import { linkGitHubRepository } from "../init/features/composer/repo"; -import { Options } from "../options"; -import { needProjectId } from "../projectUtils"; -import requireInteractive from "../requireInteractive"; - -export const command = new Command("internaltesting:frameworks:init") - .description("connect github repo to cloud build") - .before(requireInteractive) - .action(async (options: Options) => { - const projectId = needProjectId(options); - await linkGitHubRepository(projectId, "us-central2", "stack0"); - // TODO: send repo metadata to control plane - }); diff --git a/src/api/frameworks.ts b/src/gcp/frameworks.ts similarity index 84% rename from src/api/frameworks.ts rename to src/gcp/frameworks.ts index 30fcc41bd66..b74b8dbff20 100644 --- a/src/api/frameworks.ts +++ b/src/gcp/frameworks.ts @@ -1,7 +1,7 @@ import { Client } from "../apiv2"; import { frameworksOrigin } from "../api"; -export const API_VERSION = "v1"; +export const API_VERSION = "v2"; const client = new Client({ urlPrefix: frameworksOrigin, @@ -9,7 +9,7 @@ const client = new Client({ apiVersion: API_VERSION, }); -export type State = "BUILDING" | "BUILD" | "DEPLOYING" | "READY" | "FAILED"; +type State = "BUILDING" | "BUILD" | "DEPLOYING" | "READY" | "FAILED"; interface Codebase { repository?: string; @@ -17,7 +17,7 @@ interface Codebase { } /** A Stack, the primary resource of Frameworks. */ -interface Stack { +export interface Stack { name: string; mode?: string; codebase: Codebase; @@ -29,7 +29,7 @@ interface Stack { export type StackOutputOnlyFields = "createTime" | "updateTime" | "uri"; -interface Build { +export interface Build { name: string; state: State; error: Status; @@ -61,7 +61,7 @@ interface CodebaseSource { // end oneof reference } -export interface OperationMetadata { +interface OperationMetadata { createTime: string; endTime: string; target: string; @@ -87,14 +87,15 @@ export interface Operation { export async function createStack( projectId: string, location: string, - stackId: string, - stack: Stack + stackInput: Omit ): Promise { + const stackId = stackInput.name; const res = await client.post, Operation>( `projects/${projectId}/locations/${location}/stacks`, - stack, + stackInput, { queryParams: { stackId } } ); + return res.body; } @@ -105,13 +106,14 @@ export async function createBuild( projectId: string, location: string, stackId: string, - buildId: string, - build: Build + buildInput: Omit ): Promise { + const buildId = buildInput.name; const res = await client.post, Operation>( `projects/${projectId}/locations/${location}/stacks/${stackId}/builds`, - build, + buildInput, { queryParams: { buildId } } ); + return res.body; } diff --git a/src/init/features/frameworks/index.ts b/src/init/features/frameworks/index.ts index 8b75e5023c9..e26b126b2dd 100644 --- a/src/init/features/frameworks/index.ts +++ b/src/init/features/frameworks/index.ts @@ -8,11 +8,26 @@ import { DEFAULT_DEPLOY_METHOD, ALLOWED_DEPLOY_METHODS, } from "./constants"; +import { linkGitHubRepository } from "./repo"; +import { Stack, StackOutputOnlyFields } from "../../../gcp/frameworks"; +import { Repository } from "../../../gcp/cloudbuild"; +import * as poller from "../../../operation-poller"; +import { frameworksOrigin } from "../../../api"; +import * as gcp from "../../../gcp/frameworks"; +import { API_VERSION } from "../../../gcp/frameworks"; + +const frameworksPollerOptions: Omit = { + apiOrigin: frameworksOrigin, + apiVersion: API_VERSION, + masterTimeout: 25 * 60 * 1_000, + maxBackoff: 10_000, +}; /** * Setup new frameworks project. */ export async function doSetup(setup: any): Promise { + const projectId: string = setup?.rcfile?.projects?.default; setup.frameworks = {}; utils.logBullet("First we need a few details to create your service."); @@ -54,4 +69,42 @@ export async function doSetup(setup: any): Promise { }, setup.frameworks ); + + if (setup.frameworks.deployMethod === "github") { + const cloudBuildConnRepo = await linkGitHubRepository( + projectId, + setup.frameworks.region, + setup.frameworks.serviceName + ); + toStack(cloudBuildConnRepo, setup.frameworks.serviceName); + } +} + +function toStack( + cloudBuildConnRepo: Repository, + stackId: string +): Omit { + return { + name: stackId, + codebase: { repository: cloudBuildConnRepo.name, rootDirectory: "/" }, + labels: {}, + }; +} + +/** + * Creates Stack object from long running operations. + */ +export async function createStack( + projectId: string, + location: string, + stackInput: Omit +): Promise { + const op = await gcp.createStack(projectId, location, stackInput); + const stack = await poller.pollOperation({ + ...frameworksPollerOptions, + pollerName: `create-${projectId}-${location}-${stackInput.name}`, + operationResourceName: op.name, + }); + + return stack; } diff --git a/src/init/features/composer/repo.ts b/src/init/features/frameworks/repo.ts similarity index 97% rename from src/init/features/composer/repo.ts rename to src/init/features/frameworks/repo.ts index c5080ffa73f..5b726b855f5 100644 --- a/src/init/features/composer/repo.ts +++ b/src/init/features/frameworks/repo.ts @@ -25,10 +25,6 @@ function extractRepoSlugFromURI(remoteUri: string): string | undefined { return match[1]; } -function generateConnectionId(stackId: string): string { - return `composer-${stackId}-conn`; -} - /** * Generates a repository ID. * N.B. The deterministic nature of the repository ID implies that each @@ -48,7 +44,7 @@ export async function linkGitHubRepository( location: string, stackId: string ): Promise { - const connectionId = generateConnectionId(stackId); + const connectionId = stackId; await getOrCreateConnection(projectId, location, connectionId); let remoteUri = await promptRepositoryURI(projectId, location, connectionId); diff --git a/src/test/init/frameworks/index.spec.ts b/src/test/init/frameworks/index.spec.ts new file mode 100644 index 00000000000..1534f014f55 --- /dev/null +++ b/src/test/init/frameworks/index.spec.ts @@ -0,0 +1,61 @@ +import * as sinon from "sinon"; +import { expect } from "chai"; + +import * as gcp from "../../../gcp/frameworks"; +import * as poller from "../../../operation-poller"; +import { createStack } from "../../../init/features/frameworks/index"; + +describe("operationsConverter", () => { + const sandbox: sinon.SinonSandbox = sinon.createSandbox(); + + let pollOperationStub: sinon.SinonStub; + let createStackStub: sinon.SinonStub; + + beforeEach(() => { + pollOperationStub = sandbox + .stub(poller, "pollOperation") + .throws("Unexpected pollOperation call"); + createStackStub = sandbox.stub(gcp, "createStack").throws("Unexpected createStack call"); + }); + + afterEach(() => { + sandbox.verifyAndRestore(); + }); + + describe("createStack", () => { + const projectId = "projectId"; + const location = "us-central1"; + const stackId = "stackId"; + const stackInput = { + name: stackId, + codebase: { + repository: `projects/${projectId}/locations/${location}/connections/${stackId}`, + rootDirectory: "/", + }, + labels: {}, + }; + const op = { + name: `projects/${projectId}/locations/${location}/stacks/${stackId}`, + done: true, + }; + const completeStack = { + name: `projects/${projectId}/locations/${location}/stacks/${stackId}`, + codebase: { + repository: `projects/${projectId}/locations/${location}/connections/${stackId}`, + rootDirectory: "/", + }, + labels: {}, + createTime: "0", + updateTime: "1", + uri: "https://placeholder.com", + }; + + it("checks is correct arguments are sent & creates a stack", async () => { + createStackStub.resolves(op); + pollOperationStub.resolves(completeStack); + + await createStack(projectId, location, stackInput); + expect(createStackStub).to.be.calledWith(projectId, location, stackInput); + }); + }); +}); diff --git a/src/test/init/features/composer.spec.ts b/src/test/init/frameworks/repo.spec.ts similarity index 98% rename from src/test/init/features/composer.spec.ts rename to src/test/init/frameworks/repo.spec.ts index 661bdd831aa..1c21b608b43 100644 --- a/src/test/init/features/composer.spec.ts +++ b/src/test/init/frameworks/repo.spec.ts @@ -5,7 +5,7 @@ import * as gcb from "../../../gcp/cloudbuild"; import * as prompt from "../../../prompt"; import * as poller from "../../../operation-poller"; import { FirebaseError } from "../../../error"; -import * as repo from "../../../init/features/composer/repo"; +import * as repo from "../../../init/features/frameworks/repo"; import * as utils from "../../../utils"; describe("composer", () => { From e091b2af7f9c7e0120bbe673a753609d896c3a38 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 5 Jul 2023 10:12:58 -0700 Subject: [PATCH 1068/1699] Frameworks: Call "next build" from command line instead of import (#6066) --- firebase-vscode/package-lock.json | 4 ++-- firebase-vscode/package.json | 11 ++++------- src/frameworks/next/index.ts | 21 ++++++++++++++------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index ef2240a2da3..ccbdb5e7b61 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-vscode", - "version": "0.0.23-alpha.1", + "version": "0.0.23-alpha.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-vscode", - "version": "0.0.23-alpha.1", + "version": "0.0.23-alpha.2", "dependencies": { "@vscode/codicons": "0.0.30", "@vscode/webview-ui-toolkit": "^1.2.1", diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index 1eae05edc62..e18acd3939c 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -3,7 +3,7 @@ "displayName": "firebase-vscode", "publisher": "firebase", "description": "VSCode Extension for Firebase", - "version": "0.0.23-alpha.1", + "version": "0.0.23-alpha.2", "engines": { "vscode": "^1.69.0" }, @@ -22,12 +22,12 @@ "properties": { "firebase.debug": { "type": "boolean", - "default": true, + "default": false, "description": "Enable writing debug-level messages to the file provided in firebase.debugLogPath (requires restart)" }, "firebase.debugLogPath": { "type": "string", - "default": "/tmp/firebase-plugin.log", + "default": "", "description": "If firebase.debug is true, appends debug-level messages to the provided file (requires restart)" }, "firebase.npmPath": { @@ -133,8 +133,5 @@ "webpack": "^5.75.0", "webpack-cli": "^5.0.1", "webpack-merge": "^5.8.0" - }, - "extensionDependencies": [ - "google.monospace" - ] + } } \ No newline at end of file diff --git a/src/frameworks/next/index.ts b/src/frameworks/next/index.ts index e64ae4865d1..df3fb97f9de 100644 --- a/src/frameworks/next/index.ts +++ b/src/frameworks/next/index.ts @@ -28,6 +28,7 @@ import { relativeRequire, findDependency, validateLocales, + getNodeModuleBin, } from "../utils"; import { BuildResult, FrameworkType, SupportLevel } from "../interfaces"; @@ -101,8 +102,6 @@ export async function discover(dir: string) { * Build a next.js application. */ export async function build(dir: string): Promise { - const { default: nextBuild } = relativeRequire(dir, "next/dist/build"); - await warnIfCustomBuildScript(dir, name, DEFAULT_BUILD_SCRIPT); const reactVersion = getReactVersion(dir); @@ -111,12 +110,20 @@ export async function build(dir: string): Promise { process.env.__NEXT_REACT_ROOT = "true"; } - await nextBuild(dir, null, false, false, true).catch((e) => { - // Err on the side of displaying this error, since this is likely a bug in - // the developer's code that we want to display immediately - console.error(e.message); - throw e; + const cli = getNodeModuleBin("next", dir); + + const nextBuild = new Promise((resolve, reject) => { + const buildProcess = spawn(cli, ["build"], { cwd: dir }); + buildProcess.stdout?.on("data", (data) => logger.info(data.toString())); + buildProcess.stderr?.on("data", (data) => logger.info(data.toString())); + buildProcess.on("error", (err) => { + reject(new FirebaseError(`Unable to build your Next.js app: ${err}`)); + }); + buildProcess.on("exit", (code) => { + resolve(code); + }); }); + await nextBuild; const reasonsForBackend = new Set(); const { distDir, trailingSlash, basePath: baseUrl } = await getConfig(dir); From b7730e3ee9d6385c7039c0114111672601880a78 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 5 Jul 2023 15:29:33 -0700 Subject: [PATCH 1069/1699] Add support for running package manager install command. (#6064) --- src/frameworks/compose/driver/docker.ts | 13 ++++++++++--- src/frameworks/compose/driver/local.ts | 4 ++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/frameworks/compose/driver/docker.ts b/src/frameworks/compose/driver/docker.ts index 8033d7c16cd..4e84f16dc38 100644 --- a/src/frameworks/compose/driver/docker.ts +++ b/src/frameworks/compose/driver/docker.ts @@ -105,7 +105,11 @@ export class DockerDriver implements Driver { } private execDockerPush(args: string[]) { - console.log(`executing docker build: ${args.join(" ")}`); + console.debug(JSON.stringify({ message: `executing docker build: ${args.join(" ")}` })); + console.info( + JSON.stringify({ foo: "bar", message: `executing docker build: ${args.join(" ")}` }) + ); + console.error(JSON.stringify({ message: `executing docker build: ${args.join(" ")}` })); return spawn.sync("docker", ["push", ...args], { stdio: [/* stdin= */ "pipe", /* stdout= */ "inherit", /* stderr= */ "inherit"], }); @@ -154,8 +158,11 @@ export class DockerDriver implements Driver { .fromLastStage(DOCKER_STAGE_INSTALL) .workdir("/home/firebase/app") .envs(this.spec.environmentVariables || {}) - .copyForFirebase("package.json", ".") - .run(this.spec.installCommand); + .copyForFirebase("package.json", "."); + if (this.spec.packageManagerInstallCommand) { + this.dockerfileBuilder.run(this.spec.packageManagerInstallCommand); + } + this.dockerfileBuilder.run(this.spec.installCommand); this.buildStage(DOCKER_STAGE_INSTALL, "."); } } diff --git a/src/frameworks/compose/driver/local.ts b/src/frameworks/compose/driver/local.ts index 5bd219c113c..f20af3583bc 100644 --- a/src/frameworks/compose/driver/local.ts +++ b/src/frameworks/compose/driver/local.ts @@ -20,6 +20,10 @@ export class LocalDriver implements Driver { install(): void { if (this.spec.installCommand) { + if (this.spec.packageManagerInstallCommand) { + const [cmd, ...args] = this.spec.packageManagerInstallCommand.split(" "); + this.execCmd(cmd, args); + } const [cmd, ...args] = this.spec.installCommand.split(" "); this.execCmd(cmd, args); } From 1ef81dfec73c0b06ff22e1bb40d3cb4f51ee0674 Mon Sep 17 00:00:00 2001 From: christhompsongoogle <106194718+christhompsongoogle@users.noreply.github.com> Date: Thu, 6 Jul 2023 10:36:57 -0700 Subject: [PATCH 1070/1699] Release Firebase Emulator UI v 1.11.7 (#6079) * Release Firebase Emulator Ui v 1.11.7 * Update commit --------- Co-authored-by: joehan --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..5b951c3337e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Released Firebase Emulator UI v1.11.7, which includes preview support for multiple Firestore databases (#6079) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index ca3ec53dbe8..32de1c709ce 100644 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -45,9 +45,9 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet ui: experiments.isEnabled("emulatoruisnapshot") ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" } : { - version: "1.11.6", - expectedSize: 3063444, - expectedChecksum: "14b971f4ed4909f348e647db7114d62b", + version: "1.11.7", + expectedSize: 3064105, + expectedChecksum: "bd2bcc331cbf613a5b3b55a1ce08998b", }, pubsub: { version: "0.7.1", From 794b869330efa8b4bbd08b0c53754c3d132f3046 Mon Sep 17 00:00:00 2001 From: aalej Date: Fri, 7 Jul 2023 02:03:47 +0800 Subject: [PATCH 1071/1699] Update firebase open links (#6073) * Update firebase open links * adding changelog --------- Co-authored-by: joehan --- CHANGELOG.md | 3 ++- src/commands/open.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b951c3337e..07dd3b508f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ -- Released Firebase Emulator UI v1.11.7, which includes preview support for multiple Firestore databases (#6079) +- Fixed incorrect links in `firebase open hosting` and `firebase open crash`. (#6073) +- Released Firebase Emulator UI v1.11.7, which includes preview support for multiple Firestore databases. (#6079) diff --git a/src/commands/open.ts b/src/commands/open.ts index 45d5a5138c0..3b30b334364 100644 --- a/src/commands/open.ts +++ b/src/commands/open.ts @@ -22,7 +22,7 @@ const LINKS: Link[] = [ { name: "Analytics", arg: "analytics", consolePath: "/analytics" }, { name: "Authentication: Providers", arg: "auth", consolePath: "/authentication/providers" }, { name: "Authentication: Users", arg: "auth:users", consolePath: "/authentication/users" }, - { name: "Crash Reporting", arg: "crash", consolePath: "/monitoring" }, + { name: "Crash Reporting", arg: "crash", consolePath: "/crashlytics" }, { name: "Database: Data", arg: "database", consolePath: "/database/data" }, { name: "Database: Rules", arg: "database:rules", consolePath: "/database/rules" }, { name: "Docs", arg: "docs", url: "https://firebase.google.com/docs" }, @@ -44,7 +44,7 @@ const LINKS: Link[] = [ { name: "Functions", arg: "functions", consolePath: "/functions/list" }, { name: "Functions Log", arg: "functions:log" } /* Special Case */, { name: "Hosting: Deployed Site", arg: "hosting:site" } /* Special Case */, - { name: "Hosting", arg: "hosting", consolePath: "/hosting/main" }, + { name: "Hosting", arg: "hosting", consolePath: "/hosting/sites" }, { name: "Notifications", arg: "notifications", consolePath: "/notification" }, { name: "Project Dashboard", arg: "dashboard", consolePath: "/overview" }, { name: "Project Settings", arg: "settings", consolePath: "/settings/general" }, From a6c3aec258d55a4012f8404d07238477a882c4c5 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 6 Jul 2023 20:02:30 +0000 Subject: [PATCH 1072/1699] 12.4.3 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 1e9c7e9c395..1f8651a0e91 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "12.4.2", + "version": "12.4.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "12.4.2", + "version": "12.4.3", "license": "MIT", "dependencies": { "@google-cloud/pubsub": "^3.0.1", diff --git a/package.json b/package.json index 39244402cda..20fa744fa17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "12.4.2", + "version": "12.4.3", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 48b226f29c08103e2c1892513c46d62103353eb3 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 6 Jul 2023 20:02:43 +0000 Subject: [PATCH 1073/1699] [firebase-release] Removed change log and reset repo after 12.4.3 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07dd3b508f7..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Fixed incorrect links in `firebase open hosting` and `firebase open crash`. (#6073) -- Released Firebase Emulator UI v1.11.7, which includes preview support for multiple Firestore databases. (#6079) From 3bcc6712f81f9c8e882b6c5e5e41869a1d24f824 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Thu, 6 Jul 2023 14:05:58 -0700 Subject: [PATCH 1074/1699] VSCode plugin: Handle service accounts better (#6078) --- firebase-vscode/common/messaging/protocol.ts | 5 +- firebase-vscode/src/cli.ts | 72 ++++++++++++++----- firebase-vscode/src/workflow.ts | 69 +++++++++--------- firebase-vscode/webviews/SidebarApp.tsx | 19 +++-- .../webviews/components/AccountSection.tsx | 42 ++++++----- .../webviews/components/ProjectSection.tsx | 2 +- firebase-vscode/webviews/globals/ux-text.ts | 4 ++ src/requireAuth.ts | 16 ++++- 8 files changed, 145 insertions(+), 84 deletions(-) diff --git a/firebase-vscode/common/messaging/protocol.ts b/firebase-vscode/common/messaging/protocol.ts index ec79deefe5e..eedd4bbb249 100644 --- a/firebase-vscode/common/messaging/protocol.ts +++ b/firebase-vscode/common/messaging/protocol.ts @@ -21,14 +21,13 @@ export interface WebviewToExtensionParamsMap { requestChangeUser: { user: User | ServiceAccountUser }; /** Trigger project selection */ - selectProject: { email: string }; + selectProject: {}; /** * Runs `firebase init hosting` command. * TODO(hsubox76): Generalize to work for all `firebase init` products. */ selectAndInitHostingFolder: { projectId: string, - email: string, singleAppSupport: boolean }; @@ -91,7 +90,7 @@ export interface ExtensionToWebviewParamsMap { /** * This can potentially call multiple webviews to notify of user selection. */ - notifyUserChanged: { email: string }; + notifyUserChanged: { user: User | ServiceAccountUser }; /** * Notifies webview when user has successfully selected a hosting folder diff --git a/firebase-vscode/src/cli.ts b/firebase-vscode/src/cli.ts index beae5b8a777..d89db2e2fcb 100644 --- a/firebase-vscode/src/cli.ts +++ b/firebase-vscode/src/cli.ts @@ -18,40 +18,76 @@ import { Account, User } from "../../src/types/auth"; import { Options } from "../../src/options"; import { currentOptions, getCommandOptions } from "./options"; import { setInquirerOptions } from "./stubs/inquirer-stub"; -import { ServiceAccount } from "../common/types"; +import { ServiceAccount, ServiceAccountUser } from "../common/types"; import { listChannels } from "../../src/hosting/api"; import { ChannelWithId } from "../common/messaging/types"; import { pluginLogger } from "./logger-wrapper"; import { Config } from "../../src/config"; +import { currentUser } from "./workflow"; +import { setAccessToken } from "../../src/apiv2"; + +/** + * Try to get a service account by calling requireAuth() without + * providing any account info. + */ +async function getServiceAccount() { + let email = null; + try { + email = (await requireAuth({})) || null; + } catch (e) { + pluginLogger.debug('No service account found (this may be normal), requireAuth error output:', + e.original || e); + } + return email; +} /** * Wrap the CLI's requireAuth() which is normally run before every command * requiring user to be logged in. The CLI automatically supplies it with * account info if found in configstore so we need to fill that part in. */ -async function requireAuthWrapper(showError: boolean = true) { +async function requireAuthWrapper(showError: boolean = true): Promise { // Try to get global default from configstore. For some reason this is // often overwritten when restarting the extension. pluginLogger.debug('requireAuthWrapper'); + let authFound = false; let account = getGlobalDefaultAccount(); if (!account) { // If nothing in configstore top level, grab the first "additionalAccount" const accounts = getAllAccounts(); - if (accounts.length > 0) { - account = accounts[0]; - setGlobalDefaultAccount(account); + for (const additionalAccount of accounts) { + if (additionalAccount.user.email === currentUser.email) { + account = additionalAccount; + setGlobalDefaultAccount(account); + } } } - // `requireAuth()` will register the token with apiv2, and if account is - // still null at this point, it will use google-auth-library - // to find the service account. + if (account) { + authFound = true; + } + const commandOptions = await getCommandOptions(undefined, { + ...currentOptions + }); + // `requireAuth()` is not just a check, but will also register SERVICE + // ACCOUNT tokens in memory as a variable in apiv2.ts, which is needed + // for subsequent API calls. Warning: this variable takes precedence + // over Google login tokens and must be removed if a Google + // account is the current user. try { - const commandOptions = await getCommandOptions(undefined, { - ...currentOptions, - ...account, - }); - await requireAuth(commandOptions); + const serviceAccountEmail = await getServiceAccount(); + // Priority 1: Service account exists and is the current selected user + if (serviceAccountEmail && currentUser.email === serviceAccountEmail) { + await requireAuth(commandOptions); + } else if (account) { + // Priority 2: Google login account exists and is the currently selected + // user + // Priority 3: Google login account exists and there is no selected user + // Clear service account access token from memory in apiv2. + setAccessToken(); + await requireAuth({...commandOptions, ...account}); + } } catch (e) { + // No service account or google login found. if (showError) { pluginLogger.error('requireAuth error', e.original || e); vscode.window.showErrorMessage("Not logged in", { @@ -68,7 +104,7 @@ async function requireAuthWrapper(showError: boolean = true) { } // If we reach here, there is either a google account or no error on // requireAuth (which means there is a service account or glogin) - return true; + return authFound; } export async function getAccounts(): Promise> { @@ -76,11 +112,11 @@ export async function getAccounts(): Promise> { const accounts: Array = getAllAccounts(); pluginLogger.debug(`Found ${accounts.length} non-service accounts.`); // Get other accounts (assuming service account for now, could also be glogin) - const otherAuthExists = await requireAuthWrapper(false); - if (otherAuthExists) { - pluginLogger.debug(`Found service account`); + const serviceAccountEmail = await getServiceAccount(); + if (serviceAccountEmail) { + pluginLogger.debug(`Found service account: ${serviceAccountEmail}`); accounts.push({ - user: { email: "service_account", type: "service_account" }, + user: { email: serviceAccountEmail, type: "service_account" }, }); } return accounts; diff --git a/firebase-vscode/src/workflow.ts b/firebase-vscode/src/workflow.ts index 7ec6186cc6f..5327aa871f0 100644 --- a/firebase-vscode/src/workflow.ts +++ b/firebase-vscode/src/workflow.ts @@ -33,7 +33,7 @@ import { import { ServiceAccountUser } from "../common/types"; let users: Array = []; -let currentUserEmail = ""; +export let currentUser: User | ServiceAccountUser; // Stores a mapping from user email to list of projects for that user let projectsUserMapping = new Map(); let channels = null; @@ -71,24 +71,22 @@ async function promptUserForProject( function updateCurrentUser( users: User[], broker: ExtensionBrokerImpl, - newUserEmail?: string + newUser?: User | ServiceAccountUser ) { - if (newUserEmail) { - if (newUserEmail === currentUserEmail) { - return currentUserEmail; - } else { - currentUserEmail = newUserEmail; + if (newUser) { + if (newUser.email !== currentUser.email) { + currentUser = newUser; } } - if (!newUserEmail) { + if (!newUser) { if (users.length > 0) { - currentUserEmail = users[0].email; + currentUser = users[0]; } else { - currentUserEmail = null; + currentUser = null; } } - broker.send("notifyUserChanged", { email: currentUserEmail }); - return currentUserEmail; + broker.send("notifyUserChanged", { user: currentUser }); + return currentUser; } export async function setupWorkflow( @@ -107,7 +105,7 @@ export async function setupWorkflow( vscode.workspace.workspaceFolders[0].uri ); shouldWriteDebug = workspaceConfig.get('debug'); - debugLogPath= workspaceConfig.get('debugLogPath'); + debugLogPath = workspaceConfig.get('debugLogPath'); useFrameworks = workspaceConfig.get('useFrameworks'); npmPath = workspaceConfig.get('npmPath'); if (npmPath) { @@ -124,7 +122,7 @@ export async function setupWorkflow( // Sets up CLI logger to log to console process.env.DEBUG = 'true'; setupLoggers(); - + // Only log to file if firebase.debug extension setting is true. if (shouldWriteDebug) { // Re-implement file logger call from ../../src/bin/firebase.ts to not bring @@ -132,18 +130,18 @@ export async function setupWorkflow( const rootFolders = getRootFolders(); const filePath = debugLogPath || path.join(rootFolders[0], 'firebase-plugin-debug.log'); pluginLogger.info('Logging to path', filePath); - logger.add( - new transports.File({ - level: "debug", - filename: filePath, - format: format.printf((info) => { - const segments = [info.message, ...(info[SPLAT] || [])] - .map(tryStringify); - return `[${info.level}] ${stripAnsi(segments.join(" "))}`; - }), - }) - ); - } + logger.add( + new transports.File({ + level: "debug", + filename: filePath, + format: format.printf((info) => { + const segments = [info.message, ...(info[SPLAT] || [])] + .map(tryStringify); + return `[${info.level}] ${stripAnsi(segments.join(" "))}`; + }), + }) + ); + } /** * Call pluginLogger with log arguments received from webview. @@ -168,7 +166,7 @@ export async function setupWorkflow( // User login state await fetchUsers(); broker.send("notifyUsers", { users }); - currentUserEmail = updateCurrentUser(users, broker); + currentUser = updateCurrentUser(users, broker, currentUser); if (users.length > 0) { await fetchChannels(); } @@ -187,7 +185,7 @@ export async function setupWorkflow( const accounts = await getAccounts(); users = accounts.map((account) => account.user); broker.send("notifyUsers", { users }); - currentUserEmail = updateCurrentUser(users, broker); + currentUser = updateCurrentUser(users, broker); } catch (e) { // ignored } @@ -206,10 +204,10 @@ export async function setupWorkflow( users.push(user); if (users) { broker.send("notifyUsers", { users }); - currentUserEmail = updateCurrentUser( + currentUser = updateCurrentUser( users, broker, - user.email + user ); } }); @@ -219,8 +217,8 @@ export async function setupWorkflow( { user: User | ServiceAccountUser } ) => { if (users.some((user) => user.email === requestedUser.email)) { - currentUserEmail = requestedUser.email; - broker.send("notifyUserChanged", { email: currentUserEmail }); + currentUser = requestedUser; + broker.send("notifyUserChanged", { user: currentUser }); } }); @@ -259,8 +257,11 @@ export async function setupWorkflow( broker.send("notifyChannels", { channels }); } - async function selectProject({ email }) { + async function selectProject() { let projectId; + const isServiceAccount = + (currentUser as ServiceAccountUser).type === "service_account"; + const email = currentUser.email; if (process.env.MONOSPACE_ENV) { pluginLogger.debug('selectProject: found MONOSPACE_ENV, ' + 'prompting user using external flow'); @@ -280,7 +281,7 @@ export async function setupWorkflow( } catch (e) { pluginLogger.error(e); } - } else if (email === 'service_account') { + } else if (isServiceAccount) { /** * Non-Monospace service account case: get the service account's only * linked project. diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx index 5a5b4c61175..e3f75f7a2ed 100644 --- a/firebase-vscode/webviews/SidebarApp.tsx +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -18,7 +18,7 @@ export function SidebarApp() { const [hostingState, setHostingState] = useState(null); const [env, setEnv] = useState<{ isMonospace: boolean }>(); const [channels, setChannels] = useState(null); - const [userEmail, setUserEmail] = useState(null); + const [user, setUser] = useState(null); /** * null - has not finished checking yet * empty array - finished checking, no users logged in @@ -81,9 +81,9 @@ export function SidebarApp() { setProjectId(projectId); }); - broker.on("notifyUserChanged", ({ email }) => { - webLogger.debug("notifyUserChanged:", email); - setUserEmail(email); + broker.on("notifyUserChanged", ({ user }) => { + webLogger.debug("notifyUserChanged:", user.email); + setUser(user); }); broker.on("notifyHostingInitDone", ({ projectId, folderPath }) => { @@ -100,14 +100,13 @@ export function SidebarApp() { function setupHosting() { broker.send("selectAndInitHostingFolder", { projectId, - email: userEmail!, // Safe to assume user email is already there singleAppSupport: true, }); } const accountSection = ( @@ -126,10 +125,10 @@ export function SidebarApp() { <> {accountSection} - {!!userEmail && ( - + {!!user && ( + )} - {isHostingOnboarded && !!userEmail && !!projectId && ( + {isHostingOnboarded && !!user && !!projectId && ( )} - {!isHostingOnboarded && !!userEmail && !!projectId && ( + {!isHostingOnboarded && !!user && !!projectId && ( { setupHosting(); diff --git a/firebase-vscode/webviews/components/AccountSection.tsx b/firebase-vscode/webviews/components/AccountSection.tsx index 47bd62054ac..dba9ddc3896 100644 --- a/firebase-vscode/webviews/components/AccountSection.tsx +++ b/firebase-vscode/webviews/components/AccountSection.tsx @@ -14,12 +14,16 @@ import { ServiceAccountUser } from "../../common/types"; import { User } from "../../../src/types/auth"; import { TEXT } from "../globals/ux-text"; +interface UserWithType extends User { + type?: string; +} + export function AccountSection({ - userEmail, + user, allUsers, isMonospace, }: { - userEmail: string | null; + user: UserWithType | ServiceAccountUser | null; allUsers: Array | null; isMonospace: boolean; }) { @@ -27,7 +31,7 @@ export function AccountSection({ const usersLoaded = !!allUsers; // Default: initial users check hasn't completed let currentUserElement: ReactElement | string = TEXT.LOGIN_PROGRESS; - if (usersLoaded && !allUsers.length) { + if (usersLoaded && (!allUsers.length || !user)) { // Users loaded but no user was found if (isMonospace) { // Monospace: this is an error, should have found a workspace @@ -43,10 +47,14 @@ export function AccountSection({ } } else if (usersLoaded && allUsers.length > 0) { // Users loaded, at least one user was found - if (isMonospace && userEmail === "service_account") { - currentUserElement = TEXT.MONOSPACE_LOGGED_IN; + if (user.type === "service_account") { + if (isMonospace) { + currentUserElement = TEXT.MONOSPACE_LOGGED_IN; + } else { + currentUserElement = TEXT.VSCE_SERVICE_ACCOUNT_LOGGED_IN; + } } else { - currentUserElement = userEmail; + currentUserElement = user.email; } } return ( @@ -70,7 +78,7 @@ export function AccountSection({ {userDropdownVisible ? ( toggleUserDropdown(false)} /> @@ -83,19 +91,19 @@ export function AccountSection({ // TODO(roman): Convert to a better menu function UserSelectionMenu({ - userEmail, + user, allUsers, onClose, isMonospace, }: { - userEmail: string; + user: UserWithType | ServiceAccountUser; allUsers: Array; onClose: Function; isMonospace: boolean; }) { return ( <> - + { broker.send("addUser"); @@ -105,7 +113,7 @@ function UserSelectionMenu({ Sign in another user... - {allUsers.map((user) => ( + {allUsers.map((user: UserWithType | ServiceAccountUser) => ( { broker.send("requestChangeUser", { user }); @@ -113,22 +121,24 @@ function UserSelectionMenu({ }} key={user.email} > - {isMonospace && user.email === "service_account" - ? TEXT.MONOSPACE_LOGIN_SELECTION_ITEM + {user?.type === "service_account" + ? isMonospace + ? TEXT.MONOSPACE_LOGIN_SELECTION_ITEM + : TEXT.VSCE_SERVICE_ACCOUNT_SELECTION_ITEM : user.email} ))} { // You can't log out of a service account - userEmail !== "service_account" && ( + user.type !== "service_account" && ( { - broker.send("logout", { email: userEmail }); + broker.send("logout", { email: user.email }); onClose(); }} > - Sign Out {userEmail} + Sign Out {user.email} ) } diff --git a/firebase-vscode/webviews/components/ProjectSection.tsx b/firebase-vscode/webviews/components/ProjectSection.tsx index 40ca46c3b29..34c6e4b3d30 100644 --- a/firebase-vscode/webviews/components/ProjectSection.tsx +++ b/firebase-vscode/webviews/components/ProjectSection.tsx @@ -44,7 +44,7 @@ export function ProjectSection({ export function initProjectSelection(userEmail: string | null) { if (userEmail) { - broker.send("selectProject", { email: userEmail }); + broker.send("selectProject"); } else { broker.send("showMessage", { msg: "Not logged in", diff --git a/firebase-vscode/webviews/globals/ux-text.ts b/firebase-vscode/webviews/globals/ux-text.ts index d4d84521a1c..3703b1b2c16 100644 --- a/firebase-vscode/webviews/globals/ux-text.ts +++ b/firebase-vscode/webviews/globals/ux-text.ts @@ -12,6 +12,10 @@ export const TEXT = { MONOSPACE_LOGIN_SELECTION_ITEM: "Default credentials", + VSCE_SERVICE_ACCOUNT_LOGGED_IN: "Logged in with service account", + + VSCE_SERVICE_ACCOUNT_SELECTION_ITEM: "service account", + MONOSPACE_LOGIN_FAIL: "Unable to find default credentials", GOOGLE_SIGN_IN: "Sign in with Google", diff --git a/src/requireAuth.ts b/src/requireAuth.ts index caaa985f96f..2e1ee156158 100644 --- a/src/requireAuth.ts +++ b/src/requireAuth.ts @@ -33,15 +33,25 @@ function getAuthClient(config: GoogleAuthOptions): GoogleAuth { /** * Retrieves and sets the access token for the current user. + * Returns account email if found. * @param options CLI options. * @param authScopes scopes to be obtained. */ -async function autoAuth(options: Options, authScopes: string[]): Promise { +async function autoAuth(options: Options, authScopes: string[]): Promise { const client = getAuthClient({ scopes: authScopes, projectId: options.project }); const token = await client.getAccessToken(); token !== null ? apiv2.setAccessToken(token) : false; + let clientEmail; + try { + const credentials = await client.getCredentials(); + clientEmail = credentials.client_email; + } catch (e) { + // Make sure any error here doesn't block the CLI, but log it. + logger.debug(`Error getting account credentials.`); + } + if (!options.isVSCE && isMonospaceEnv()) { await selectProjectInMonospace({ projectRoot: options.config.projectDir, @@ -49,13 +59,15 @@ async function autoAuth(options: Options, authScopes: string[]): Promise { isVSCE: options.isVSCE, }); } + + return clientEmail; } /** * Ensures that there is an authenticated user. * @param options CLI options. */ -export async function requireAuth(options: any): Promise { +export async function requireAuth(options: any): Promise { api.setScopes([scopes.CLOUD_PLATFORM, scopes.FIREBASE_PLATFORM]); options.authScopes = api.getScopes(); From 7a9d6f2567c0cdcf0f16b531a02b23258599f434 Mon Sep 17 00:00:00 2001 From: blidd-google <112491344+blidd-google@users.noreply.github.com> Date: Thu, 6 Jul 2023 18:04:16 -0400 Subject: [PATCH 1075/1699] Disables KeepAlive timeout when debugger is attached to the functions emulator (#6069) Node.js 19 introduced a change that sets `keepAlive` to true by default, with a default keep-alive duration of 5 seconds. This change broke our functions emulator behavior when a debugger was attached, as the Node.js HTTP server would consider the connection to be idle when a breakpoint was hit and disconnect after 5 seconds. This PR disables HTTP Keep-Alive and sets the socket timeout to 0 so that breakpoints won't cause the connection to timeout. Fixes https://github.com/firebase/firebase-tools/issues/5991. --- CHANGELOG.md | 1 + src/emulator/functionsRuntimeWorker.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..e0c337f63b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +Disables KeepAlive timeout when debugger is attached to the functions emulator. (#6069) diff --git a/src/emulator/functionsRuntimeWorker.ts b/src/emulator/functionsRuntimeWorker.ts index 8442c32cb1a..cf4e86f086d 100644 --- a/src/emulator/functionsRuntimeWorker.ts +++ b/src/emulator/functionsRuntimeWorker.ts @@ -125,7 +125,12 @@ export class RuntimeWorker { }); } - request(req: http.RequestOptions, resp: http.ServerResponse, body?: unknown): Promise { + request( + req: http.RequestOptions, + resp: http.ServerResponse, + body?: unknown, + debug?: boolean + ): Promise { if (this.triggerKey !== FREE_WORKER_KEY) { this.logInfo(`Beginning execution of "${this.triggerKey}"`); } @@ -176,6 +181,10 @@ export class RuntimeWorker { const piped = _resp.pipe(resp); piped.on("finish", () => finishReq("finish")); }); + if (debug) { + proxy.setSocketKeepAlive(false); + proxy.setTimeout(0); + } proxy.on("timeout", () => { this.logger.log( "ERROR", @@ -367,7 +376,7 @@ export class RuntimeWorkerPool { if (debug) { await worker.sendDebugMsg(debug); } - return worker.request(req, resp, body); + return worker.request(req, resp, body, !!debug); } getIdleWorker(triggerId: string | undefined): RuntimeWorker | undefined { From eab791326b97eef0b9902512a288750d670fe8c7 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Fri, 7 Jul 2023 12:54:32 -0700 Subject: [PATCH 1076/1699] Make firebase:database:list to always use the RTDB management API (#6063) * remove rtdbmanagement experiment * rm firedata api * m * avoid breaking changes * m * doc * Update database-instances-list.ts * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/commands/database-instances-list.ts | 80 +++++++++---------------- src/gcp/firedata.ts | 40 ------------- 3 files changed, 29 insertions(+), 92 deletions(-) delete mode 100644 src/gcp/firedata.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e0c337f63b8..6727ddeea8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ Disables KeepAlive timeout when debugger is attached to the functions emulator. (#6069) +Fixed an issue where `database:list` would have inaccurate results. (#6063) diff --git a/src/commands/database-instances-list.ts b/src/commands/database-instances-list.ts index 5328715b1bc..35f79bbc98a 100644 --- a/src/commands/database-instances-list.ts +++ b/src/commands/database-instances-list.ts @@ -1,12 +1,11 @@ -import { Command } from "../command"; const Table = require("cli-table"); + +import { Command } from "../command"; import * as clc from "colorette"; import * as ora from "ora"; import { logger } from "../logger"; import { requirePermissions } from "../requirePermissions"; -import { needProjectNumber } from "../projectUtils"; -import * as firedata from "../gcp/firedata"; import { Emulators } from "../emulator/types"; import { warnEmulatorNotSupported } from "../emulator/commandUtils"; import * as experiments from "../experiments"; @@ -18,31 +17,13 @@ import { parseDatabaseLocation, } from "../management/database"; -function logInstances(instances: DatabaseInstance[]): void { - if (instances.length === 0) { - logger.info(clc.bold("No database instances found.")); - return; - } - const tableHead = ["Database Instance Name", "Location", "Type", "State"]; - const table = new Table({ head: tableHead, style: { head: ["green"] } }); - instances.forEach((db) => { - table.push([db.name, db.location, db.type, db.state]); - }); - - logger.info(table.toString()); -} - -function logInstancesCount(count = 0): void { - if (count === 0) { - return; - } - logger.info(""); - logger.info(`${count} database instance(s) total.`); -} - -export let command = new Command("database:instances:list") +export const command = new Command("database:instances:list") .description("list realtime database instances, optionally filtered by a specified location") .before(requirePermissions, ["firebasedatabase.instances.list"]) + .option( + "-l, --location ", + "(optional) location for the database instance, defaults to all regions" + ) .before(warnEmulatorNotSupported, Emulators.DATABASE) .action(async (options: any) => { const location = parseDatabaseLocation(options.location, DatabaseLocation.ANY); @@ -50,39 +31,34 @@ export let command = new Command("database:instances:list") "Preparing the list of your Firebase Realtime Database instances" + `${location === DatabaseLocation.ANY ? "" : ` for location: ${location}`}` ).start(); - let instances; - if (experiments.isEnabled("rtdbmanagement")) { - const projectId = needProjectId(options); - try { - instances = await listDatabaseInstances(projectId, location); - } catch (err: any) { - spinner.fail(); - throw err; - } - spinner.succeed(); - logInstances(instances); - logInstancesCount(instances.length); - return instances; - } - const projectNumber = await needProjectNumber(options); + const projectId = needProjectId(options); + let instances: DatabaseInstance[] = []; try { - instances = await firedata.listDatabaseInstances(projectNumber); + instances = await listDatabaseInstances(projectId, location); } catch (err: any) { spinner.fail(); throw err; } spinner.succeed(); - for (const instance of instances) { - logger.info(instance.instance); + if (instances.length === 0) { + logger.info(clc.bold("No database instances found.")); + return; + } + // TODO: remove rtdbmanagement experiment in the next major release. + if (!experiments.isEnabled("rtdbmanagement")) { + for (const instance of instances) { + logger.info(instance.name); + } + logger.info(`Project ${options.project} has ${instances.length} database instances`); + return instances; + } + const tableHead = ["Database Instance Name", "Location", "Type", "State"]; + const table = new Table({ head: tableHead, style: { head: ["green"] } }); + for (const db of instances) { + table.push([db.name, db.location, db.type, db.state]); } - logger.info(`Project ${options.project} has ${instances.length} database instances`); + logger.info(table.toString()); + logger.info(`${instances.length} database instance(s) total.`); return instances; }); - -if (experiments.isEnabled("rtdbmanagement")) { - command = command.option( - "-l, --location ", - "(optional) location for the database instance, defaults to us-central1" - ); -} diff --git a/src/gcp/firedata.ts b/src/gcp/firedata.ts deleted file mode 100644 index 796aba02445..00000000000 --- a/src/gcp/firedata.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { firedataOrigin } from "../api"; -import { Client } from "../apiv2"; -import { logger } from "../logger"; -import * as utils from "../utils"; - -export interface DatabaseInstance { - // The globally unique name of the Database instance. - // Required to be URL safe. ex: 'red-ant' - instance: string; -} - -function _handleErrorResponse(response: any): any { - if (response.body && response.body.error) { - return utils.reject(response.body.error, { code: 2 }); - } - - logger.debug("[firedata] error:", response.status, response.body); - return utils.reject("Unexpected error encountered with FireData.", { - code: 2, - }); -} - -/** - * List Realtime Database instances - * @param projectNumber Project from which you want to list databases. - * @return the list of databases. - */ -export async function listDatabaseInstances(projectNumber: string): Promise { - const client = new Client({ urlPrefix: firedataOrigin, apiVersion: "v1" }); - const response = await client.get<{ instance: DatabaseInstance[] }>( - `/projects/${projectNumber}/databases`, - { - resolveOnHTTPError: true, - } - ); - if (response.status === 200) { - return response.body.instance; - } - return _handleErrorResponse(response); -} From 2a30b5dc403ac064ad249653f6a99295d4509373 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Fri, 7 Jul 2023 14:54:39 -0700 Subject: [PATCH 1077/1699] Clarifying what the CLI does with dynamic content on next.js (per customer feedback). (#6093) --- src/frameworks/docs/nextjs.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/frameworks/docs/nextjs.md b/src/frameworks/docs/nextjs.md index 4f8f5e4e65d..dad8d3754f5 100644 --- a/src/frameworks/docs/nextjs.md +++ b/src/frameworks/docs/nextjs.md @@ -71,6 +71,9 @@ and [getStaticPaths](https://nextjs.org/docs/basic-features/data-fetching/get-st The {{firebase_cli}} will detect usage of [getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props). +In such cases the {{cli}} will deploy functions to {{cloud_functions_full}} to run dynamic +server code. You can view information about these functions, such as their domain and runtime configuration, in the [Firebase console](https://console.firebase.google.com/project/_/functions). + ## Configure {{hosting}} behavior with `next.config.js` From c37634abd3b3b83c34cc46fbbef685073d35a850 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 7 Jul 2023 15:33:25 -0700 Subject: [PATCH 1078/1699] Rewrite `src/localFunction.js` in TypeScript. (#6092) `src/localFunction.js` is used to power the `functions:shell` command. We need to make substantial changes to it to properly support 2nd Gen functions (https://github.com/firebase/firebase-tools/issues/6089). Rewriting the source in modern TypeScript as a prep. --- CHANGELOG.md | 4 +- src/functionsShellCommandAction.ts | 4 +- src/localFunction.js | 221 ------------------------- src/localFunction.ts | 250 +++++++++++++++++++++++++++++ src/test/localFunction.spec.js | 64 -------- src/test/localFunction.spec.ts | 73 +++++++++ 6 files changed, 327 insertions(+), 289 deletions(-) delete mode 100644 src/localFunction.js create mode 100644 src/localFunction.ts delete mode 100644 src/test/localFunction.spec.js create mode 100644 src/test/localFunction.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6727ddeea8c..5a02bc44461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,2 @@ -Disables KeepAlive timeout when debugger is attached to the functions emulator. (#6069) -Fixed an issue where `database:list` would have inaccurate results. (#6063) +- Disables KeepAlive timeout when debugger is attached to the functions emulator. (#6069) +- Fixed an issue where `database:list` would have inaccurate results. (#6063) diff --git a/src/functionsShellCommandAction.ts b/src/functionsShellCommandAction.ts index 9d637803aae..44280b53db9 100644 --- a/src/functionsShellCommandAction.ts +++ b/src/functionsShellCommandAction.ts @@ -6,7 +6,7 @@ import * as request from "request"; import * as util from "util"; import { FunctionsServer } from "./serve/functions"; -import * as LocalFunction from "./localFunction"; +import LocalFunction from "./localFunction"; import * as utils from "./utils"; import { logger } from "./logger"; import * as shell from "./emulator/functionsEmulatorShell"; @@ -99,7 +99,7 @@ export const actionFunction = async (options: Options) => { if (emulator.emulatedFunctions.includes(trigger.id)) { const localFunction = new LocalFunction(trigger, emulator.urls, emulator); const triggerNameDotNotation = trigger.name.replace(/-/g, "."); - _.set(context, triggerNameDotNotation, localFunction.call); + _.set(context, triggerNameDotNotation, localFunction.makeFn()); } } context.help = diff --git a/src/localFunction.js b/src/localFunction.js deleted file mode 100644 index 629e69241ec..00000000000 --- a/src/localFunction.js +++ /dev/null @@ -1,221 +0,0 @@ -"use strict"; - -var _ = require("lodash"); -var request = require("request"); - -var { encodeFirestoreValue } = require("./firestore/encodeFirestoreValue"); -var utils = require("./utils"); - -/** - * @constructor - * @this LocalFunction - * - * @param {object} trigger - * @param {object=} urls - * @param {object=} controller - */ -var LocalFunction = function (trigger, urls, controller) { - const isCallable = _.get(trigger, ["labels", "deployment-callable"], "false"); - - this.id = trigger.id; - this.name = trigger.name; - this.eventTrigger = trigger.eventTrigger; - this.httpsTrigger = trigger.httpsTrigger; - this.controller = controller; - this.url = _.get(urls, this.id); - - if (this.httpsTrigger) { - if (isCallable == "true") { - this.call = this._constructCallableFunc.bind(this); - } else { - this.call = request.defaults({ - callback: this._requestCallBack, - baseUrl: this.url, - uri: "", - }); - } - } else { - this.call = this._call.bind(this); - } -}; - -LocalFunction.prototype._isDatabaseFunc = function (eventTrigger) { - return utils.getFunctionsEventProvider(eventTrigger.eventType) === "Database"; -}; - -LocalFunction.prototype._isFirestoreFunc = function (eventTrigger) { - return utils.getFunctionsEventProvider(eventTrigger.eventType) === "Firestore"; -}; - -LocalFunction.prototype._substituteParams = function (resource, params) { - var wildcardRegex = new RegExp("{[^/{}]*}", "g"); - return resource.replace(wildcardRegex, function (wildcard) { - var wildcardNoBraces = wildcard.slice(1, -1); // .slice removes '{' and '}' from wildcard - var sub = _.get(params, wildcardNoBraces); - return sub || wildcardNoBraces + utils.randomInt(1, 9); - }); -}; - -LocalFunction.prototype._constructCallableFunc = function (data, opts) { - opts = opts || {}; - - var headers = {}; - if (opts.instanceIdToken) { - headers["Firebase-Instance-ID-Token"] = opts.instanceIdToken; - } - - return request.post({ - callback: this._requestCallBack, - baseUrl: this.url, - uri: "", - body: { data: data }, - json: true, - headers: headers, - }); -}; - -LocalFunction.prototype._constructAuth = function (auth, authType) { - if (_.get(auth, "admin") || _.get(auth, "variable")) { - return auth; // User is providing the wire auth format already. - } - if (typeof authType !== "undefined") { - switch (authType) { - case "USER": - return { - variable: { - uid: _.get(auth, "uid", ""), - token: _.get(auth, "token", {}), - }, - }; - case "ADMIN": - if (_.get(auth, "uid") || _.get(auth, "token")) { - throw new Error("authType and auth are incompatible."); - } - return { admin: true }; - case "UNAUTHENTICATED": - if (_.get(auth, "uid") || _.get(auth, "token")) { - throw new Error("authType and auth are incompatible."); - } - return { admin: false }; - default: - throw new Error( - "Unrecognized authType, valid values are: " + "ADMIN, USER, and UNAUTHENTICATED" - ); - } - } - if (auth) { - return { - variable: { - uid: auth.uid, - token: auth.token || {}, - }, - }; - } - // Default to admin - return { admin: true }; -}; - -LocalFunction.prototype._makeFirestoreValue = function (input) { - if (typeof input === "undefined" || _.isEmpty(input)) { - // Document does not exist. - return {}; - } - if (typeof input !== "object") { - throw new Error("Firestore data must be key-value pairs."); - } - var currentTime = new Date().toISOString(); - return { - fields: encodeFirestoreValue(input), - createTime: currentTime, - updateTime: currentTime, - }; -}; - -LocalFunction.prototype._requestCallBack = function (err, response, body) { - if (err) { - return console.warn("\nERROR SENDING REQUEST: " + err); - } - var status = response ? response.statusCode + ", " : ""; - - // If the body is a string we want to check if we can parse it as JSON - // and pretty-print it. We can't blindly stringify because stringifying - // a string results in some ugly escaping. - var bodyString = body; - if (typeof body === "string") { - try { - bodyString = JSON.stringify(JSON.parse(bodyString), null, 2); - } catch (e) { - // Ignore - } - } else { - bodyString = JSON.stringify(body, null, 2); - } - - return console.log("\nRESPONSE RECEIVED FROM FUNCTION: " + status + bodyString); -}; - -LocalFunction.prototype._call = function (data, opts) { - opts = opts || {}; - var operationType; - var dataPayload; - - if (this.httpsTrigger) { - this.controller.call(this.name, data || {}); - } else if (this.eventTrigger) { - if (this._isDatabaseFunc(this.eventTrigger)) { - operationType = utils.last(this.eventTrigger.eventType.split(".")); - switch (operationType) { - case "create": - dataPayload = { - data: null, - delta: data, - }; - break; - case "delete": - dataPayload = { - data: data, - delta: null, - }; - break; - default: - // 'update' or 'write' - dataPayload = { - data: data.before, - delta: data.after, - }; - } - opts.resource = this._substituteParams(this.eventTrigger.resource, opts.params); - opts.auth = this._constructAuth(opts.auth, opts.authType); - this.controller.call(this.name, dataPayload, opts); - } else if (this._isFirestoreFunc(this.eventTrigger)) { - operationType = utils.last(this.eventTrigger.eventType.split(".")); - switch (operationType) { - case "create": - dataPayload = { - value: this._makeFirestoreValue(data), - oldValue: {}, - }; - break; - case "delete": - dataPayload = { - value: {}, - oldValue: this._makeFirestoreValue(data), - }; - break; - default: - // 'update' or 'write' - dataPayload = { - value: this._makeFirestoreValue(data.after), - oldValue: this._makeFirestoreValue(data.before), - }; - } - opts.resource = this._substituteParams(this.eventTrigger.resource, opts.params); - this.controller.call(this.name, dataPayload, opts); - } else { - this.controller.call(this.name, data || {}, opts); - } - } - return "Successfully invoked function."; -}; - -module.exports = LocalFunction; diff --git a/src/localFunction.ts b/src/localFunction.ts new file mode 100644 index 00000000000..82534e1eda8 --- /dev/null +++ b/src/localFunction.ts @@ -0,0 +1,250 @@ +import * as request from "request"; + +import * as utils from "./utils"; +import { encodeFirestoreValue } from "./firestore/encodeFirestoreValue"; +import { EmulatedTriggerDefinition } from "./emulator/functionsEmulatorShared"; +import { FunctionsEmulatorShell } from "./emulator/functionsEmulatorShell"; +import { AuthMode } from "./emulator/events/types"; + +type AuthType = "USER" | "ADMIN" | "UNAUTHENTICATED"; + +type EventOptions = { + params?: Record; + authType?: AuthType; + auth?: Partial & { + uid?: string; + token?: string; + }; + resource?: string; +}; + +/** + * LocalFunction produces EmulatedTriggerDefinition into a function that can be called inside the nodejs repl. + */ +export default class LocalFunction { + private url?: string; + private paramWildcardRegex = new RegExp("{[^/{}]*}", "g"); + + constructor( + private trigger: EmulatedTriggerDefinition, + urls: Record, + private controller: FunctionsEmulatorShell + ) { + this.url = urls[trigger.id]; + } + + private substituteParams(resource: string, params?: Record): string { + if (!params) { + return resource; + } + return resource.replace(this.paramWildcardRegex, (wildcard: string) => { + const wildcardNoBraces = wildcard.slice(1, -1); // .slice removes '{' and '}' from wildcard + const sub = params?.[wildcardNoBraces]; + return sub || `${wildcardNoBraces}${utils.randomInt(1, 9)}`; + }); + } + + private constructCallableFunc( + data: string | object, + opts: { instanceIdToken?: string } + ): request.Request { + opts = opts || {}; + + const headers: Record = {}; + if (opts.instanceIdToken) { + headers["Firebase-Instance-ID-Token"] = opts.instanceIdToken; + } + + return request.post({ + callback: (...args) => this.requestCallBack(...args), + baseUrl: this.url, + uri: "", + body: { data }, + json: true, + headers: headers, + }); + } + + constructAuth(auth?: EventOptions["auth"], authType?: AuthType): AuthMode { + if (auth?.admin || auth?.variable) { + return { + admin: auth.admin || false, + variable: auth.variable, + }; // User is providing the wire auth format already. + } + if (authType) { + switch (authType) { + case "USER": + return { + admin: false, + variable: { + uid: auth?.uid ?? "", + token: auth?.token ?? {}, + }, + }; + case "ADMIN": + if (auth?.uid || auth?.token) { + throw new Error("authType and auth are incompatible."); + } + return { admin: true }; + case "UNAUTHENTICATED": + if (auth?.uid || auth?.token) { + throw new Error("authType and auth are incompatible."); + } + return { admin: false }; + default: + throw new Error( + "Unrecognized authType, valid values are: " + "ADMIN, USER, and UNAUTHENTICATED" + ); + } + } + if (auth) { + return { + admin: false, + variable: { + uid: auth.uid ?? "", + token: auth.token || {}, + }, + }; + } + // Default to admin + return { admin: true }; + } + + makeFirestoreValue(input?: unknown): { + fields?: Record; + createTime?: string; + updateTime?: string; + } { + if ( + typeof input === "undefined" || + input === null || + (typeof input === "object" && Object.keys(input).length === 0) + ) { + // Document does not exist. + return {}; + } + if (typeof input !== "object") { + throw new Error("Firestore data must be key-value pairs."); + } + const currentTime = new Date().toISOString(); + return { + fields: encodeFirestoreValue(input), + createTime: currentTime, + updateTime: currentTime, + }; + } + + private requestCallBack(err: unknown, response: request.Response, body: string | object): void { + if (err) { + return console.warn("\nERROR SENDING REQUEST: " + err); + } + const status = response ? response.statusCode + ", " : ""; + + // If the body is a string we want to check if we can parse it as JSON + // and pretty-print it. We can't blindly stringify because stringifying + // a string results in some ugly escaping. + let bodyString = body; + if (typeof bodyString === "string") { + try { + bodyString = JSON.stringify(JSON.parse(bodyString), null, 2); + } catch (e) { + // Ignore + } + } else { + bodyString = JSON.stringify(body, null, 2); + } + + return console.log("\nRESPONSE RECEIVED FROM FUNCTION: " + status + bodyString); + } + + private isDatabaseFn(eventTrigger: Required["eventTrigger"]): boolean { + return utils.getFunctionsEventProvider(eventTrigger.eventType) === "Database"; + } + private isFirestoreFunc( + eventTrigger: Required["eventTrigger"] + ): boolean { + return utils.getFunctionsEventProvider(eventTrigger.eventType) === "Firestore"; + } + + private triggerEvent(data: unknown, opts?: EventOptions): void { + opts = opts || {}; + let operationType; + let dataPayload; + + if (this.trigger.httpsTrigger) { + this.controller.call(this.trigger.name, data || {}, opts); + } else if (this.trigger.eventTrigger) { + if (this.isDatabaseFn(this.trigger.eventTrigger)) { + operationType = utils.last(this.trigger.eventTrigger.eventType.split(".")); + switch (operationType) { + case "create": + dataPayload = { + data: null, + delta: data, + }; + break; + case "delete": + dataPayload = { + data: data, + delta: null, + }; + break; + default: + // 'update' or 'write' + dataPayload = { + data: (data as any).before, + delta: (data as any).after, + }; + } + opts.resource = this.substituteParams(this.trigger.eventTrigger.resource!, opts.params); + opts.auth = this.constructAuth(opts.auth, opts.authType); + this.controller.call(this.trigger.name, dataPayload, opts); + } else if (this.isFirestoreFunc(this.trigger.eventTrigger)) { + operationType = utils.last(this.trigger.eventTrigger.eventType.split(".")); + switch (operationType) { + case "create": + dataPayload = { + value: this.makeFirestoreValue(data), + oldValue: {}, + }; + break; + case "delete": + dataPayload = { + value: {}, + oldValue: this.makeFirestoreValue(data), + }; + break; + default: + // 'update' or 'write' + dataPayload = { + value: this.makeFirestoreValue((data as any).after), + oldValue: this.makeFirestoreValue((data as any).before), + }; + } + opts.resource = this.substituteParams(this.trigger.eventTrigger.resource!, opts.params); + this.controller.call(this.trigger.name, dataPayload, opts); + } else { + this.controller.call(this.trigger.name, data || {}, opts); + } + } + return console.log("Successfully invoked function."); + } + + makeFn() { + if (this.trigger.httpsTrigger) { + const isCallable = !!this.trigger.labels?.["deployment-callable"]; + if (isCallable) { + return (data: any, opt: any) => this.constructCallableFunc(data, opt); + } else { + return request.defaults({ + callback: (...args) => this.requestCallBack(...args), + baseUrl: this.url, + uri: "", + }); + } + } else { + return (data: any, opt: any) => this.triggerEvent(data, opt); + } + } +} diff --git a/src/test/localFunction.spec.js b/src/test/localFunction.spec.js deleted file mode 100644 index 2b47d7b2879..00000000000 --- a/src/test/localFunction.spec.js +++ /dev/null @@ -1,64 +0,0 @@ -"use strict"; - -var chai = require("chai"); -var expect = chai.expect; - -var LocalFunction = require("../localFunction"); - -describe("localFunction._constructAuth", function () { - var lf = new LocalFunction({}); - - describe("#_constructAuth", function () { - var constructAuth = lf._constructAuth; - - it("warn if opts.auth and opts.authType are conflicting", function () { - expect(function () { - return constructAuth({ uid: "something" }, "UNAUTHENTICATED"); - }).to.throw("incompatible"); - - expect(function () { - return constructAuth({ uid: "something" }, "ADMIN"); - }).to.throw("incompatible"); - }); - - it("construct the correct auth for admin users", function () { - expect(constructAuth(undefined, "ADMIN")).to.deep.equal({ admin: true }); - }); - - it("construct the correct auth for unauthenticated users", function () { - expect(constructAuth(undefined, "UNAUTHENTICATED")).to.deep.equal({ - admin: false, - }); - }); - - it("construct the correct auth for authenticated users", function () { - expect(constructAuth(undefined, "USER")).to.deep.equal({ - variable: { uid: "", token: {} }, - }); - expect(constructAuth({ uid: "11" }, "USER")).to.deep.equal({ - variable: { uid: "11", token: {} }, - }); - }); - - it("leaves auth untouched if it already follows wire format", function () { - var auth = { variable: { uid: "something" } }; - expect(constructAuth(auth)).to.deep.equal(auth); - }); - }); - - describe("localFunction._makeFirestoreValue", function () { - var makeFirestoreValue = lf._makeFirestoreValue; - - it("returns {} when there is no data", function () { - expect(makeFirestoreValue()).to.deep.equal({}); - expect(makeFirestoreValue(null)).to.deep.equal({}); - expect(makeFirestoreValue({})).to.deep.equal({}); - }); - - it("throws error when data is not key-value pairs", function () { - expect(function () { - return makeFirestoreValue("string"); - }).to.throw(Error); - }); - }); -}); diff --git a/src/test/localFunction.spec.ts b/src/test/localFunction.spec.ts new file mode 100644 index 00000000000..e1f7c9f6c78 --- /dev/null +++ b/src/test/localFunction.spec.ts @@ -0,0 +1,73 @@ +import { expect } from "chai"; + +import LocalFunction from "../localFunction"; +import { EmulatedTriggerDefinition } from "../emulator/functionsEmulatorShared"; +import { FunctionsEmulatorShell } from "../emulator/functionsEmulatorShell"; + +const EMULATED_TRIGGER: EmulatedTriggerDefinition = { + id: "fn", + region: "us-central1", + platform: "gcfv1", + availableMemoryMb: 1024, + entryPoint: "test-resource", + name: "test-resource", + timeoutSeconds: 3, +}; + +describe("constructAuth", () => { + const lf = new LocalFunction(EMULATED_TRIGGER, {}, {} as FunctionsEmulatorShell); + + describe("#_constructAuth", () => { + it("warn if opts.auth and opts.authType are conflicting", () => { + expect(() => { + return lf.constructAuth({ uid: "something" }, "UNAUTHENTICATED"); + }).to.throw("incompatible"); + + expect(() => { + return lf.constructAuth({ admin: false, uid: "something" }, "ADMIN"); + }).to.throw("incompatible"); + }); + + it("construct the correct auth for admin users", () => { + expect(lf.constructAuth(undefined, "ADMIN")).to.deep.equal({ admin: true }); + }); + + it("construct the correct auth for unauthenticated users", () => { + expect(lf.constructAuth(undefined, "UNAUTHENTICATED")).to.deep.equal({ + admin: false, + }); + }); + + it("construct the correct auth for authenticated users", () => { + expect(lf.constructAuth(undefined, "USER")).to.deep.equal({ + admin: false, + variable: { uid: "", token: {} }, + }); + expect(lf.constructAuth({ uid: "11" }, "USER")).to.deep.equal({ + admin: false, + variable: { uid: "11", token: {} }, + }); + }); + + it("leaves auth untouched if it already follows wire format", () => { + const auth = { admin: false, variable: { uid: "something" } }; + expect(lf.constructAuth(auth)).to.deep.equal(auth); + }); + }); +}); + +describe("makeFirestoreValue", () => { + const lf = new LocalFunction(EMULATED_TRIGGER, {}, {} as FunctionsEmulatorShell); + + it("returns {} when there is no data", () => { + expect(lf.makeFirestoreValue()).to.deep.equal({}); + expect(lf.makeFirestoreValue(null)).to.deep.equal({}); + expect(lf.makeFirestoreValue({})).to.deep.equal({}); + }); + + it("throws error when data is not key-value pairs", () => { + expect(() => { + return lf.makeFirestoreValue("string"); + }).to.throw(Error); + }); +}); From f4581809c3af60fe58c10a143df0679f76d4b216 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 10 Jul 2023 13:27:36 -0700 Subject: [PATCH 1079/1699] VSCode plugin: Add UX improvements (#6091) --- firebase-vscode/common/messaging/protocol.ts | 10 ++- firebase-vscode/package-lock.json | 4 +- firebase-vscode/package.json | 2 +- firebase-vscode/src/logger-wrapper.ts | 74 +++++++++++++++ firebase-vscode/src/workflow.ts | 49 +++------- firebase-vscode/webviews/SidebarApp.tsx | 7 +- .../webviews/components/AccountSection.tsx | 90 +++++++++++++------ .../webviews/components/DeployPanel.tsx | 29 ++++-- firebase-vscode/webviews/globals/ux-text.ts | 12 ++- 9 files changed, 197 insertions(+), 80 deletions(-) diff --git a/firebase-vscode/common/messaging/protocol.ts b/firebase-vscode/common/messaging/protocol.ts index eedd4bbb249..79203a82a60 100644 --- a/firebase-vscode/common/messaging/protocol.ts +++ b/firebase-vscode/common/messaging/protocol.ts @@ -64,7 +64,7 @@ export interface WebviewToExtensionParamsMap { /** * Equivalent to the `firebase emulators:start` command. */ - launchEmulators : { + launchEmulators: { emulatorUiSelections: EmulatorUiSelections, }; @@ -96,7 +96,11 @@ export interface ExtensionToWebviewParamsMap { * Notifies webview when user has successfully selected a hosting folder * and it has been written to firebase.json. */ - notifyHostingInitDone: { projectId: string, folderPath?: string }; + notifyHostingInitDone: { + projectId: string, + folderPath?: string + framework?: string + }; /** * Notify webview of status of deployment attempt. @@ -119,7 +123,7 @@ export interface ExtensionToWebviewParamsMap { notifyPreviewChannelResponse: { id: string }; notifyEmulatorsStopped: {}; - notifyRunningEmulatorInfo: RunningEmulatorInfo ; + notifyRunningEmulatorInfo: RunningEmulatorInfo; notifyEmulatorImportFolder: { folder: string }; } diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index ccbdb5e7b61..1d261781bf8 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-vscode", - "version": "0.0.23-alpha.2", + "version": "0.0.23-alpha.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-vscode", - "version": "0.0.23-alpha.2", + "version": "0.0.23-alpha.3", "dependencies": { "@vscode/codicons": "0.0.30", "@vscode/webview-ui-toolkit": "^1.2.1", diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index e18acd3939c..2539455a726 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -3,7 +3,7 @@ "displayName": "firebase-vscode", "publisher": "firebase", "description": "VSCode Extension for Firebase", - "version": "0.0.23-alpha.2", + "version": "0.0.23-alpha.3", "engines": { "vscode": "^1.69.0" }, diff --git a/firebase-vscode/src/logger-wrapper.ts b/firebase-vscode/src/logger-wrapper.ts index f736403f32a..54ab62d1300 100644 --- a/firebase-vscode/src/logger-wrapper.ts +++ b/firebase-vscode/src/logger-wrapper.ts @@ -1,10 +1,24 @@ +import * as path from "path"; +import * as vscode from "vscode"; +import { transports, format } from "winston"; +import Transport from "winston-transport"; +import stripAnsi from "strip-ansi"; +import { SPLAT } from "triple-beam"; import { logger as cliLogger } from "../../src/logger"; +import { setupLoggers, tryStringify } from "../../src/utils"; import { setInquirerLogger } from "./stubs/inquirer-stub"; +import { getRootFolders } from "./config-files"; export const pluginLogger: Record void> = {}; const logLevels = ['debug', 'info', 'log', 'warn', 'error']; +const outputChannel = vscode.window.createOutputChannel('Firebase'); + +export function showOutputChannel() { + outputChannel.show(); +} + for (const logLevel of logLevels) { pluginLogger[logLevel] = (...args) => { const prefixedArgs = ['[Firebase Plugin]', ...args]; @@ -12,4 +26,64 @@ for (const logLevel of logLevels) { }; } +/** + * Logging setup for logging to console and to file. + */ +export function logSetup({ shouldWriteDebug, debugLogPath }: { + shouldWriteDebug: boolean, + debugLogPath: string +}) { + // Log to console (use built in CLI functionality) + process.env.DEBUG = 'true'; + setupLoggers(); + + // Log to file + // Only log to file if firebase.debug extension setting is true. + if (shouldWriteDebug) { + // Re-implement file logger call from ../../src/bin/firebase.ts to not bring + // in the entire firebase.ts file + const rootFolders = getRootFolders(); + const filePath = debugLogPath || path.join(rootFolders[0], 'firebase-plugin-debug.log'); + pluginLogger.info('Logging to path', filePath); + cliLogger.add( + new transports.File({ + level: "debug", + filename: filePath, + format: format.printf((info) => { + const segments = [info.message, ...(info[SPLAT] || [])] + .map(tryStringify); + return `[${info.level}] ${stripAnsi(segments.join(" "))}`; + }), + }) + ); + cliLogger.add( + new VSCodeOutputTransport({ level: "info" }) + ); + } +} + +/** + * Custom Winston transport that writes to VSCode output channel. + * Write only "info" and greater to avoid too much spam from "debug". + */ +class VSCodeOutputTransport extends Transport { + constructor(opts) { + super(opts); + } + log(info, callback) { + setImmediate(() => { + this.emit('logged', info); + }); + const segments = [info.message, ...(info[SPLAT] || [])] + .map(tryStringify); + const text = `[${info.level}] ${stripAnsi(segments.join(" "))}`; + + if (info.level !== 'debug') { + // info or greater: write to output window + outputChannel.appendLine(text); + } + + callback(); + } +} setInquirerLogger(pluginLogger); diff --git a/firebase-vscode/src/workflow.ts b/firebase-vscode/src/workflow.ts index 5327aa871f0..2be4e0e1975 100644 --- a/firebase-vscode/src/workflow.ts +++ b/firebase-vscode/src/workflow.ts @@ -1,8 +1,4 @@ -import * as path from "path"; import * as vscode from "vscode"; -import { transports, format } from "winston"; -import stripAnsi from "strip-ansi"; -import { SPLAT } from "triple-beam"; import { ExtensionContext, workspace } from "vscode"; import { FirebaseProjectMetadata } from "../../src/types/project"; @@ -19,16 +15,13 @@ import { import { User } from "../../src/types/auth"; import { currentOptions } from "./options"; import { selectProjectInMonospace } from "../../src/monospace"; -import { setupLoggers, tryStringify } from "../../src/utils"; -import { pluginLogger } from "./logger-wrapper"; -import { logger } from '../../src/logger'; +import { logSetup, pluginLogger, showOutputChannel } from "./logger-wrapper"; import { discover } from "../../src/frameworks"; import { setEnabled } from "../../src/experiments"; import { readAndSendFirebaseConfigs, setupFirebaseJsonAndRcFileSystemWatcher, - updateFirebaseRCProject, - getRootFolders + updateFirebaseRCProject } from "./config-files"; import { ServiceAccountUser } from "../common/types"; @@ -37,6 +30,7 @@ export let currentUser: User | ServiceAccountUser; // Stores a mapping from user email to list of projects for that user let projectsUserMapping = new Map(); let channels = null; +let currentFramework: string | undefined; async function fetchUsers() { const accounts = await getAccounts(); @@ -116,32 +110,8 @@ export async function setupWorkflow( if (useFrameworks) { setEnabled('webframeworks', true); } - /** - * Logging setup for logging to console and to file. - */ - // Sets up CLI logger to log to console - process.env.DEBUG = 'true'; - setupLoggers(); - // Only log to file if firebase.debug extension setting is true. - if (shouldWriteDebug) { - // Re-implement file logger call from ../../src/bin/firebase.ts to not bring - // in the entire firebase.ts file - const rootFolders = getRootFolders(); - const filePath = debugLogPath || path.join(rootFolders[0], 'firebase-plugin-debug.log'); - pluginLogger.info('Logging to path', filePath); - logger.add( - new transports.File({ - level: "debug", - filename: filePath, - format: format.printf((info) => { - const segments = [info.message, ...(info[SPLAT] || [])] - .map(tryStringify); - return `[${info.level}] ${stripAnsi(segments.join(" "))}`; - }), - }) - ); - } + logSetup({ shouldWriteDebug, debugLogPath }); /** * Call pluginLogger with log arguments received from webview. @@ -227,6 +197,9 @@ export async function setupWorkflow( broker.on("selectAndInitHostingFolder", selectAndInitHosting); broker.on("hostingDeploy", async ({ target: deployTarget }) => { + showOutputChannel(); + pluginLogger.info(`Starting deployment of project ` + + `${currentOptions.projectId} to channel: ${deployTarget}`); const { success, consoleUrl, hostingUrl } = await deployToHosting( currentOptions.config, deployTarget @@ -323,14 +296,14 @@ export async function setupWorkflow( } async function selectAndInitHosting({ projectId, singleAppSupport }) { - let discoveredFramework; + currentFramework = undefined; // Note: discover() takes a few seconds. No need to block users that don't // have frameworks support enabled. if (useFrameworks) { - discoveredFramework = useFrameworks && await discover(currentOptions.cwd, false); + currentFramework = useFrameworks && await discover(currentOptions.cwd, false); pluginLogger.debug('Searching for a web framework in this project.'); } - if (discoveredFramework) { + if (currentFramework) { pluginLogger.debug('Detected web framework, launching frameworks init.'); await initHosting({ spa: singleAppSupport, @@ -358,7 +331,7 @@ export async function setupWorkflow( } readAndSendFirebaseConfigs(broker, context); broker.send("notifyHostingInitDone", - { projectId, folderPath: currentOptions.cwd }); + { projectId, folderPath: currentOptions.cwd, framework: currentFramework }); await fetchChannels(true); } } diff --git a/firebase-vscode/webviews/SidebarApp.tsx b/firebase-vscode/webviews/SidebarApp.tsx index e3f75f7a2ed..768377bf7d1 100644 --- a/firebase-vscode/webviews/SidebarApp.tsx +++ b/firebase-vscode/webviews/SidebarApp.tsx @@ -19,6 +19,7 @@ export function SidebarApp() { const [env, setEnv] = useState<{ isMonospace: boolean }>(); const [channels, setChannels] = useState(null); const [user, setUser] = useState(null); + const [framework, setFramework] = useState(null); /** * null - has not finished checking yet * empty array - finished checking, no users logged in @@ -86,9 +87,12 @@ export function SidebarApp() { setUser(user); }); - broker.on("notifyHostingInitDone", ({ projectId, folderPath }) => { + broker.on("notifyHostingInitDone", ({ projectId, folderPath, framework }) => { webLogger.debug(`notifyHostingInitDone: ${projectId}, ${folderPath}`); setHostingOnboarded(true); + if (framework) { + setFramework(framework); + } }); broker.on("notifyHostingDeploy", ({ success }) => { @@ -134,6 +138,7 @@ export function SidebarApp() { setHostingState={setHostingState} projectId={projectId} channels={channels} + framework={framework} /> )} diff --git a/firebase-vscode/webviews/components/AccountSection.tsx b/firebase-vscode/webviews/components/AccountSection.tsx index dba9ddc3896..02f7ec8e79f 100644 --- a/firebase-vscode/webviews/components/AccountSection.tsx +++ b/firebase-vscode/webviews/components/AccountSection.tsx @@ -57,12 +57,22 @@ export function AccountSection({ currentUserElement = user.email; } } - return ( -
    -
    + + + + + + +

    Hello Vite!

    +
    + +
    +

    + Click on the Vite logo to learn more +

    +